From d5e46c1124c306dddc99a59dda90d1c0541cc855 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Tue, 11 Jan 2022 11:21:09 +0300 Subject: [PATCH 001/109] Add Flow Compatibility Management API base route --- Development/cmake/NmosCppLibraries.cmake | 2 + Development/nmos-cpp-node/config.json | 4 ++ Development/nmos/api_utils.h | 5 ++ Development/nmos/flowcompatibility_api.cpp | 69 ++++++++++++++++++++++ Development/nmos/flowcompatibility_api.h | 19 ++++++ Development/nmos/is11_versions.h | 26 ++++++++ Development/nmos/node_resources.cpp | 21 +++++++ Development/nmos/node_server.cpp | 4 ++ Development/nmos/settings.cpp | 1 + Development/nmos/settings.h | 4 ++ 10 files changed, 155 insertions(+) create mode 100644 Development/nmos/flowcompatibility_api.cpp create mode 100644 Development/nmos/flowcompatibility_api.h create mode 100644 Development/nmos/is11_versions.h diff --git a/Development/cmake/NmosCppLibraries.cmake b/Development/cmake/NmosCppLibraries.cmake index b6d6d972a..03c2859fc 100644 --- a/Development/cmake/NmosCppLibraries.cmake +++ b/Development/cmake/NmosCppLibraries.cmake @@ -763,6 +763,7 @@ set(NMOS_CPP_NMOS_SOURCES nmos/events_ws_api.cpp nmos/events_ws_client.cpp nmos/filesystem_route.cpp + nmos/flowcompatibility_api.cpp nmos/group_hint.cpp nmos/id.cpp nmos/lldp_handler.cpp @@ -836,6 +837,7 @@ set(NMOS_CPP_NMOS_HEADERS nmos/events_ws_api.h nmos/events_ws_client.h nmos/filesystem_route.h + nmos/flowcompatibility_api.h nmos/format.h nmos/group_hint.h nmos/health.h diff --git a/Development/nmos-cpp-node/config.json b/Development/nmos-cpp-node/config.json index aa42a39f5..dafe1ab0f 100644 --- a/Development/nmos-cpp-node/config.json +++ b/Development/nmos-cpp-node/config.json @@ -85,6 +85,9 @@ // is09_versions [registry, node]: used to specify the enabled API versions for a version-locked configuration //"is09_versions": ["v1.0"], + // is11_versions [node]: used to specify the enabled API versions for a version-locked configuration + //"is11_versions": ["v1.0"], + // pri [registry, node]: used for the 'pri' TXT record; specifying nmos::service_priorities::no_priority (maximum value) disables advertisement completely //"pri": 100, @@ -116,6 +119,7 @@ //"events_port": 3216, //"events_ws_port": 3217, //"channelmapping_port": 3215, + //"flowcompatibility_port": 3218, // system_port [node]: used to construct request URLs for the System API (if not discovered via DNS-SD) //"system_port": 10641, diff --git a/Development/nmos/api_utils.h b/Development/nmos/api_utils.h index 2dda425a6..401a9894f 100644 --- a/Development/nmos/api_utils.h +++ b/Development/nmos/api_utils.h @@ -50,6 +50,8 @@ namespace nmos const route_pattern channelmapping_api = make_route_pattern(U("api"), U("channelmapping")); // IS-09 System API (originally specified in JT-NM TR-1001-1:2018 Annex A) const route_pattern system_api = make_route_pattern(U("api"), U("system")); + // IS-11 Flow Compatibility Management API + const route_pattern flowcompatibility_api = make_route_pattern(U("api"), U("flowcompatibility")); // API version pattern const route_pattern version = make_route_pattern(U("version"), U("v[0-9]+\\.[0-9]+")); @@ -80,6 +82,9 @@ namespace nmos const route_pattern outputSubroute = make_route_pattern(U("outputSubroute"), U("properties|sourceid|channels|caps")); const route_pattern activationId = make_route_pattern(U("activationId"), U("[a-zA-Z0-9\\-_]+")); + // Flow Compatibility Management API + const route_pattern flowCompatibilityResourceType = make_route_pattern(U("resourceType"), U("senders|receivers|inputs|outputs")); + // Common patterns const route_pattern resourceId = make_route_pattern(U("resourceId"), U("[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}")); } diff --git a/Development/nmos/flowcompatibility_api.cpp b/Development/nmos/flowcompatibility_api.cpp new file mode 100644 index 000000000..681296ffb --- /dev/null +++ b/Development/nmos/flowcompatibility_api.cpp @@ -0,0 +1,69 @@ +#include "nmos/flowcompatibility_api.h" + +#include +#include "nmos/is11_versions.h" +#include "nmos/model.h" + +namespace nmos +{ + namespace experimental + { + web::http::experimental::listener::api_router make_unmounted_flowcompatibility_api(nmos::node_model& model, slog::base_gate& gate); + + web::http::experimental::listener::api_router make_flowcompatibility_api(nmos::node_model& model, slog::base_gate& gate) + { + using namespace web::http::experimental::listener::api_router_using_declarations; + + api_router flowcompatibility_api; + + flowcompatibility_api.support(U("/?"), methods::GET, [](http_request req, http_response res, const string_t&, const route_parameters&) + { + set_reply(res, status_codes::OK, nmos::make_sub_routes_body({ U("x-nmos/") }, req, res)); + return pplx::task_from_result(true); + }); + + flowcompatibility_api.support(U("/x-nmos/?"), methods::GET, [](http_request req, http_response res, const string_t&, const route_parameters&) + { + set_reply(res, status_codes::OK, nmos::make_sub_routes_body({ U("flowcompatibility/") }, req, res)); + return pplx::task_from_result(true); + }); + + const auto versions = with_read_lock(model.mutex, [&model] { return nmos::is11_versions::from_settings(model.settings); }); + flowcompatibility_api.support(U("/x-nmos/") + nmos::patterns::flowcompatibility_api.pattern + U("/?"), methods::GET, [versions](http_request req, http_response res, const string_t&, const route_parameters&) + { + set_reply(res, status_codes::OK, nmos::make_sub_routes_body(nmos::make_api_version_sub_routes(versions), req, res)); + return pplx::task_from_result(true); + }); + + flowcompatibility_api.mount(U("/x-nmos/") + nmos::patterns::flowcompatibility_api.pattern + U("/") + nmos::patterns::version.pattern, make_unmounted_flowcompatibility_api(model, gate)); + + return flowcompatibility_api; + } + + web::http::experimental::listener::api_router make_unmounted_flowcompatibility_api(nmos::node_model& model, slog::base_gate& gate_) + { + using namespace web::http::experimental::listener::api_router_using_declarations; + + api_router flowcompatibility_api; + + // check for supported API version + const auto versions = with_read_lock(model.mutex, [&model] { return nmos::is11_versions::from_settings(model.settings); }); + + flowcompatibility_api.support(U(".*"), nmos::details::make_api_version_handler(versions, gate_)); + + flowcompatibility_api.support(U("/?"), methods::GET, [](http_request req, http_response res, const string_t&, const route_parameters&) + { + set_reply(res, status_codes::OK, nmos::make_sub_routes_body({ U("senders/"), U("receivers/"), U("inputs/"), U("outputs/") }, req, res)); + return pplx::task_from_result(true); + }); + + flowcompatibility_api.support(U("/") + nmos::patterns::flowCompatibilityResourceType.pattern + U("/?"), methods::GET, [](http_request req, http_response res, const string_t&, const route_parameters&) + { + set_reply(res, status_codes::OK, web::json::value::array()); + return pplx::task_from_result(true); + }); + + return flowcompatibility_api; + } + } +} diff --git a/Development/nmos/flowcompatibility_api.h b/Development/nmos/flowcompatibility_api.h new file mode 100644 index 000000000..767194903 --- /dev/null +++ b/Development/nmos/flowcompatibility_api.h @@ -0,0 +1,19 @@ +#ifndef NMOS_FLOWCOMPATIBILITY_API_H +#define NMOS_FLOWCOMPATIBILITY_API_H + +#include "nmos/api_utils.h" +#include "nmos/slog.h" + +// Flow Compatibility Management API implementation +// See https://github.com/AMWA-TV/is-11/blob/v1.0-dev/APIs/FlowCompatibilityManagementAPI.raml +namespace nmos +{ + struct node_model; + + namespace experimental + { + web::http::experimental::listener::api_router make_flowcompatibility_api(nmos::node_model& model, slog::base_gate& gate); + } +} + +#endif diff --git a/Development/nmos/is11_versions.h b/Development/nmos/is11_versions.h new file mode 100644 index 000000000..b2ebca4a6 --- /dev/null +++ b/Development/nmos/is11_versions.h @@ -0,0 +1,26 @@ +#ifndef NMOS_IS11_VERSIONS_H +#define NMOS_IS11_VERSIONS_H + +#include +#include +#include "nmos/api_version.h" +#include "nmos/settings.h" + +namespace nmos +{ + namespace is11_versions + { + const api_version v1_0{ 1, 0 }; + + const std::set all{ nmos::is11_versions::v1_0 }; + + inline std::set from_settings(const nmos::settings& settings) + { + return settings.has_field(nmos::fields::is11_versions) + ? boost::copy_range>(nmos::fields::is11_versions(settings) | boost::adaptors::transformed([](const web::json::value& v) { return nmos::parse_api_version(v.as_string()); })) + : nmos::is11_versions::all; + } + } +} + +#endif diff --git a/Development/nmos/node_resources.cpp b/Development/nmos/node_resources.cpp index 7c75cf01a..0cd9530c7 100644 --- a/Development/nmos/node_resources.cpp +++ b/Development/nmos/node_resources.cpp @@ -16,6 +16,7 @@ #include "nmos/is05_versions.h" #include "nmos/is07_versions.h" #include "nmos/is08_versions.h" +#include "nmos/is11_versions.h" #include "nmos/media_type.h" #include "nmos/resource.h" #include "nmos/sdp_utils.h" // for nmos::make_components @@ -106,6 +107,26 @@ namespace nmos } } + if (0 <= nmos::fields::flowcompatibility_port(settings)) + { + for (const auto& version : nmos::is11_versions::from_settings(settings)) + { + auto flowcompatibility_uri = web::uri_builder() + .set_scheme(nmos::http_scheme(settings)) + .set_port(nmos::fields::flowcompatibility_port(settings)) + .set_path(U("/x-nmos/flowcompatibility/") + make_api_version(version)); + auto type = U("urn:x-nmos:control:fcm/") + make_api_version(version); + + for (const auto& host : hosts) + { + web::json::push_back(data[U("controls")], value_of({ + { U("href"), flowcompatibility_uri.set_host(host).to_uri().to_string() }, + { U("type"), type } + })); + } + } + } + if (0 <= nmos::experimental::fields::manifest_port(settings)) { // See https://specs.amwa.tv/nmos-parameter-registers/branches/main/device-control-types/manifest-base.html diff --git a/Development/nmos/node_server.cpp b/Development/nmos/node_server.cpp index 2e4469788..feb66f06d 100644 --- a/Development/nmos/node_server.cpp +++ b/Development/nmos/node_server.cpp @@ -5,6 +5,7 @@ #include "nmos/channelmapping_activation.h" #include "nmos/events_api.h" #include "nmos/events_ws_api.h" +#include "nmos/flowcompatibility_api.h" #include "nmos/logging_api.h" #include "nmos/manifest_api.h" #include "nmos/model.h" @@ -61,6 +62,9 @@ namespace nmos // Configure the Channel Mapping API node_server.api_routers[{ {}, nmos::fields::channelmapping_port(node_model.settings) }].mount({}, nmos::make_channelmapping_api(node_model, node_implementation.validate_map, gate)); + // Configure the Flow Compatibility API + node_server.api_routers[{ {}, nmos::fields::flowcompatibility_port(node_model.settings) }].mount({}, nmos::experimental::make_flowcompatibility_api(node_model, gate)); + auto& events_ws_api = node_server.ws_handlers[{ {}, nmos::fields::events_ws_port(node_model.settings) }]; events_ws_api.first = nmos::make_events_ws_api(node_model, events_ws_api.second, gate); diff --git a/Development/nmos/settings.cpp b/Development/nmos/settings.cpp index b6dd55546..189b2bfa6 100644 --- a/Development/nmos/settings.cpp +++ b/Development/nmos/settings.cpp @@ -73,6 +73,7 @@ namespace nmos if (!registry) web::json::insert(settings, std::make_pair(nmos::fields::connection_port, http_port)); if (!registry) web::json::insert(settings, std::make_pair(nmos::fields::events_port, http_port)); if (!registry) web::json::insert(settings, std::make_pair(nmos::fields::channelmapping_port, http_port)); + if (!registry) web::json::insert(settings, std::make_pair(nmos::fields::flowcompatibility_port, http_port)); // can't share a port between an http_listener and a websocket_listener, so don't apply this one... //if (!registry) web::json::insert(settings, std::make_pair(nmos::fields::events_ws_port, http_port)); if (!registry) web::json::insert(settings, std::make_pair(nmos::experimental::fields::manifest_port, http_port)); diff --git a/Development/nmos/settings.h b/Development/nmos/settings.h index c72502b18..711ec8e19 100644 --- a/Development/nmos/settings.h +++ b/Development/nmos/settings.h @@ -101,6 +101,9 @@ namespace nmos // is09_versions [registry, node]: used to specify the enabled API versions for a version-locked configuration const web::json::field_as_array is09_versions{ U("is09_versions") }; // when omitted, nmos::is09_versions::all is used + // is11_versions [node]: used to specify the enabled API versions for a version-locked configuration + const web::json::field_as_array is11_versions{ U("is11_versions") }; // when omitted, nmos::is11_versions::all is used + // pri [registry, node]: used for the 'pri' TXT record; specifying nmos::service_priorities::no_priority (maximum value) disables advertisement completely const web::json::field_as_integer_or pri{ U("pri"), 100 }; // default to highest_development_priority @@ -134,6 +137,7 @@ namespace nmos const web::json::field_as_integer_or events_port{ U("events_port"), 3216 }; const web::json::field_as_integer_or events_ws_port{ U("events_ws_port"), 3217 }; const web::json::field_as_integer_or channelmapping_port{ U("channelmapping_port"), 3215 }; + const web::json::field_as_integer_or flowcompatibility_port{ U("flowcompatibility_port"), 3218 }; // system_port [node]: used to construct request URLs for the System API (if not discovered via DNS-SD) const web::json::field_as_integer_or system_port{ U("system_port"), 10641 }; From 487818e725f77d34b103ddf0daec65a413fd5dcd Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Wed, 12 Jan 2022 03:26:36 +0300 Subject: [PATCH 002/109] Add IS-11 resource makers and implement all GET operations --- Development/cmake/NmosCppLibraries.cmake | 2 + .../nmos-cpp-node/node_implementation.cpp | 74 ++++ Development/nmos/api_utils.h | 3 + Development/nmos/flowcompatibility_api.cpp | 415 ++++++++++++++++++ .../nmos/flowcompatibility_resources.cpp | 165 +++++++ .../nmos/flowcompatibility_resources.h | 34 ++ Development/nmos/json_fields.h | 21 + Development/nmos/model.h | 4 + 8 files changed, 718 insertions(+) create mode 100644 Development/nmos/flowcompatibility_resources.cpp create mode 100644 Development/nmos/flowcompatibility_resources.h diff --git a/Development/cmake/NmosCppLibraries.cmake b/Development/cmake/NmosCppLibraries.cmake index 03c2859fc..bcda0a5af 100644 --- a/Development/cmake/NmosCppLibraries.cmake +++ b/Development/cmake/NmosCppLibraries.cmake @@ -749,6 +749,7 @@ set(NMOS_CPP_NMOS_SOURCES nmos/certificate_handlers.cpp nmos/channelmapping_activation.cpp nmos/channelmapping_api.cpp + nmos/flowcompatibility_resources.cpp nmos/channelmapping_resources.cpp nmos/channels.cpp nmos/client_utils.cpp @@ -838,6 +839,7 @@ set(NMOS_CPP_NMOS_HEADERS nmos/events_ws_client.h nmos/filesystem_route.h nmos/flowcompatibility_api.h + nmos/flowcompatibility_resources.h nmos/format.h nmos/group_hint.h nmos/health.h diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 449538116..0dcb88a75 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -22,6 +22,7 @@ #include "nmos/connection_events_activation.h" #include "nmos/events_resources.h" #include "nmos/format.h" +#include "nmos/flowcompatibility_resources.h" #include "nmos/group_hint.h" #include "nmos/interlace_mode.h" #ifdef HAVE_LLDP @@ -854,6 +855,79 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) auto channelmapping_output = nmos::make_channelmapping_output(id, name, description, source_id, channel_labels, routable_inputs); if (!insert_resource_after(delay_millis, model.channelmapping_resources, std::move(channelmapping_output), gate)) throw node_implementation_init_exception(); } + + // example IS-11 input and senders + { + unsigned char edid_bytes[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x10, 0xac, 0x16, 0xd0, 0x48, 0x4c, 0x46, 0x34, + 0x1a, 0x12, 0x01, 0x04, 0x6a, 0x25, 0x17, 0x78, + 0xef, 0xb6, 0x90, 0xa6, 0x54, 0x51, 0x91, 0x25, + 0x17, 0x50, 0x54, 0xa5, 0x4b, 0x00, 0x81, 0x80, + 0x71, 0x4f, 0x95, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xab, 0x22, + 0xa0, 0xa0, 0x50, 0x84, 0x1a, 0x30, 0x30, 0x20, + 0x36, 0x00, 0x72, 0xe6, 0x10, 0x00, 0x00, 0x1a, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x47, 0x33, 0x34, + 0x30, 0x48, 0x38, 0x36, 0x50, 0x34, 0x46, 0x4c, + 0x48, 0x0a, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x32, + 0x4d, 0x1e, 0x53, 0x0e, 0x04, 0x11, 0xb2, 0x05, + 0xf8, 0x58, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xfc, + 0x00, 0x44, 0x45, 0x4c, 0x4c, 0x20, 0x45, 0x31, + 0x37, 0x38, 0x57, 0x46, 0x50, 0x0a, 0x00, 0x78 + }; + std::string edid(edid_bytes, edid_bytes + sizeof(edid_bytes)); + const auto input_id = impl::make_id(seed_id, nmos::types::input); + auto input = nmos::experimental::make_flowcompatibility_input(input_id, true, web::json::value::object(), edid, model.settings); + impl::set_label_description(input, impl::ports::mux, 0); // The single Input originates both video and audio signals + if (!insert_resource_after(delay_millis, model.flowcompatibility_resources, std::move(input), gate)) return; + + int index = how_many - 1; // Make the last created Sender IS-11 compatible + for (const auto& port : { impl::ports::video, impl::ports::audio }) + { + const auto sender_id = impl::make_id(seed_id, nmos::types::sender, port, index); + const std::vector supported_param_constraints{ + nmos::caps::transport::packet_time.key + }; + auto flowcompatibility_sender = nmos::experimental::make_flowcompatibility_sender(sender_id, { input_id }, supported_param_constraints); + if (!insert_resource_after(delay_millis, model.flowcompatibility_resources, std::move(flowcompatibility_sender), gate)) return; + } + } + + // example IS-11 output and receivers + { + unsigned char edid_bytes[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x10, 0xac, 0x16, 0xd0, 0x48, 0x4c, 0x46, 0x34, + 0x1a, 0x12, 0x01, 0x04, 0x6a, 0x25, 0x17, 0x78, + 0xef, 0xb6, 0x90, 0xa6, 0x54, 0x51, 0x91, 0x25, + 0x17, 0x50, 0x54, 0xa5, 0x4b, 0x00, 0x81, 0x80, + 0x71, 0x4f, 0x95, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xab, 0x22, + 0xa0, 0xa0, 0x50, 0x84, 0x1a, 0x30, 0x30, 0x20, + 0x36, 0x00, 0x72, 0xe6, 0x10, 0x00, 0x00, 0x1a, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x47, 0x33, 0x34, + 0x30, 0x48, 0x38, 0x36, 0x50, 0x34, 0x46, 0x4c, + 0x48, 0x0a, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x32, + 0x4d, 0x1e, 0x53, 0x0e, 0x04, 0x11, 0xb2, 0x05, + 0xf8, 0x58, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xfc, + 0x00, 0x44, 0x45, 0x4c, 0x4c, 0x20, 0x45, 0x31, + 0x37, 0x38, 0x57, 0x46, 0x50, 0x0a, 0x00, 0x78 + }; + std::string edid(edid_bytes, edid_bytes + sizeof(edid_bytes)); + const auto output_id = impl::make_id(seed_id, nmos::types::output); + auto output = nmos::experimental::make_flowcompatibility_output(output_id, false, boost::none, edid, model.settings); + impl::set_label_description(output, impl::ports::mux, 0); // The single Output consumes both video and audio signals + if (!insert_resource_after(delay_millis, model.flowcompatibility_resources, std::move(output), gate)) return; + + int index = how_many - 1; // Make the last created Receiver IS-11 compatible + for (const auto& port : { impl::ports::video, impl::ports::audio }) + { + const auto receiver_id = impl::make_id(seed_id, nmos::types::receiver, port, index); + auto flowcompatibility_receiver = nmos::experimental::make_flowcompatibility_receiver(receiver_id, { output_id }); + if (!insert_resource_after(delay_millis, model.flowcompatibility_resources, std::move(flowcompatibility_receiver), gate)) return; + } + } } void node_implementation_run(nmos::node_model& model, slog::base_gate& gate) diff --git a/Development/nmos/api_utils.h b/Development/nmos/api_utils.h index 401a9894f..fae50c8fe 100644 --- a/Development/nmos/api_utils.h +++ b/Development/nmos/api_utils.h @@ -84,6 +84,9 @@ namespace nmos // Flow Compatibility Management API const route_pattern flowCompatibilityResourceType = make_route_pattern(U("resourceType"), U("senders|receivers|inputs|outputs")); + const route_pattern senderReceiverSubrouteType = make_route_pattern(U("senderReceiverSubroute"), U("inputs|outputs")); + const route_pattern constraintsType = make_route_pattern(U("constraintsType"), U("active|supported")); + const route_pattern edidType = make_route_pattern(U("edidType"), U("base|effective")); // Common patterns const route_pattern resourceId = make_route_pattern(U("resourceId"), U("[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}")); diff --git a/Development/nmos/flowcompatibility_api.cpp b/Development/nmos/flowcompatibility_api.cpp index 681296ffb..b8b0caa0f 100644 --- a/Development/nmos/flowcompatibility_api.cpp +++ b/Development/nmos/flowcompatibility_api.cpp @@ -1,6 +1,8 @@ #include "nmos/flowcompatibility_api.h" +#include #include +#include "cpprest/containerstream.h" #include "nmos/is11_versions.h" #include "nmos/model.h" @@ -63,6 +65,419 @@ namespace nmos return pplx::task_from_result(true); }); + flowcompatibility_api.support(U("/") + nmos::patterns::flowCompatibilityResourceType.pattern + U("/?"), methods::GET, [&model, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + { + nmos::api_gate gate(gate_, req, parameters); + auto lock = model.read_lock(); + auto& resources = model.flowcompatibility_resources; + + const string_t resourceType = parameters.at(nmos::patterns::flowCompatibilityResourceType.name); + + const auto match = [&resourceType](const nmos::resources::value_type& resource) { return resource.type == nmos::type_from_resourceType(resourceType); }; + + size_t count = 0; + + // experimental extension, to support human-readable HTML rendering of NMOS responses + if (experimental::details::is_html_response_preferred(req, web::http::details::mime_types::application_json)) + { + set_reply(res, status_codes::OK, + web::json::serialize_array(resources + | boost::adaptors::filtered(match) + | boost::adaptors::transformed( + [&count, &req](const nmos::resource& resource) { ++count; return experimental::details::make_html_response_a_tag(resource.id + U("/"), req); } + )), + web::http::details::mime_types::application_json); + } + else + { + set_reply(res, status_codes::OK, + web::json::serialize_array(resources + | boost::adaptors::filtered(match) + | boost::adaptors::transformed( + [&count](const nmos::resource& resource) { ++count; return value(resource.id + U("/")); } + ) + ), + web::http::details::mime_types::application_json); + } + + slog::log(gate, SLOG_FLF) << "Returning " << count << " matching " << resourceType; + + return pplx::task_from_result(true); + }); + + flowcompatibility_api.support(U("/") + nmos::patterns::flowCompatibilityResourceType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) + { + auto lock = model.read_lock(); + auto& resources = model.flowcompatibility_resources; + + const string_t resourceType = parameters.at(nmos::patterns::flowCompatibilityResourceType.name); + const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); + + const std::pair id_type{ resourceId, nmos::type_from_resourceType(resourceType) }; + auto resource = find_resource(resources, id_type); + if (resources.end() != resource) + { + if (nmos::types::sender == resource->type || + nmos::types::receiver == resource->type) + { + auto matching_resource = find_resource(model.node_resources, id_type); + if (model.node_resources.end() == matching_resource) + { + throw std::logic_error("matching IS-04 resource not found"); + } + } + + std::set sub_routes; + if (nmos::types::sender == resource->type) + { + sub_routes = { U("inputs/"), U("status/"), U("constraints/") }; + } + else if (nmos::types::receiver == resource->type) + { + sub_routes = { U("outputs/"), U("status/") }; + } + else + { + sub_routes = { U("edid/"), U("properties/") }; + } + + set_reply(res, status_codes::OK, nmos::make_sub_routes_body(std::move(sub_routes), req, res)); + } + else if (nmos::details::is_erased_resource(resources, id_type)) + { + set_error_reply(res, status_codes::NotFound, U("Not Found; ") + nmos::details::make_erased_resource_error()); + } + else + { + set_reply(res, status_codes::NotFound); + } + + return pplx::task_from_result(true); + }); + + flowcompatibility_api.support(U("/") + nmos::patterns::connectorType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/") + nmos::patterns::senderReceiverSubrouteType.pattern + U("/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) + { + auto lock = model.read_lock(); + auto& resources = model.flowcompatibility_resources; + + const string_t resourceType = parameters.at(nmos::patterns::connectorType.name); + const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); + const string_t associatedResourceType = parameters.at(nmos::patterns::senderReceiverSubrouteType.name); + + const std::pair id_type{ resourceId, nmos::type_from_resourceType(resourceType) }; + auto resource = find_resource(resources, id_type); + if (resources.end() != resource) + { + auto matching_resource = find_resource(model.node_resources, id_type); + if (model.node_resources.end() == matching_resource) + { + throw std::logic_error("matching IS-04 resource not found"); + } + + const auto match = [&](const nmos::resources::value_type& resource) + { + return resource.type == nmos::type_from_resourceType(resourceType) && nmos::fields::id(resource.data) == resourceId; + }; + + const auto filter = (nmos::types::input == nmos::type_from_resourceType(associatedResourceType)) ? + nmos::fields::inputs : + nmos::fields::outputs; + + set_reply(res, status_codes::OK, + web::json::serialize_array(resources + | boost::adaptors::filtered(match) + | boost::adaptors::transformed( + [&filter](const nmos::resource& resource) { return value_from_elements(filter(resource.data)); } + ) + ), + web::http::details::mime_types::application_json); + } + else if (nmos::details::is_erased_resource(resources, id_type)) + { + set_error_reply(res, status_codes::NotFound, U("Not Found; ") + nmos::details::make_erased_resource_error()); + } + else + { + set_reply(res, status_codes::NotFound); + } + + return pplx::task_from_result(true); + }); + + flowcompatibility_api.support(U("/") + nmos::patterns::connectorType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/status/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) + { + auto lock = model.read_lock(); + auto& resources = model.flowcompatibility_resources; + + const string_t resourceType = parameters.at(nmos::patterns::connectorType.name); + const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); + + const std::pair id_type{ resourceId, nmos::type_from_resourceType(resourceType) }; + auto resource = find_resource(resources, id_type); + if (resources.end() != resource) + { + auto matching_resource = find_resource(model.node_resources, id_type); + if (model.node_resources.end() == matching_resource) + { + throw std::logic_error("matching IS-04 resource not found"); + } + + set_reply(res, status_codes::OK, nmos::fields::status(resource->data)); + } + else if (nmos::details::is_erased_resource(resources, id_type)) + { + set_error_reply(res, status_codes::NotFound, U("Not Found; ") + nmos::details::make_erased_resource_error()); + } + else + { + set_reply(res, status_codes::NotFound); + } + + return pplx::task_from_result(true); + }); + + flowcompatibility_api.support(U("/") + nmos::patterns::senderType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/constraints/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) + { + auto lock = model.read_lock(); + auto& resources = model.flowcompatibility_resources; + + const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); + + const std::pair id_type{ resourceId, nmos::types::sender }; + auto resource = find_resource(resources, id_type); + if (resources.end() != resource) + { + auto matching_resource = find_resource(model.node_resources, id_type); + if (model.node_resources.end() == matching_resource) + { + throw std::logic_error("matching IS-04 resource not found"); + } + + std::set sub_routes{ U("active/"), U("supported/") }; + + set_reply(res, status_codes::OK, nmos::make_sub_routes_body(std::move(sub_routes), req, res)); + } + else if (nmos::details::is_erased_resource(resources, id_type)) + { + set_error_reply(res, status_codes::NotFound, U("Not Found; ") + nmos::details::make_erased_resource_error()); + } + else + { + set_reply(res, status_codes::NotFound); + } + + return pplx::task_from_result(true); + }); + + flowcompatibility_api.support(U("/") + nmos::patterns::senderType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/constraints/") + nmos::patterns::constraintsType.pattern + U("/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) + { + auto lock = model.read_lock(); + auto& resources = model.flowcompatibility_resources; + + const string_t constraintsType = parameters.at(nmos::patterns::constraintsType.name); + const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); + + const std::pair id_type{ resourceId, nmos::types::sender }; + auto resource = find_resource(resources, id_type); + if (resources.end() != resource) + { + auto matching_resource = find_resource(model.node_resources, id_type); + if (model.node_resources.end() == matching_resource) + { + throw std::logic_error("matching IS-04 resource not found"); + } + + if ("active" == constraintsType) { + set_reply(res, status_codes::OK, nmos::fields::active_constraint_sets(resource->data)); + } + else if ("supported" == constraintsType) { + set_reply(res, status_codes::OK, nmos::fields::supported_param_constraints(resource->data)); + } + else { + set_reply(res, status_codes::NotFound); + } + } + else if (nmos::details::is_erased_resource(resources, id_type)) + { + set_error_reply(res, status_codes::NotFound, U("Not Found; ") + nmos::details::make_erased_resource_error()); + } + else + { + set_reply(res, status_codes::NotFound); + } + + return pplx::task_from_result(true); + }); + + flowcompatibility_api.support(U("/") + nmos::patterns::inputOutputType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/properties/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) + { + auto lock = model.read_lock(); + auto& resources = model.flowcompatibility_resources; + + const string_t resourceType = parameters.at(nmos::patterns::inputOutputType.name); + const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); + + const std::pair id_type{ resourceId, nmos::type_from_resourceType(resourceType) }; + auto resource = find_resource(resources, id_type); + if (resources.end() != resource) + { + auto data = resource->data; + + if (nmos::types::input == nmos::type_from_resourceType(resourceType)) + { + if (!nmos::fields::endpoint_base_edid(resource->data).is_null()) + { + data.erase(nmos::fields::endpoint_base_edid); + } + if (!nmos::fields::endpoint_effective_edid(resource->data).is_null()) + { + data.erase(nmos::fields::endpoint_effective_edid); + } + } + else if (nmos::types::output == nmos::type_from_resourceType(resourceType)) + { + if (!nmos::fields::endpoint_edid(resource->data).is_null()) + { + data.erase(nmos::fields::endpoint_edid); + } + } + + set_reply(res, status_codes::OK, data); + } + else if (nmos::details::is_erased_resource(resources, id_type)) + { + set_error_reply(res, status_codes::NotFound, U("Not Found; ") + nmos::details::make_erased_resource_error()); + } + else + { + set_reply(res, status_codes::NotFound); + } + + return pplx::task_from_result(true); + }); + + flowcompatibility_api.support(U("/") + nmos::patterns::inputOutputType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/edid/?"), methods::GET, [&model, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + { + nmos::api_gate gate(gate_, req, parameters); + auto lock = model.read_lock(); + auto& resources = model.flowcompatibility_resources; + + const string_t resourceType = parameters.at(nmos::patterns::inputOutputType.name); + const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); + + const std::pair id_type{ resourceId, nmos::type_from_resourceType(resourceType) }; + auto resource = find_resource(resources, id_type); + if (resources.end() != resource) + { + if (nmos::types::input == nmos::type_from_resourceType(resourceType)) { + std::set sub_routes{ U("base/"), U("effective/") }; + + set_reply(res, status_codes::OK, nmos::make_sub_routes_body(std::move(sub_routes), req, res)); + } + else if (nmos::types::output == nmos::type_from_resourceType(resourceType)) { + auto& edid_endpoint = nmos::fields::endpoint_edid(resource->data); + + if (!edid_endpoint.is_null()) + { + // The edid endpoint data in the resource must have either "edid_binary" or "edid_href" for the redirect + auto& edid_binary = nmos::fields::edid_binary(edid_endpoint); + + if (!edid_binary.is_null()) + { + slog::log(gate, SLOG_FLF) << "Returning EDID binary for " << id_type; + + auto i_stream = concurrency::streams::bytestream::open_istream(edid_binary.as_string()); + set_reply(res, status_codes::OK, i_stream); + } + else + { + slog::log(gate, SLOG_FLF) << "Redirecting to EDID file for " << id_type; + + set_reply(res, status_codes::TemporaryRedirect); + res.headers().add(web::http::header_names::location, nmos::fields::edid_href(edid_endpoint)); + } + } + else + { + slog::log(gate, SLOG_FLF) << "EDID requested for " << id_type << " which does not have one"; + + set_error_reply(res, status_codes::NoContent); + } + } + else { + set_reply(res, status_codes::NotImplemented); + } + } + else if (nmos::details::is_erased_resource(resources, id_type)) + { + set_error_reply(res, status_codes::NotFound, U("Not Found; ") + nmos::details::make_erased_resource_error()); + } + else + { + set_reply(res, status_codes::NotFound); + } + + return pplx::task_from_result(true); + }); + + flowcompatibility_api.support(U("/") + nmos::patterns::inputType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/edid/") + nmos::patterns::edidType.pattern + U("/?"), methods::GET, [&model, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + { + nmos::api_gate gate(gate_, req, parameters); + auto lock = model.read_lock(); + auto& resources = model.flowcompatibility_resources; + + const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); + const string_t edidType = parameters.at(nmos::patterns::edidType.name); + + const std::pair id_type{ resourceId, nmos::types::input }; + auto resource = find_resource(resources, id_type); + if (resources.end() != resource) + { + const auto filter = ("base" == edidType) ? + nmos::fields::endpoint_base_edid : + nmos::fields::endpoint_effective_edid; + + auto& edid_endpoint = filter(resource->data); + + if (!edid_endpoint.is_null()) + { + // The edid endpoint data in the resource must have either "edid_binary" or "edid_href" for the redirect + auto& edid_binary = nmos::fields::edid_binary(edid_endpoint); + + if (!edid_binary.is_null()) + { + slog::log(gate, SLOG_FLF) << "Returning EDID binary for " << id_type; + + auto i_stream = concurrency::streams::bytestream::open_istream(edid_binary.as_string()); + set_reply(res, status_codes::OK, i_stream); + } + else + { + slog::log(gate, SLOG_FLF) << "Redirecting to EDID file for " << id_type; + + set_reply(res, status_codes::TemporaryRedirect); + res.headers().add(web::http::header_names::location, nmos::fields::edid_href(edid_endpoint)); + } + } + else + { + slog::log(gate, SLOG_FLF) << edidType << " EDID requested for " << id_type << " which does not have one"; + + set_error_reply(res, status_codes::NoContent); + } + } + else if (nmos::details::is_erased_resource(resources, id_type)) + { + set_error_reply(res, status_codes::NotFound, U("Not Found; ") + nmos::details::make_erased_resource_error()); + } + else + { + set_reply(res, status_codes::NotFound); + } + + return pplx::task_from_result(true); + }); + return flowcompatibility_api; } } diff --git a/Development/nmos/flowcompatibility_resources.cpp b/Development/nmos/flowcompatibility_resources.cpp new file mode 100644 index 000000000..38fac6f34 --- /dev/null +++ b/Development/nmos/flowcompatibility_resources.cpp @@ -0,0 +1,165 @@ +#include "nmos/flowcompatibility_resources.h" + +#include +#include "nmos/capabilities.h" +#include "nmos/is11_versions.h" +#include "nmos/resource.h" + +namespace nmos +{ + namespace experimental + { + nmos::resource make_flowcompatibility_sender(const nmos::id& id, const std::vector& inputs, const std::vector& param_constraints) + { + using web::json::value; + using web::json::value_of; + using web::json::value_from_elements; + + std::set parameter_constraints{ + nmos::caps::meta::label.key, + nmos::caps::meta::preference.key, + nmos::caps::meta::enabled.key, + nmos::caps::format::media_type.key, + nmos::caps::format::grain_rate.key, + nmos::caps::format::frame_width.key, + nmos::caps::format::frame_height.key, + nmos::caps::format::interlace_mode.key, + nmos::caps::format::colorspace.key, + nmos::caps::format::color_sampling.key, + nmos::caps::format::component_depth.key, + nmos::caps::format::channel_count.key, + nmos::caps::format::sample_rate.key, + nmos::caps::format::sample_depth.key + }; + + parameter_constraints.insert(param_constraints.begin(), param_constraints.end()); + + auto status = value_of({ + { nmos::fields::state, U("Unconstrained") }, + }); + + auto active_constraint_sets = value_of({ + { nmos::fields::constraint_sets, value::array() }, + }); + + auto supported_param_constraints = value_of({ + { nmos::fields::parameter_constraints, value_from_elements(parameter_constraints) }, + }); + + auto data = value_of({ + { nmos::fields::id, id }, + { nmos::fields::device_id, U("these are not the droids you are looking for") }, + { nmos::fields::active_constraint_sets, active_constraint_sets }, + { nmos::fields::inputs, value_from_elements(inputs) }, + { nmos::fields::supported_param_constraints, supported_param_constraints }, + { nmos::fields::status, status }, + }); + + return{ is11_versions::v1_0, types::sender, std::move(data), id, false }; + } + + nmos::resource make_flowcompatibility_receiver(const nmos::id& id, const std::vector& outputs) + { + using web::json::value_of; + using web::json::value_from_elements; + + auto status = value_of({ + { nmos::fields::state, U("OK") }, + }); + + auto data = value_of({ + { nmos::fields::id, id }, + { nmos::fields::device_id, U("these are not the droids you are looking for") }, + { nmos::fields::outputs, value_from_elements(outputs) }, + { nmos::fields::status, status }, + }); + + return{ is11_versions::v1_0, types::receiver, std::move(data), id, false }; + } + + web::json::value make_flowcompatibility_edid_endpoint(const web::uri& edid_file) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::edid_href, edid_file.to_string() } + }); + } + + web::json::value make_flowcompatibility_edid_endpoint(const utility::string_t& edid_file) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::edid_binary, edid_file } + }); + } + + web::json::value make_flowcompatibility_input_output_base(const nmos::id& id, bool connected, bool edid_support, const nmos::settings& settings) + { + using web::json::value; + + auto data = details::make_resource_core(id, settings); + + data[nmos::fields::connected] = value::boolean(connected); + data[nmos::fields::edid_support] = value::boolean(edid_support); + + return data; + } + + struct edid_file_visitor : public boost::static_visitor + { + web::json::value operator()(utility::string_t edid_file) const + { + return make_flowcompatibility_edid_endpoint(edid_file); + } + + web::json::value operator()(web::uri edid_file) const + { + return make_flowcompatibility_edid_endpoint(edid_file); + } + }; + + nmos::resource make_flowcompatibility_input(const nmos::id& id, bool connected, const nmos::settings& settings) + { + auto data = make_flowcompatibility_input_output_base(id, connected, false, settings); + + return{ is11_versions::v1_0, types::input, std::move(data), id, false }; + } + + nmos::resource make_flowcompatibility_input(const nmos::id& id, bool connected, const bst::optional& effective_edid_properties, const boost::variant& effective_edid, const nmos::settings& settings) + { + auto data = make_flowcompatibility_input_output_base(id, connected, true, settings); + + if (effective_edid_properties.has_value()) + { + data[nmos::fields::effective_edid_properties] = effective_edid_properties.value(); + } + + data[nmos::fields::endpoint_effective_edid] = boost::apply_visitor(edid_file_visitor(), effective_edid); + + return{ is11_versions::v1_0, types::input, std::move(data), id, false }; + } + + nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const nmos::settings& settings) + { + auto data = make_flowcompatibility_input_output_base(id, connected, false, settings); + + return{ is11_versions::v1_0, types::output, std::move(data), id, false }; + } + + nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const bst::optional& edid_properties, const boost::variant& edid, const nmos::settings& settings) + { + auto data = make_flowcompatibility_input_output_base(id, connected, true, settings); + + if (edid_properties.has_value()) + { + data[nmos::fields::edid_properties] = edid_properties.value(); + } + + data[nmos::fields::endpoint_edid] = boost::apply_visitor(edid_file_visitor(), edid); + + return{ is11_versions::v1_0, types::output, std::move(data), id, false }; + } + } +} diff --git a/Development/nmos/flowcompatibility_resources.h b/Development/nmos/flowcompatibility_resources.h new file mode 100644 index 000000000..53c4ce7e6 --- /dev/null +++ b/Development/nmos/flowcompatibility_resources.h @@ -0,0 +1,34 @@ +#ifndef NMOS_FLOWCOMPATIBILITY_RESOURCES_H +#define NMOS_FLOWCOMPATIBILITY_RESOURCES_H + +#include +#include +#include "bst/optional.h" +#include "cpprest/base_uri.h" +#include "nmos/id.h" +#include "nmos/settings.h" + +namespace nmos +{ + struct resource; + + namespace experimental + { + nmos::resource make_flowcompatibility_sender(const nmos::id& id, const std::vector& inputs, const std::vector& param_constraints); + nmos::resource make_flowcompatibility_receiver(const nmos::id& id, const std::vector& outputs); + + // See https://github.com/AMWA-TV/is-11/blob/v1.0-dev/APIs/schemas/input.json + // Makes an input without EDID support + nmos::resource make_flowcompatibility_input(const nmos::id& id, bool connected, const nmos::settings& settings); + // Makes an input with EDID support + nmos::resource make_flowcompatibility_input(const nmos::id& id, bool connected, const bst::optional& effective_edid_properties, const boost::variant& effective_edid, const nmos::settings& settings); + + // See https://github.com/AMWA-TV/is-11/blob/v1.0-dev/APIs/schemas/output.json + // Makes an output without EDID support + nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const nmos::settings& settings); + // Makes an output with EDID support + nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const bst::optional& edid_properties, const boost::variant& edid, const nmos::settings& settings); + } +} + +#endif diff --git a/Development/nmos/json_fields.h b/Development/nmos/json_fields.h index 6d7584b94..9e15f0ad7 100644 --- a/Development/nmos/json_fields.h +++ b/Development/nmos/json_fields.h @@ -230,6 +230,27 @@ namespace nmos const web::json::field_as_string hostname{ U("hostname") }; // hostname, ipv4 or ipv6 const web::json::field_as_integer port{ U("port") }; // 1..65535 + // IS-11 Flow Compatibility Management + + // for flowcompatibility_api + const web::json::field_as_array inputs{ U("inputs") }; + const web::json::field_as_array outputs{ U("outputs") }; + const web::json::field_as_bool connected{ U("connected") }; + const web::json::field_as_bool edid_support{ U("edid_support") }; + const web::json::field_as_value active_constraint_sets{ U("active_constraint_sets") }; // object + const web::json::field_as_value supported_param_constraints{ U("supported_param_constraints") }; // object + const web::json::field_as_array parameter_constraints{ U("parameter_constraints") }; + const web::json::field_as_value status{ U("status") }; // object + const web::json::field_as_string state{ U("state") }; + const web::json::field_as_object base_edid_properties{ U("base_edid") }; + const web::json::field_as_object effective_edid_properties{ U("effective_edid") }; + const web::json::field_as_object edid_properties{ U("edid") }; + const web::json::field_as_value_or endpoint_base_edid{ U("endpoint_base_edid"), {} }; // object + const web::json::field_as_value_or endpoint_effective_edid{ U("endpoint_effective_edid"), {} }; // object + const web::json::field_as_value_or endpoint_edid{ U("endpoint_edid"), {} }; // object + const web::json::field_as_value_or edid_binary{ U("edid_binary"), {} }; // string + const web::json::field_as_string edid_href{ U("edid_href") }; + // NMOS Parameter Registers // Sender Attributes Register diff --git a/Development/nmos/model.h b/Development/nmos/model.h index d5c6b9f99..835db90a8 100644 --- a/Development/nmos/model.h +++ b/Development/nmos/model.h @@ -101,6 +101,10 @@ namespace nmos // IS-08 inputs and outputs for this node // see nmos/channelmapping_resources.h nmos::resources channelmapping_resources; + + // IS-11 senders, receivers, inputs and outputs for this node + // see nmos/flowcompatibility_resources.h + nmos::resources flowcompatibility_resources; }; struct registry_model : model From 79d2024b9f229cb3dadb988ecc8908d8b6670805 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 14 Jan 2022 18:32:00 +0300 Subject: [PATCH 003/109] Implement PUT and DELETE for /inputs/{inputId}/edid/base --- .../nmos-cpp-node/node_implementation.cpp | 118 +++++++- Development/nmos/flowcompatibility_api.cpp | 251 +++++++++++++++++- Development/nmos/flowcompatibility_api.h | 24 +- .../nmos/flowcompatibility_resources.cpp | 62 +++-- .../nmos/flowcompatibility_resources.h | 26 +- Development/nmos/json_fields.h | 16 +- Development/nmos/node_server.cpp | 2 +- Development/nmos/node_server.h | 8 + 8 files changed, 457 insertions(+), 50 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 0dcb88a75..3a65b5cd9 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -7,6 +7,7 @@ #include #include #include +#include "bst/optional.h" #include "pplx/pplx_utils.h" // for pplx::complete_after, etc. #include "cpprest/host_utils.h" #ifdef HAVE_LLDP @@ -876,16 +877,23 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) 0x00, 0x44, 0x45, 0x4c, 0x4c, 0x20, 0x45, 0x31, 0x37, 0x38, 0x57, 0x46, 0x50, 0x0a, 0x00, 0x78 }; - std::string edid(edid_bytes, edid_bytes + sizeof(edid_bytes)); + utility::string_t edid(edid_bytes, edid_bytes + sizeof(edid_bytes)); + const auto input_id = impl::make_id(seed_id, nmos::types::input); - auto input = nmos::experimental::make_flowcompatibility_input(input_id, true, web::json::value::object(), edid, model.settings); - impl::set_label_description(input, impl::ports::mux, 0); // The single Input originates both video and audio signals - if (!insert_resource_after(delay_millis, model.flowcompatibility_resources, std::move(input), gate)) return; + std::vector sender_ids; int index = how_many - 1; // Make the last created Sender IS-11 compatible for (const auto& port : { impl::ports::video, impl::ports::audio }) { - const auto sender_id = impl::make_id(seed_id, nmos::types::sender, port, index); + sender_ids.push_back(impl::make_id(seed_id, nmos::types::sender, port, index)); + } + + auto input = nmos::experimental::make_flowcompatibility_input(input_id, true, true, edid, web::json::value::object(), sender_ids, model.settings); + impl::set_label_description(input, impl::ports::mux, 0); // The single Input originates both video and audio signals + if (!insert_resource_after(delay_millis, model.flowcompatibility_resources, std::move(input), gate)) return; + + for (const auto& sender_id : sender_ids) + { const std::vector supported_param_constraints{ nmos::caps::transport::packet_time.key }; @@ -914,16 +922,23 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) 0x00, 0x44, 0x45, 0x4c, 0x4c, 0x20, 0x45, 0x31, 0x37, 0x38, 0x57, 0x46, 0x50, 0x0a, 0x00, 0x78 }; - std::string edid(edid_bytes, edid_bytes + sizeof(edid_bytes)); + utility::string_t edid(edid_bytes, edid_bytes + sizeof(edid_bytes)); + const auto output_id = impl::make_id(seed_id, nmos::types::output); - auto output = nmos::experimental::make_flowcompatibility_output(output_id, false, boost::none, edid, model.settings); - impl::set_label_description(output, impl::ports::mux, 0); // The single Output consumes both video and audio signals - if (!insert_resource_after(delay_millis, model.flowcompatibility_resources, std::move(output), gate)) return; + std::vector receiver_ids; int index = how_many - 1; // Make the last created Receiver IS-11 compatible for (const auto& port : { impl::ports::video, impl::ports::audio }) { - const auto receiver_id = impl::make_id(seed_id, nmos::types::receiver, port, index); + receiver_ids.push_back(impl::make_id(seed_id, nmos::types::receiver, port, index)); + } + + auto output = nmos::experimental::make_flowcompatibility_output(output_id, false, edid, boost::none, receiver_ids, model.settings); + impl::set_label_description(output, impl::ports::mux, 0); // The single Output consumes both video and audio signals + if (!insert_resource_after(delay_millis, model.flowcompatibility_resources, std::move(output), gate)) return; + + for (const auto& receiver_id : receiver_ids) + { auto flowcompatibility_receiver = nmos::experimental::make_flowcompatibility_receiver(receiver_id, { output_id }); if (!insert_resource_after(delay_millis, model.flowcompatibility_resources, std::move(flowcompatibility_receiver), gate)) return; } @@ -1293,6 +1308,84 @@ nmos::channelmapping_activation_handler make_node_implementation_channelmapping_ }; } +// Example Flow Compatibility Management API base EDID update callback to perform application-specific operations to apply updated Base EDID +nmos::experimental::details::flowcompatibility_base_edid_put_handler make_node_implementation_flowcompatibility_base_edid_put_handler(slog::base_gate& gate) +{ + return [&gate](const nmos::id& input_id, const utility::string_t& base_edid, bst::optional& base_edid_properties) + { + base_edid_properties = boost::none; + + slog::log(gate, SLOG_FLF) << "Base EDID updated for input " << input_id; + }; +} + +// Example Flow Compatibility Management API base EDID delete callback to perform application-specific operations in the case Base EDID is deleted +nmos::experimental::details::flowcompatibility_base_edid_delete_handler make_node_implementation_flowcompatibility_base_edid_delete_handler(slog::base_gate& gate) +{ + return [&gate](const nmos::id& input_id) + { + slog::log(gate, SLOG_FLF) << "Base EDID deleted for input " << input_id; + }; +} + +// Example Flow Compatibility Management API callback to update effective EDID - captures flowcompatibility_resources by reference! +nmos::experimental::details::flowcompatibility_effective_edid_setter make_node_implementation_effective_edid_setter(const nmos::resources& flowcompatibility_resources, slog::base_gate& gate) +{ + return [&flowcompatibility_resources, &gate](const nmos::id& input_id, boost::variant& effective_edid, bst::optional& effective_edid_properties) + { + unsigned char edid_bytes[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x10, 0xac, 0x16, 0xd0, 0x48, 0x4c, 0x46, 0x34, + 0x1a, 0x12, 0x01, 0x04, 0x6a, 0x25, 0x17, 0x78, + 0xef, 0xb6, 0x90, 0xa6, 0x54, 0x51, 0x91, 0x25, + 0x17, 0x50, 0x54, 0xa5, 0x4b, 0x00, 0x81, 0x80, + 0x71, 0x4f, 0x95, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xab, 0x22, + 0xa0, 0xa0, 0x50, 0x84, 0x1a, 0x30, 0x30, 0x20, + 0x36, 0x00, 0x72, 0xe6, 0x10, 0x00, 0x00, 0x1a, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x47, 0x33, 0x34, + 0x30, 0x48, 0x38, 0x36, 0x50, 0x34, 0x46, 0x4c, + 0x48, 0x0a, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x32, + 0x4d, 0x1e, 0x53, 0x0e, 0x04, 0x11, 0xb2, 0x05, + 0xf8, 0x58, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xfc, + 0x00, 0x44, 0x45, 0x4c, 0x4c, 0x20, 0x45, 0x31, + 0x37, 0x38, 0x57, 0x46, 0x50, 0x0a, 0x00, 0x78 + }; + + effective_edid_properties = boost::none; + + bst::optional base_edid = boost::none; + + const std::pair id_type{ input_id, nmos::types::input }; + auto resource = find_resource(flowcompatibility_resources, id_type); + if (flowcompatibility_resources.end() != resource) + { + auto& edid_endpoint = nmos::fields::endpoint_base_edid(resource->data); + + if (!edid_endpoint.is_null()) + { + auto& edid_binary = nmos::fields::edid_binary(edid_endpoint); + + if (!edid_binary.is_null()) + { + base_edid = edid_binary.as_string(); + } + } + } + + if (base_edid.has_value()) + { + effective_edid = base_edid.value(); + } + else + { + effective_edid = utility::string_t(edid_bytes, edid_bytes + sizeof(edid_bytes)); + } + + slog::log(gate, SLOG_FLF) << "Effective EDID is set for input " << input_id; + }; +} + namespace impl { nmos::interlace_mode get_interlace_mode(const nmos::settings& settings) @@ -1410,5 +1503,8 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode .on_set_transportfile(make_node_implementation_transportfile_setter(model.node_resources, model.settings)) .on_connection_activated(make_node_implementation_connection_activation_handler(model, gate)) .on_validate_channelmapping_output_map(make_node_implementation_map_validator()) // may be omitted if not required - .on_channelmapping_activated(make_node_implementation_channelmapping_activation_handler(gate)); + .on_channelmapping_activated(make_node_implementation_channelmapping_activation_handler(gate)) + .on_base_edid_changed(make_node_implementation_flowcompatibility_base_edid_put_handler(gate)) + .on_base_edid_deleted(make_node_implementation_flowcompatibility_base_edid_delete_handler(gate)) + .on_set_effective_edid(make_node_implementation_effective_edid_setter(model.flowcompatibility_resources, gate)); } diff --git a/Development/nmos/flowcompatibility_api.cpp b/Development/nmos/flowcompatibility_api.cpp index b8b0caa0f..8f570d433 100644 --- a/Development/nmos/flowcompatibility_api.cpp +++ b/Development/nmos/flowcompatibility_api.cpp @@ -4,15 +4,16 @@ #include #include "cpprest/containerstream.h" #include "nmos/is11_versions.h" +#include "nmos/flowcompatibility_resources.h" #include "nmos/model.h" namespace nmos { namespace experimental { - web::http::experimental::listener::api_router make_unmounted_flowcompatibility_api(nmos::node_model& model, slog::base_gate& gate); + web::http::experimental::listener::api_router make_unmounted_flowcompatibility_api(nmos::node_model& model, details::flowcompatibility_base_edid_put_handler base_edid_put_handler, details::flowcompatibility_base_edid_delete_handler base_edid_delete_handler, details::flowcompatibility_effective_edid_setter effective_edid_setter, slog::base_gate& gate); - web::http::experimental::listener::api_router make_flowcompatibility_api(nmos::node_model& model, slog::base_gate& gate) + web::http::experimental::listener::api_router make_flowcompatibility_api(nmos::node_model& model, details::flowcompatibility_base_edid_put_handler base_edid_put_handler, details::flowcompatibility_base_edid_delete_handler base_edid_delete_handler, details::flowcompatibility_effective_edid_setter effective_edid_setter, slog::base_gate& gate) { using namespace web::http::experimental::listener::api_router_using_declarations; @@ -37,12 +38,12 @@ namespace nmos return pplx::task_from_result(true); }); - flowcompatibility_api.mount(U("/x-nmos/") + nmos::patterns::flowcompatibility_api.pattern + U("/") + nmos::patterns::version.pattern, make_unmounted_flowcompatibility_api(model, gate)); + flowcompatibility_api.mount(U("/x-nmos/") + nmos::patterns::flowcompatibility_api.pattern + U("/") + nmos::patterns::version.pattern, make_unmounted_flowcompatibility_api(model, base_edid_put_handler, base_edid_delete_handler, effective_edid_setter, gate)); return flowcompatibility_api; } - web::http::experimental::listener::api_router make_unmounted_flowcompatibility_api(nmos::node_model& model, slog::base_gate& gate_) + web::http::experimental::listener::api_router make_unmounted_flowcompatibility_api(nmos::node_model& model, details::flowcompatibility_base_edid_put_handler base_edid_put_handler, details::flowcompatibility_base_edid_delete_handler base_edid_delete_handler, details::flowcompatibility_effective_edid_setter effective_edid_setter, slog::base_gate& gate_) { using namespace web::http::experimental::listener::api_router_using_declarations; @@ -441,8 +442,9 @@ namespace nmos if (!edid_endpoint.is_null()) { - // The edid endpoint data in the resource must have either "edid_binary" or "edid_href" for the redirect + // The base edid endpoint may be an empty object which signalizes Base EDID changeability auto& edid_binary = nmos::fields::edid_binary(edid_endpoint); + auto& edid_href = nmos::fields::edid_href(edid_endpoint); if (!edid_binary.is_null()) { @@ -451,13 +453,19 @@ namespace nmos auto i_stream = concurrency::streams::bytestream::open_istream(edid_binary.as_string()); set_reply(res, status_codes::OK, i_stream); } - else + else if (!edid_href.is_null()) { slog::log(gate, SLOG_FLF) << "Redirecting to EDID file for " << id_type; set_reply(res, status_codes::TemporaryRedirect); res.headers().add(web::http::header_names::location, nmos::fields::edid_href(edid_endpoint)); } + else + { + slog::log(gate, SLOG_FLF) << edidType << " EDID requested for " << id_type << " which does not have one"; + + set_error_reply(res, status_codes::NoContent); + } } else { @@ -478,6 +486,237 @@ namespace nmos return pplx::task_from_result(true); }); + flowcompatibility_api.support(U("/") + nmos::patterns::inputType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/edid/base/?"), methods::PUT, [&model, base_edid_put_handler, effective_edid_setter, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + { + nmos::api_gate gate(gate_, req, parameters); + auto lock = model.write_lock(); + auto& resources = model.flowcompatibility_resources; + + const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); + + const std::pair id_type{ resourceId, nmos::types::input }; + auto resource = find_resource(resources, id_type); + if (resources.end() != resource) + { + auto& endpoint_base_edid = nmos::fields::endpoint_base_edid(resource->data); + + if (!endpoint_base_edid.is_null()) + { + if (!nmos::fields::temporarily_locked(endpoint_base_edid)) + { + bst::optional base_edid_properties = boost::none; + + const auto request_body = req.content_ready().get().extract_vector().get(); + const utility::string_t base_edid{ request_body.begin(), request_body.end() }; + + slog::log(gate, SLOG_FLF) << "Base EDID update is requested for " << id_type << " with file size " << base_edid.size(); + + if (base_edid_put_handler) + { + base_edid_put_handler(resourceId, base_edid, base_edid_properties); + } + + // Check senders exist before modifying the input and therefore versions of associated senders + for (const auto& sender_id : nmos::fields::senders(resource->data)) + { + auto associated_sender = find_resource(model.node_resources, std::make_pair(sender_id.as_string(), nmos::types::sender)); + if (model.node_resources.end() == associated_sender) + { + throw std::logic_error("associated IS-04 sender not found"); + } + } + + web::json::value updated_timestamp = web::json::value::string(nmos::make_version()); + + // Update Base EDID in flowcompatibility_resources + modify_resource(resources, resourceId, [&base_edid, &base_edid_properties, &updated_timestamp](nmos::resource& input) + { + if (base_edid_properties.has_value()) + { + input.data[nmos::fields::base_edid_properties] = base_edid_properties.value(); + } + + input.data[nmos::fields::endpoint_base_edid] = make_flowcompatibility_edid_endpoint(base_edid); + + input.data[nmos::fields::version] = updated_timestamp; + }); + + for (const auto& sender_id : nmos::fields::senders(resource->data)) + { + modify_resource(model.node_resources, sender_id.as_string(), [&updated_timestamp](nmos::resource& sender) + { + sender.data[nmos::fields::version] = updated_timestamp; + }); + } + + if (effective_edid_setter) + { + boost::variant effective_edid; + bst::optional effective_edid_properties = boost::none; + + effective_edid_setter(resourceId, effective_edid, effective_edid_properties); + + modify_resource(resources, resourceId, [&effective_edid, &effective_edid_properties, &updated_timestamp](nmos::resource& input) + { + input.data[nmos::fields::endpoint_effective_edid] = boost::apply_visitor(edid_file_visitor(), effective_edid); + + if (effective_edid_properties.has_value()) + { + input.data[nmos::fields::effective_edid_properties] = effective_edid_properties.value(); + } + + input.data[nmos::fields::version] = updated_timestamp = web::json::value::string(nmos::make_version()); + }); + + for (const auto& sender_id : nmos::fields::senders(resource->data)) + { + modify_resource(model.node_resources, sender_id.as_string(), [&updated_timestamp](nmos::resource& sender) + { + sender.data[nmos::fields::version] = updated_timestamp; + }); + } + } + + model.notify(); + + set_reply(res, status_codes::NoContent); + } + else + { + slog::log(gate, SLOG_FLF) << "Base EDID update is requested for " << id_type << " but this input is locked"; + + set_error_reply(res, status_codes::Locked); + } + } + else + { + slog::log(gate, SLOG_FLF) << "Base EDID update is requested for " << id_type << " but this input is not configured to allow it"; + + set_error_reply(res, status_codes::MethodNotAllowed); + } + } + else if (nmos::details::is_erased_resource(resources, id_type)) + { + set_error_reply(res, status_codes::NotFound, U("Not Found; ") + nmos::details::make_erased_resource_error()); + } + else + { + set_reply(res, status_codes::NotFound); + } + + return pplx::task_from_result(true); + }); + + flowcompatibility_api.support(U("/") + nmos::patterns::inputType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/edid/base/?"), methods::DEL, [&model, base_edid_delete_handler, effective_edid_setter, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + { + nmos::api_gate gate(gate_, req, parameters); + auto lock = model.write_lock(); + auto& resources = model.flowcompatibility_resources; + + const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); + + const std::pair id_type{ resourceId, nmos::types::input }; + auto resource = find_resource(resources, id_type); + if (resources.end() != resource) + { + auto& endpoint_base_edid = nmos::fields::endpoint_base_edid(resource->data); + + if (!endpoint_base_edid.is_null()) + { + if (!nmos::fields::temporarily_locked(endpoint_base_edid)) + { + slog::log(gate, SLOG_FLF) << "Base EDID deletion is requested for " << id_type; + + if (base_edid_delete_handler) + { + base_edid_delete_handler(resourceId); + } + + // Check senders exist before modifying the input and therefore versions of associated senders + for (const auto& sender_id : nmos::fields::senders(resource->data)) + { + auto associated_sender = find_resource(model.node_resources, std::make_pair(sender_id.as_string(), nmos::types::sender)); + if (model.node_resources.end() == associated_sender) + { + throw std::logic_error("associated IS-04 sender not found"); + } + } + + web::json::value updated_timestamp = web::json::value::string(""); + + modify_resource(resources, resourceId, [&effective_edid_setter, &updated_timestamp](nmos::resource& input) + { + input.data[nmos::fields::endpoint_base_edid] = make_flowcompatibility_dummy_edid_endpoint(); + + input.data[nmos::fields::version] = updated_timestamp = web::json::value::string(nmos::make_version()); + }); + + for (const auto& sender_id : nmos::fields::senders(resource->data)) + { + modify_resource(model.node_resources, sender_id.as_string(), [&updated_timestamp](nmos::resource& sender) + { + sender.data[nmos::fields::version] = updated_timestamp; + }); + } + + if (effective_edid_setter) + { + boost::variant effective_edid; + bst::optional effective_edid_properties = boost::none; + + effective_edid_setter(resourceId, effective_edid, effective_edid_properties); + + modify_resource(resources, resourceId, [&effective_edid, &effective_edid_properties, &updated_timestamp](nmos::resource& input) + { + input.data[nmos::fields::endpoint_effective_edid] = boost::apply_visitor(edid_file_visitor(), effective_edid); + + if (effective_edid_properties.has_value()) + { + input.data[nmos::fields::effective_edid_properties] = effective_edid_properties.value(); + } + + input.data[nmos::fields::version] = updated_timestamp = web::json::value::string(nmos::make_version()); + }); + + for (const auto& sender_id : nmos::fields::senders(resource->data)) + { + modify_resource(model.node_resources, sender_id.as_string(), [&updated_timestamp](nmos::resource& sender) + { + sender.data[nmos::fields::version] = updated_timestamp; + }); + } + } + + model.notify(); + + set_reply(res, status_codes::NoContent); + } + else + { + slog::log(gate, SLOG_FLF) << "Base EDID deletion is requested for " << id_type << " but this input is locked"; + + set_error_reply(res, status_codes::Locked); + } + } + else + { + slog::log(gate, SLOG_FLF) << "Base EDID deletion is requested for " << id_type << " but this input is not configured to allow it"; + + set_error_reply(res, status_codes::MethodNotAllowed); + } + } + else if (nmos::details::is_erased_resource(resources, id_type)) + { + set_error_reply(res, status_codes::NotFound, U("Not Found; ") + nmos::details::make_erased_resource_error()); + } + else + { + set_reply(res, status_codes::NotFound); + } + + return pplx::task_from_result(true); + }); + return flowcompatibility_api; } } diff --git a/Development/nmos/flowcompatibility_api.h b/Development/nmos/flowcompatibility_api.h index 767194903..f722cadb1 100644 --- a/Development/nmos/flowcompatibility_api.h +++ b/Development/nmos/flowcompatibility_api.h @@ -1,6 +1,8 @@ #ifndef NMOS_FLOWCOMPATIBILITY_API_H #define NMOS_FLOWCOMPATIBILITY_API_H +#include +#include "bst/optional.h" #include "nmos/api_utils.h" #include "nmos/slog.h" @@ -12,7 +14,27 @@ namespace nmos namespace experimental { - web::http::experimental::listener::api_router make_flowcompatibility_api(nmos::node_model& model, slog::base_gate& gate); + namespace details + { + // a flowcompatibility_base_edid_put_handler is a notification that the Base EDID for the specified IS-11 input has changed + // it can be used to perform any final validation of the specified Base EDID and set parsed Base EDID properties + // it may throw web::json::json_exception, which will be mapped to a 400 Bad Request status code with NMOS error "debug" information including the exception message + // or std::runtime_error, which will be mapped to a 500 Internal Error status code with NMOS error "debug" information including the exception message + // if base_edid has no value, the handler should not throw any exceptions + typedef std::function& base_edid_properties)> flowcompatibility_base_edid_put_handler; + + // a flowcompatibility_base_edid_delete_handler is a notification that the Base EDID for the specified IS-11 input has been deleted + // this callback should not throw exceptions, as the Base EDID will already have been changed and those changes will not be rolled back + typedef std::function flowcompatibility_base_edid_delete_handler; + + // a flowcompatibility_effective_edid_setter updates the specified Effective EDID for the specified IS-11 input + // effective EDID of the input is updated when either Active Constraints of a Sender associated with this input are updated + // or base EDID of the input is updated or deleted + // this callback should not throw exceptions, as it's called after the mentioned changes which will not be rolled back + typedef std::function& effective_edid, bst::optional& effective_edid_properties)> flowcompatibility_effective_edid_setter; + } + + web::http::experimental::listener::api_router make_flowcompatibility_api(nmos::node_model& model, details::flowcompatibility_base_edid_put_handler base_edid_put_handler, details::flowcompatibility_base_edid_delete_handler base_edid_delete_handler, details::flowcompatibility_effective_edid_setter effective_edid_setter, slog::base_gate& gate); } } diff --git a/Development/nmos/flowcompatibility_resources.cpp b/Development/nmos/flowcompatibility_resources.cpp index 38fac6f34..76ff2af38 100644 --- a/Development/nmos/flowcompatibility_resources.cpp +++ b/Development/nmos/flowcompatibility_resources.cpp @@ -77,21 +77,32 @@ namespace nmos return{ is11_versions::v1_0, types::receiver, std::move(data), id, false }; } - web::json::value make_flowcompatibility_edid_endpoint(const web::uri& edid_file) + web::json::value make_flowcompatibility_dummy_edid_endpoint(bool locked) { using web::json::value_of; return value_of({ - { nmos::fields::edid_href, edid_file.to_string() } + { nmos::fields::temporarily_locked, locked }, }); } - web::json::value make_flowcompatibility_edid_endpoint(const utility::string_t& edid_file) + web::json::value make_flowcompatibility_edid_endpoint(const web::uri& edid_file, bool locked) { using web::json::value_of; return value_of({ - { nmos::fields::edid_binary, edid_file } + { nmos::fields::edid_href, edid_file.to_string() }, + { nmos::fields::temporarily_locked, locked }, + }); + } + + web::json::value make_flowcompatibility_edid_endpoint(const utility::string_t& edid_file, bool locked) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::edid_binary, edid_file }, + { nmos::fields::temporarily_locked, locked }, }); } @@ -107,58 +118,63 @@ namespace nmos return data; } - struct edid_file_visitor : public boost::static_visitor + nmos::resource make_flowcompatibility_input(const nmos::id& id, bool connected, const std::vector& senders, const nmos::settings& settings) { - web::json::value operator()(utility::string_t edid_file) const - { - return make_flowcompatibility_edid_endpoint(edid_file); - } - - web::json::value operator()(web::uri edid_file) const - { - return make_flowcompatibility_edid_endpoint(edid_file); - } - }; + using web::json::value_from_elements; - nmos::resource make_flowcompatibility_input(const nmos::id& id, bool connected, const nmos::settings& settings) - { auto data = make_flowcompatibility_input_output_base(id, connected, false, settings); + data[nmos::fields::senders] = value_from_elements(senders); return{ is11_versions::v1_0, types::input, std::move(data), id, false }; } - nmos::resource make_flowcompatibility_input(const nmos::id& id, bool connected, const bst::optional& effective_edid_properties, const boost::variant& effective_edid, const nmos::settings& settings) + nmos::resource make_flowcompatibility_input(const nmos::id& id, bool connected, bool base_edid_changeable, const boost::variant& effective_edid, const bst::optional& effective_edid_properties, const std::vector& senders, const nmos::settings& settings) { + using web::json::value_from_elements; + auto data = make_flowcompatibility_input_output_base(id, connected, true, settings); + if (base_edid_changeable) + { + data[nmos::fields::endpoint_base_edid] = make_flowcompatibility_dummy_edid_endpoint(false); + } + + data[nmos::fields::endpoint_effective_edid] = boost::apply_visitor(edid_file_visitor(), effective_edid); + if (effective_edid_properties.has_value()) { data[nmos::fields::effective_edid_properties] = effective_edid_properties.value(); } - data[nmos::fields::endpoint_effective_edid] = boost::apply_visitor(edid_file_visitor(), effective_edid); + data[nmos::fields::senders] = value_from_elements(senders); return{ is11_versions::v1_0, types::input, std::move(data), id, false }; } - nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const nmos::settings& settings) + nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const std::vector& receivers, const nmos::settings& settings) { + using web::json::value_from_elements; + auto data = make_flowcompatibility_input_output_base(id, connected, false, settings); + data[nmos::fields::receivers] = value_from_elements(receivers); return{ is11_versions::v1_0, types::output, std::move(data), id, false }; } - nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const bst::optional& edid_properties, const boost::variant& edid, const nmos::settings& settings) + nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const boost::variant& edid, const bst::optional& edid_properties, const std::vector& receivers, const nmos::settings& settings) { + using web::json::value_from_elements; + auto data = make_flowcompatibility_input_output_base(id, connected, true, settings); + data[nmos::fields::receivers] = value_from_elements(receivers); + + data[nmos::fields::endpoint_edid] = boost::apply_visitor(edid_file_visitor(), edid); if (edid_properties.has_value()) { data[nmos::fields::edid_properties] = edid_properties.value(); } - data[nmos::fields::endpoint_edid] = boost::apply_visitor(edid_file_visitor(), edid); - return{ is11_versions::v1_0, types::output, std::move(data), id, false }; } } diff --git a/Development/nmos/flowcompatibility_resources.h b/Development/nmos/flowcompatibility_resources.h index 53c4ce7e6..ef863de66 100644 --- a/Development/nmos/flowcompatibility_resources.h +++ b/Development/nmos/flowcompatibility_resources.h @@ -17,17 +17,35 @@ namespace nmos nmos::resource make_flowcompatibility_sender(const nmos::id& id, const std::vector& inputs, const std::vector& param_constraints); nmos::resource make_flowcompatibility_receiver(const nmos::id& id, const std::vector& outputs); + // Makes a dummy EDID endpoint to show that an input supports EDID of this type but it currently has no value + web::json::value make_flowcompatibility_dummy_edid_endpoint(bool locked = false); + web::json::value make_flowcompatibility_edid_endpoint(const web::uri& edid_file, bool locked = false); + web::json::value make_flowcompatibility_edid_endpoint(const utility::string_t& edid_file, bool locked = false); + // See https://github.com/AMWA-TV/is-11/blob/v1.0-dev/APIs/schemas/input.json // Makes an input without EDID support - nmos::resource make_flowcompatibility_input(const nmos::id& id, bool connected, const nmos::settings& settings); + nmos::resource make_flowcompatibility_input(const nmos::id& id, bool connected, const std::vector& senders, const nmos::settings& settings); // Makes an input with EDID support - nmos::resource make_flowcompatibility_input(const nmos::id& id, bool connected, const bst::optional& effective_edid_properties, const boost::variant& effective_edid, const nmos::settings& settings); + nmos::resource make_flowcompatibility_input(const nmos::id& id, bool connected, bool base_edid_changeable, const boost::variant& effective_edid, const bst::optional& effective_edid_properties, const std::vector& senders, const nmos::settings& settings); // See https://github.com/AMWA-TV/is-11/blob/v1.0-dev/APIs/schemas/output.json // Makes an output without EDID support - nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const nmos::settings& settings); + nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const std::vector& receivers, const nmos::settings& settings); // Makes an output with EDID support - nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const bst::optional& edid_properties, const boost::variant& edid, const nmos::settings& settings); + nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const boost::variant& edid, const bst::optional& edid_properties, const std::vector& receivers, const nmos::settings& settings); + + struct edid_file_visitor : public boost::static_visitor + { + web::json::value operator()(utility::string_t edid_file) const + { + return make_flowcompatibility_edid_endpoint(edid_file); + } + + web::json::value operator()(web::uri edid_file) const + { + return make_flowcompatibility_edid_endpoint(edid_file); + } + }; } } diff --git a/Development/nmos/json_fields.h b/Development/nmos/json_fields.h index 9e15f0ad7..4cb75f569 100644 --- a/Development/nmos/json_fields.h +++ b/Development/nmos/json_fields.h @@ -235,21 +235,29 @@ namespace nmos // for flowcompatibility_api const web::json::field_as_array inputs{ U("inputs") }; const web::json::field_as_array outputs{ U("outputs") }; + const web::json::field_as_bool temporarily_locked{ U("temporarily_locked") }; + + // for properties const web::json::field_as_bool connected{ U("connected") }; const web::json::field_as_bool edid_support{ U("edid_support") }; + const web::json::field_as_object base_edid_properties{ U("base_edid") }; + const web::json::field_as_object effective_edid_properties{ U("effective_edid") }; + const web::json::field_as_object edid_properties{ U("edid") }; + + // for sender + const web::json::field_as_object active_constraints{ U("active_constraints") }; const web::json::field_as_value active_constraint_sets{ U("active_constraint_sets") }; // object const web::json::field_as_value supported_param_constraints{ U("supported_param_constraints") }; // object const web::json::field_as_array parameter_constraints{ U("parameter_constraints") }; const web::json::field_as_value status{ U("status") }; // object const web::json::field_as_string state{ U("state") }; - const web::json::field_as_object base_edid_properties{ U("base_edid") }; - const web::json::field_as_object effective_edid_properties{ U("effective_edid") }; - const web::json::field_as_object edid_properties{ U("edid") }; + + // for EDID endpoints const web::json::field_as_value_or endpoint_base_edid{ U("endpoint_base_edid"), {} }; // object const web::json::field_as_value_or endpoint_effective_edid{ U("endpoint_effective_edid"), {} }; // object const web::json::field_as_value_or endpoint_edid{ U("endpoint_edid"), {} }; // object const web::json::field_as_value_or edid_binary{ U("edid_binary"), {} }; // string - const web::json::field_as_string edid_href{ U("edid_href") }; + const web::json::field_as_value_or edid_href{ U("edid_href"), {} }; // string // NMOS Parameter Registers diff --git a/Development/nmos/node_server.cpp b/Development/nmos/node_server.cpp index feb66f06d..c675c21d7 100644 --- a/Development/nmos/node_server.cpp +++ b/Development/nmos/node_server.cpp @@ -63,7 +63,7 @@ namespace nmos node_server.api_routers[{ {}, nmos::fields::channelmapping_port(node_model.settings) }].mount({}, nmos::make_channelmapping_api(node_model, node_implementation.validate_map, gate)); // Configure the Flow Compatibility API - node_server.api_routers[{ {}, nmos::fields::flowcompatibility_port(node_model.settings) }].mount({}, nmos::experimental::make_flowcompatibility_api(node_model, gate)); + node_server.api_routers[{ {}, nmos::fields::flowcompatibility_port(node_model.settings) }].mount({}, nmos::experimental::make_flowcompatibility_api(node_model, node_implementation.base_edid_changed, node_implementation.base_edid_deleted, node_implementation.set_effective_edid, gate)); auto& events_ws_api = node_server.ws_handlers[{ {}, nmos::fields::events_ws_port(node_model.settings) }]; events_ws_api.first = nmos::make_events_ws_api(node_model, events_ws_api.second, gate); diff --git a/Development/nmos/node_server.h b/Development/nmos/node_server.h index dc8f4efa8..109f5cfc4 100644 --- a/Development/nmos/node_server.h +++ b/Development/nmos/node_server.h @@ -6,6 +6,7 @@ #include "nmos/channelmapping_activation.h" #include "nmos/connection_api.h" #include "nmos/connection_activation.h" +#include "nmos/flowcompatibility_api.h" #include "nmos/node_behaviour.h" #include "nmos/node_system_behaviour.h" #include "nmos/ocsp_response_handler.h" @@ -57,6 +58,9 @@ namespace nmos node_implementation& on_validate_channelmapping_output_map(nmos::details::channelmapping_output_map_validator validate_map) { this->validate_map = std::move(validate_map); return *this; } node_implementation& on_channelmapping_activated(nmos::channelmapping_activation_handler channelmapping_activated) { this->channelmapping_activated = std::move(channelmapping_activated); return *this; } node_implementation& on_get_ocsp_response(nmos::ocsp_response_handler get_ocsp_response) { this->get_ocsp_response = std::move(get_ocsp_response); return *this; } + node_implementation& on_base_edid_changed(nmos::experimental::details::flowcompatibility_base_edid_put_handler base_edid_changed) { this->base_edid_changed = std::move(base_edid_changed); return *this; } + node_implementation& on_base_edid_deleted(nmos::experimental::details::flowcompatibility_base_edid_delete_handler base_edid_deleted) { this->base_edid_deleted = std::move(base_edid_deleted); return *this; } + node_implementation& on_set_effective_edid(nmos::experimental::details::flowcompatibility_effective_edid_setter set_effective_edid) { this->set_effective_edid = std::move(set_effective_edid); return *this; } // deprecated, use on_validate_connection_resource_patch node_implementation& on_validate_merged(nmos::details::connection_resource_patch_validator validate_merged) { return on_validate_connection_resource_patch(std::move(validate_merged)); } @@ -86,6 +90,10 @@ namespace nmos nmos::channelmapping_activation_handler channelmapping_activated; nmos::ocsp_response_handler get_ocsp_response; + + nmos::experimental::details::flowcompatibility_base_edid_put_handler base_edid_changed; + nmos::experimental::details::flowcompatibility_base_edid_delete_handler base_edid_deleted; + nmos::experimental::details::flowcompatibility_effective_edid_setter set_effective_edid; }; // Construct a server instance for an NMOS Node, implementing the IS-04 Node API, IS-05 Connection API, IS-07 Events API From 634967d4930748f7241326455937f3f86b4f60ba Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sat, 15 Jan 2022 01:06:00 +0300 Subject: [PATCH 004/109] flowcompatibility_api.cpp: refactor repeated code --- Development/nmos/flowcompatibility_api.cpp | 245 +++++++++------------ 1 file changed, 110 insertions(+), 135 deletions(-) diff --git a/Development/nmos/flowcompatibility_api.cpp b/Development/nmos/flowcompatibility_api.cpp index 8f570d433..d3edc64bc 100644 --- a/Development/nmos/flowcompatibility_api.cpp +++ b/Development/nmos/flowcompatibility_api.cpp @@ -43,6 +43,98 @@ namespace nmos return flowcompatibility_api; } + void set_edid_endpoint_as_reply(web::http::http_response& res, const std::pair& id_type, const web::json::value& edid_endpoint, nmos::api_gate& gate) + { + if (!edid_endpoint.is_null()) + { + // The base edid endpoint may be an empty object which signalizes that Base EDID is currently absent but it's allowed to be set + // The effective edid and edid endpoints should contain either edid_binary or edid_href + auto& edid_binary = nmos::fields::edid_binary(edid_endpoint); + auto& edid_href = nmos::fields::edid_href(edid_endpoint); + + if (!edid_binary.is_null()) + { + slog::log(gate, SLOG_FLF) << "Returning EDID binary for " << id_type; + + auto i_stream = concurrency::streams::bytestream::open_istream(edid_binary.as_string()); + set_reply(res, web::http::status_codes::OK, i_stream); + } + else if (!edid_href.is_null()) + { + slog::log(gate, SLOG_FLF) << "Redirecting to EDID file for " << id_type; + + set_reply(res, web::http::status_codes::TemporaryRedirect); + res.headers().add(web::http::header_names::location, nmos::fields::edid_href(edid_endpoint)); + } + else + { + slog::log(gate, SLOG_FLF) << "EDID requested for " << id_type << " does not exist"; + + set_error_reply(res, web::http::status_codes::NoContent); + } + } + else + { + slog::log(gate, SLOG_FLF) << "EDID requested for " << id_type << " does not exist"; + + set_error_reply(res, web::http::status_codes::NoContent); + } + } + + // it's expected that read lock is already catched for the model + bool all_resources_exist(nmos::resources& resources, const web::json::array& resource_ids, const nmos::type& type) + { + for (const auto& resource_id : resource_ids) + { + if (resources.end() == find_resource(resources, std::make_pair(resource_id.as_string(), type))) + { + return false; + } + } + return true; + } + + // it's expected that read lock is already catched for the model + void update_version(nmos::resources& resources, const web::json::array& resource_ids, const utility::string_t& new_version) + { + for (const auto& resource_id : resource_ids) + { + modify_resource(resources, resource_id.as_string(), [&new_version](nmos::resource& resource) + { + resource.data[nmos::fields::version] = web::json::value::string(new_version); + }); + } + } + + // it's expected that write lock is already catched for the model and an input with the resource_id exists + void update_effective_edid(nmos::node_model& model, const details::flowcompatibility_effective_edid_setter& effective_edid_setter, const utility::string_t resource_id) + { + boost::variant effective_edid; + bst::optional effective_edid_properties = boost::none; + + effective_edid_setter(resource_id, effective_edid, effective_edid_properties); + + utility::string_t updated_timestamp; + + modify_resource(model.flowcompatibility_resources, resource_id, [&effective_edid, &effective_edid_properties, &updated_timestamp](nmos::resource& input) + { + input.data[nmos::fields::endpoint_effective_edid] = boost::apply_visitor(edid_file_visitor(), effective_edid); + + if (effective_edid_properties.has_value()) + { + input.data[nmos::fields::effective_edid_properties] = effective_edid_properties.value(); + } + + updated_timestamp = nmos::make_version(); + input.data[nmos::fields::version] = web::json::value::string(updated_timestamp); + }); + + const std::pair id_type{ resource_id, nmos::types::input }; + auto resource = find_resource(model.flowcompatibility_resources, id_type); + + update_version(model.node_resources, nmos::fields::senders(resource->data), updated_timestamp); + } + web::http::experimental::listener::api_router make_unmounted_flowcompatibility_api(nmos::node_model& model, details::flowcompatibility_base_edid_put_handler base_edid_put_handler, details::flowcompatibility_base_edid_delete_handler base_edid_delete_handler, details::flowcompatibility_effective_edid_setter effective_edid_setter, slog::base_gate& gate_) { using namespace web::http::experimental::listener::api_router_using_declarations; @@ -378,32 +470,9 @@ namespace nmos else if (nmos::types::output == nmos::type_from_resourceType(resourceType)) { auto& edid_endpoint = nmos::fields::endpoint_edid(resource->data); - if (!edid_endpoint.is_null()) - { - // The edid endpoint data in the resource must have either "edid_binary" or "edid_href" for the redirect - auto& edid_binary = nmos::fields::edid_binary(edid_endpoint); - - if (!edid_binary.is_null()) - { - slog::log(gate, SLOG_FLF) << "Returning EDID binary for " << id_type; - - auto i_stream = concurrency::streams::bytestream::open_istream(edid_binary.as_string()); - set_reply(res, status_codes::OK, i_stream); - } - else - { - slog::log(gate, SLOG_FLF) << "Redirecting to EDID file for " << id_type; - - set_reply(res, status_codes::TemporaryRedirect); - res.headers().add(web::http::header_names::location, nmos::fields::edid_href(edid_endpoint)); - } - } - else - { - slog::log(gate, SLOG_FLF) << "EDID requested for " << id_type << " which does not have one"; + slog::log(gate, SLOG_FLF) << "EDID requested for " << id_type; - set_error_reply(res, status_codes::NoContent); - } + set_edid_endpoint_as_reply(res, id_type, edid_endpoint, gate); } else { set_reply(res, status_codes::NotImplemented); @@ -440,39 +509,9 @@ namespace nmos auto& edid_endpoint = filter(resource->data); - if (!edid_endpoint.is_null()) - { - // The base edid endpoint may be an empty object which signalizes Base EDID changeability - auto& edid_binary = nmos::fields::edid_binary(edid_endpoint); - auto& edid_href = nmos::fields::edid_href(edid_endpoint); - - if (!edid_binary.is_null()) - { - slog::log(gate, SLOG_FLF) << "Returning EDID binary for " << id_type; - - auto i_stream = concurrency::streams::bytestream::open_istream(edid_binary.as_string()); - set_reply(res, status_codes::OK, i_stream); - } - else if (!edid_href.is_null()) - { - slog::log(gate, SLOG_FLF) << "Redirecting to EDID file for " << id_type; - - set_reply(res, status_codes::TemporaryRedirect); - res.headers().add(web::http::header_names::location, nmos::fields::edid_href(edid_endpoint)); - } - else - { - slog::log(gate, SLOG_FLF) << edidType << " EDID requested for " << id_type << " which does not have one"; - - set_error_reply(res, status_codes::NoContent); - } - } - else - { - slog::log(gate, SLOG_FLF) << edidType << " EDID requested for " << id_type << " which does not have one"; + slog::log(gate, SLOG_FLF) << edidType << " EDID requested for " << id_type; - set_error_reply(res, status_codes::NoContent); - } + set_edid_endpoint_as_reply(res, id_type, edid_endpoint, gate); } else if (nmos::details::is_erased_resource(resources, id_type)) { @@ -517,16 +556,12 @@ namespace nmos } // Check senders exist before modifying the input and therefore versions of associated senders - for (const auto& sender_id : nmos::fields::senders(resource->data)) + if (!all_resources_exist(model.node_resources, nmos::fields::senders(resource->data), nmos::types::sender)) { - auto associated_sender = find_resource(model.node_resources, std::make_pair(sender_id.as_string(), nmos::types::sender)); - if (model.node_resources.end() == associated_sender) - { - throw std::logic_error("associated IS-04 sender not found"); - } + throw std::logic_error("associated IS-04 sender not found"); } - web::json::value updated_timestamp = web::json::value::string(nmos::make_version()); + utility::string_t updated_timestamp; // Update Base EDID in flowcompatibility_resources modify_resource(resources, resourceId, [&base_edid, &base_edid_properties, &updated_timestamp](nmos::resource& input) @@ -538,43 +573,15 @@ namespace nmos input.data[nmos::fields::endpoint_base_edid] = make_flowcompatibility_edid_endpoint(base_edid); - input.data[nmos::fields::version] = updated_timestamp; + updated_timestamp = nmos::make_version(); + input.data[nmos::fields::version] = web::json::value::string(updated_timestamp); }); - for (const auto& sender_id : nmos::fields::senders(resource->data)) - { - modify_resource(model.node_resources, sender_id.as_string(), [&updated_timestamp](nmos::resource& sender) - { - sender.data[nmos::fields::version] = updated_timestamp; - }); - } + update_version(model.node_resources, nmos::fields::senders(resource->data), updated_timestamp); if (effective_edid_setter) { - boost::variant effective_edid; - bst::optional effective_edid_properties = boost::none; - - effective_edid_setter(resourceId, effective_edid, effective_edid_properties); - - modify_resource(resources, resourceId, [&effective_edid, &effective_edid_properties, &updated_timestamp](nmos::resource& input) - { - input.data[nmos::fields::endpoint_effective_edid] = boost::apply_visitor(edid_file_visitor(), effective_edid); - - if (effective_edid_properties.has_value()) - { - input.data[nmos::fields::effective_edid_properties] = effective_edid_properties.value(); - } - - input.data[nmos::fields::version] = updated_timestamp = web::json::value::string(nmos::make_version()); - }); - - for (const auto& sender_id : nmos::fields::senders(resource->data)) - { - modify_resource(model.node_resources, sender_id.as_string(), [&updated_timestamp](nmos::resource& sender) - { - sender.data[nmos::fields::version] = updated_timestamp; - }); - } + update_effective_edid(model, effective_edid_setter, resourceId); } model.notify(); @@ -633,58 +640,26 @@ namespace nmos } // Check senders exist before modifying the input and therefore versions of associated senders - for (const auto& sender_id : nmos::fields::senders(resource->data)) + if (!all_resources_exist(model.node_resources, nmos::fields::senders(resource->data), nmos::types::sender)) { - auto associated_sender = find_resource(model.node_resources, std::make_pair(sender_id.as_string(), nmos::types::sender)); - if (model.node_resources.end() == associated_sender) - { - throw std::logic_error("associated IS-04 sender not found"); - } + throw std::logic_error("associated IS-04 sender not found"); } - web::json::value updated_timestamp = web::json::value::string(""); + utility::string_t updated_timestamp; modify_resource(resources, resourceId, [&effective_edid_setter, &updated_timestamp](nmos::resource& input) { input.data[nmos::fields::endpoint_base_edid] = make_flowcompatibility_dummy_edid_endpoint(); - input.data[nmos::fields::version] = updated_timestamp = web::json::value::string(nmos::make_version()); + updated_timestamp = nmos::make_version(); + input.data[nmos::fields::version] = web::json::value::string(updated_timestamp); }); - for (const auto& sender_id : nmos::fields::senders(resource->data)) - { - modify_resource(model.node_resources, sender_id.as_string(), [&updated_timestamp](nmos::resource& sender) - { - sender.data[nmos::fields::version] = updated_timestamp; - }); - } + update_version(model.node_resources, nmos::fields::senders(resource->data), updated_timestamp); if (effective_edid_setter) { - boost::variant effective_edid; - bst::optional effective_edid_properties = boost::none; - - effective_edid_setter(resourceId, effective_edid, effective_edid_properties); - - modify_resource(resources, resourceId, [&effective_edid, &effective_edid_properties, &updated_timestamp](nmos::resource& input) - { - input.data[nmos::fields::endpoint_effective_edid] = boost::apply_visitor(edid_file_visitor(), effective_edid); - - if (effective_edid_properties.has_value()) - { - input.data[nmos::fields::effective_edid_properties] = effective_edid_properties.value(); - } - - input.data[nmos::fields::version] = updated_timestamp = web::json::value::string(nmos::make_version()); - }); - - for (const auto& sender_id : nmos::fields::senders(resource->data)) - { - modify_resource(model.node_resources, sender_id.as_string(), [&updated_timestamp](nmos::resource& sender) - { - sender.data[nmos::fields::version] = updated_timestamp; - }); - } + update_effective_edid(model, effective_edid_setter, resourceId); } model.notify(); From 6d2a2128e85642d48b24d137dc8156261a7da8bf Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sun, 16 Jan 2022 23:55:24 +0300 Subject: [PATCH 005/109] Add IS-11 and BCP-004-01 JSON Schemas --- Development/cmake/NmosCppLibraries.cmake | 159 ++++++++++++++++++ .../nmos/bcp00401_schemas/bcp00401_schemas.h | 25 +++ Development/nmos/is11_schemas/is11_schemas.h | 34 ++++ Development/nmos/json_schema.cpp | 72 ++++++++ Development/nmos/json_schema.h | 2 + Development/third_party/bcp-004-01/README.md | 8 + .../v1.0.x/APIs/schemas/constraint_set.json | 30 ++++ .../v1.0.x/APIs/schemas/constraint_sets.json | 9 + .../v1.0.x/APIs/schemas/param_constraint.json | 13 ++ .../schemas/param_constraint_boolean.json | 15 ++ .../schemas/param_constraint_integer.json | 21 +++ .../APIs/schemas/param_constraint_number.json | 21 +++ .../schemas/param_constraint_rational.json | 39 +++++ .../APIs/schemas/param_constraint_string.json | 15 ++ .../schemas/receiver_constraint_sets.json | 30 ++++ Development/third_party/is-11/README.md | 8 + .../v1.0.x/APIs/schemas/constraints-base.json | 16 ++ .../APIs/schemas/constraints_active.json | 17 ++ .../APIs/schemas/constraints_supported.json | 36 ++++ .../is-11/v1.0.x/APIs/schemas/edid.json | 86 ++++++++++ .../v1.0.x/APIs/schemas/edid_timing.json | 46 +++++ .../schemas/empty_constraints_active.json | 18 ++ .../is-11/v1.0.x/APIs/schemas/error.json | 27 +++ .../schemas/flowcompatibility-api-base.json | 18 ++ .../v1.0.x/APIs/schemas/input-edid-base.json | 16 ++ .../APIs/schemas/input-output-base.json | 16 ++ .../is-11/v1.0.x/APIs/schemas/input.json | 28 +++ .../is-11/v1.0.x/APIs/schemas/output.json | 27 +++ .../v1.0.x/APIs/schemas/receiver-base.json | 16 ++ .../v1.0.x/APIs/schemas/receiver-status.json | 18 ++ .../v1.0.x/APIs/schemas/resource-list.json | 11 ++ .../v1.0.x/APIs/schemas/sender-base.json | 17 ++ .../v1.0.x/APIs/schemas/sender-status.json | 20 +++ .../is-11/v1.0.x/APIs/schemas/uuid-list.json | 10 ++ 34 files changed, 944 insertions(+) create mode 100644 Development/nmos/bcp00401_schemas/bcp00401_schemas.h create mode 100644 Development/nmos/is11_schemas/is11_schemas.h create mode 100644 Development/third_party/bcp-004-01/README.md create mode 100644 Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/constraint_set.json create mode 100644 Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/constraint_sets.json create mode 100644 Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint.json create mode 100644 Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_boolean.json create mode 100644 Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_integer.json create mode 100644 Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_number.json create mode 100644 Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_rational.json create mode 100644 Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_string.json create mode 100644 Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/receiver_constraint_sets.json create mode 100644 Development/third_party/is-11/README.md create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/constraints-base.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/constraints_active.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/constraints_supported.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/edid.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/edid_timing.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/empty_constraints_active.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/error.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/flowcompatibility-api-base.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/input-edid-base.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/input-output-base.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/input.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/output.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-base.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-status.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/resource-list.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/sender-base.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/sender-status.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/uuid-list.json diff --git a/Development/cmake/NmosCppLibraries.cmake b/Development/cmake/NmosCppLibraries.cmake index bcda0a5af..c852c7d41 100644 --- a/Development/cmake/NmosCppLibraries.cmake +++ b/Development/cmake/NmosCppLibraries.cmake @@ -686,6 +686,163 @@ target_include_directories(nmos_is09_schemas PUBLIC list(APPEND NMOS_CPP_TARGETS nmos_is09_schemas) add_library(nmos-cpp::nmos_is09_schemas ALIAS nmos_is09_schemas) +# nmos_is11_schemas library + +set(NMOS_IS11_SCHEMAS_HEADERS + nmos/is11_schemas/is11_schemas.h + ) + +set(NMOS_IS11_V1_0_TAG v1.0.x) + +set(NMOS_IS11_V1_0_SCHEMAS_JSON + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/constraints_active.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/constraints-base.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/constraints_supported.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/edid.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/edid_timing.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/empty_constraints_active.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/error.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/flowcompatibility-api-base.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/input-edid-base.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/input.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/input-output-base.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/output.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/receiver-base.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/receiver-status.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/resource-list.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/sender-base.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/sender-status.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/uuid-list.json + ) + +set(NMOS_IS11_SCHEMAS_JSON_MATCH "third_party/is-11/([^/]+)/APIs/schemas/([^;]+)\\.json") +set(NMOS_IS11_SCHEMAS_SOURCE_REPLACE "${CMAKE_CURRENT_BINARY_DIR_REPLACE}/nmos/is11_schemas/\\1/\\2.cpp") +string(REGEX REPLACE "${NMOS_IS11_SCHEMAS_JSON_MATCH}(;|$)" "${NMOS_IS11_SCHEMAS_SOURCE_REPLACE}\\3" NMOS_IS11_V1_0_SCHEMAS_SOURCES "${NMOS_IS11_V1_0_SCHEMAS_JSON}") + +foreach(JSON ${NMOS_IS11_V1_0_SCHEMAS_JSON}) + string(REGEX REPLACE "${NMOS_IS11_SCHEMAS_JSON_MATCH}" "${NMOS_IS11_SCHEMAS_SOURCE_REPLACE}" SOURCE "${JSON}") + string(REGEX REPLACE "${NMOS_IS11_SCHEMAS_JSON_MATCH}" "\\1" NS "${JSON}") + string(REGEX REPLACE "${NMOS_IS11_SCHEMAS_JSON_MATCH}" "\\2" VAR "${JSON}") + string(MAKE_C_IDENTIFIER "${NS}" NS) + string(MAKE_C_IDENTIFIER "${VAR}" VAR) + + file(WRITE "${SOURCE}.in" "\ +// Auto-generated from: ${JSON}\n\ +\n\ +namespace nmos\n\ +{\n\ + namespace is11_schemas\n\ + {\n\ + namespace ${NS}\n\ + {\n\ + const char* ${VAR} = R\"-auto-generated-(") + + file(READ "${JSON}" RAW) + file(APPEND "${SOURCE}.in" "${RAW}") + + file(APPEND "${SOURCE}.in" ")-auto-generated-\";\n\ + }\n\ + }\n\ +}\n") + + configure_file("${SOURCE}.in" "${SOURCE}" COPYONLY) +endforeach() + +add_library( + nmos_is11_schemas STATIC + ${NMOS_IS11_SCHEMAS_HEADERS} + ${NMOS_IS11_V1_0_SCHEMAS_SOURCES} + ) + +source_group("nmos\\is11_schemas\\Header Files" FILES ${NMOS_IS11_SCHEMAS_HEADERS}) +source_group("nmos\\is11_schemas\\${NMOS_IS11_V1_0_TAG}\\Source Files" FILES ${NMOS_IS11_V1_0_SCHEMAS_SOURCES}) + +target_link_libraries( + nmos_is11_schemas PRIVATE + nmos-cpp::compile-settings + ) +target_include_directories(nmos_is11_schemas PUBLIC + $ + $ + ) + +list(APPEND NMOS_CPP_TARGETS nmos_is11_schemas) +add_library(nmos-cpp::nmos_is11_schemas ALIAS nmos_is11_schemas) + +# nmos_bcp00401_schemas library + +set(NMOS_BCP00401_SCHEMAS_HEADERS + nmos/bcp00401_schemas/bcp00401_schemas.h + ) + +set(NMOS_BCP00401_V1_0_TAG v1.0.x) + +set(NMOS_BCP00401_V1_0_SCHEMAS_JSON + third_party/bcp-004-01/${NMOS_BCP00401_V1_0_TAG}/APIs/schemas/constraint_set.json + third_party/bcp-004-01/${NMOS_BCP00401_V1_0_TAG}/APIs/schemas/constraint_sets.json + third_party/bcp-004-01/${NMOS_BCP00401_V1_0_TAG}/APIs/schemas/param_constraint_boolean.json + third_party/bcp-004-01/${NMOS_BCP00401_V1_0_TAG}/APIs/schemas/param_constraint_integer.json + third_party/bcp-004-01/${NMOS_BCP00401_V1_0_TAG}/APIs/schemas/param_constraint.json + third_party/bcp-004-01/${NMOS_BCP00401_V1_0_TAG}/APIs/schemas/param_constraint_number.json + third_party/bcp-004-01/${NMOS_BCP00401_V1_0_TAG}/APIs/schemas/param_constraint_rational.json + third_party/bcp-004-01/${NMOS_BCP00401_V1_0_TAG}/APIs/schemas/param_constraint_string.json + third_party/bcp-004-01/${NMOS_BCP00401_V1_0_TAG}/APIs/schemas/receiver_constraint_sets.json + ) + +set(NMOS_BCP00401_SCHEMAS_JSON_MATCH "third_party/bcp-004-01/([^/]+)/APIs/schemas/([^;]+)\\.json") +set(NMOS_BCP00401_SCHEMAS_SOURCE_REPLACE "${CMAKE_CURRENT_BINARY_DIR_REPLACE}/nmos/bcp00401_schemas/\\1/\\2.cpp") +string(REGEX REPLACE "${NMOS_BCP00401_SCHEMAS_JSON_MATCH}(;|$)" "${NMOS_BCP00401_SCHEMAS_SOURCE_REPLACE}\\3" NMOS_BCP00401_V1_0_SCHEMAS_SOURCES "${NMOS_BCP00401_V1_0_SCHEMAS_JSON}") + +foreach(JSON ${NMOS_BCP00401_V1_0_SCHEMAS_JSON}) + string(REGEX REPLACE "${NMOS_BCP00401_SCHEMAS_JSON_MATCH}" "${NMOS_BCP00401_SCHEMAS_SOURCE_REPLACE}" SOURCE "${JSON}") + string(REGEX REPLACE "${NMOS_BCP00401_SCHEMAS_JSON_MATCH}" "\\1" NS "${JSON}") + string(REGEX REPLACE "${NMOS_BCP00401_SCHEMAS_JSON_MATCH}" "\\2" VAR "${JSON}") + string(MAKE_C_IDENTIFIER "${NS}" NS) + string(MAKE_C_IDENTIFIER "${VAR}" VAR) + + file(WRITE "${SOURCE}.in" "\ +// Auto-generated from: ${JSON}\n\ +\n\ +namespace nmos\n\ +{\n\ + namespace bcp00401_schemas\n\ + {\n\ + namespace ${NS}\n\ + {\n\ + const char* ${VAR} = R\"-auto-generated-(") + + file(READ "${JSON}" RAW) + file(APPEND "${SOURCE}.in" "${RAW}") + + file(APPEND "${SOURCE}.in" ")-auto-generated-\";\n\ + }\n\ + }\n\ +}\n") + + configure_file("${SOURCE}.in" "${SOURCE}" COPYONLY) +endforeach() + +add_library( + nmos_bcp00401_schemas STATIC + ${NMOS_BCP00401_SCHEMAS_HEADERS} + ${NMOS_BCP00401_V1_0_SCHEMAS_SOURCES} + ) + +source_group("nmos\\bcp00401_schemas\\Header Files" FILES ${NMOS_BCP00401_SCHEMAS_HEADERS}) +source_group("nmos\\bcp00401_schemas\\${NMOS_BCP00401_V1_0_TAG}\\Source Files" FILES ${NMOS_BCP00401_V1_0_SCHEMAS_SOURCES}) + +target_link_libraries( + nmos_bcp00401_schemas PRIVATE + nmos-cpp::compile-settings + ) +target_include_directories(nmos_bcp00401_schemas PUBLIC + $ + $ + ) + +list(APPEND NMOS_CPP_TARGETS nmos_bcp00401_schemas) +add_library(nmos-cpp::nmos_bcp00401_schemas ALIAS nmos_bcp00401_schemas) + # nmos-cpp library set(NMOS_CPP_BST_SOURCES @@ -988,6 +1145,8 @@ target_link_libraries( nmos-cpp::nmos_is05_schemas nmos-cpp::nmos_is08_schemas nmos-cpp::nmos_is09_schemas + nmos-cpp::nmos_is11_schemas + nmos-cpp::nmos_bcp00401_schemas nmos-cpp::mdns nmos-cpp::slog nmos-cpp::OpenSSL diff --git a/Development/nmos/bcp00401_schemas/bcp00401_schemas.h b/Development/nmos/bcp00401_schemas/bcp00401_schemas.h new file mode 100644 index 000000000..f601034fe --- /dev/null +++ b/Development/nmos/bcp00401_schemas/bcp00401_schemas.h @@ -0,0 +1,25 @@ +#ifndef NMOS_BCP00401_SCHEMAS_H +#define NMOS_BCP00401_SCHEMAS_H + +// Extern declarations for auto-generated constants +// could be auto-generated, but isn't currently! +namespace nmos +{ + namespace bcp00401_schemas + { + namespace v1_0_x + { + extern const char* constraint_set; + extern const char* constraint_sets; + extern const char* param_constraint_boolean; + extern const char* param_constraint_integer; + extern const char* param_constraint; + extern const char* param_constraint_number; + extern const char* param_constraint_rational; + extern const char* param_constraint_string; + extern const char* receiver_constraint_sets; + } + } +} + +#endif diff --git a/Development/nmos/is11_schemas/is11_schemas.h b/Development/nmos/is11_schemas/is11_schemas.h new file mode 100644 index 000000000..ec6b1971b --- /dev/null +++ b/Development/nmos/is11_schemas/is11_schemas.h @@ -0,0 +1,34 @@ +#ifndef NMOS_IS11_SCHEMAS_H +#define NMOS_IS11_SCHEMAS_H + +// Extern declarations for auto-generated constants +// could be auto-generated, but isn't currently! +namespace nmos +{ + namespace is11_schemas + { + namespace v1_0_x + { + extern const char* constraints_active; + extern const char* constraints_base; + extern const char* constraints_supported; + extern const char* edid; + extern const char* edid_timing; + extern const char* empty_constraints_active; + extern const char* error; + extern const char* flowcompatibility_api_base; + extern const char* input_edid_base; + extern const char* input; + extern const char* input_output_base; + extern const char* output; + extern const char* receiver_base; + extern const char* receiver_status; + extern const char* resource_list; + extern const char* sender_base; + extern const char* sender_status; + extern const char* uuid_list; + } + } +} + +#endif diff --git a/Development/nmos/json_schema.cpp b/Development/nmos/json_schema.cpp index fdd70581d..7ca692d38 100644 --- a/Development/nmos/json_schema.cpp +++ b/Development/nmos/json_schema.cpp @@ -1,6 +1,7 @@ #include "nmos/json_schema.h" #include "cpprest/basic_utils.h" +#include "nmos/bcp00401_schemas/bcp00401_schemas.h" #include "nmos/is04_versions.h" #include "nmos/is04_schemas/is04_schemas.h" #include "nmos/is05_versions.h" @@ -9,6 +10,8 @@ #include "nmos/is08_schemas/is08_schemas.h" #include "nmos/is09_versions.h" #include "nmos/is09_schemas/is09_schemas.h" +#include "nmos/is11_versions.h" +#include "nmos/is11_schemas/is11_schemas.h" #include "nmos/type.h" namespace nmos @@ -126,6 +129,38 @@ namespace nmos const web::uri systemapi_global_schema_uri = make_schema_uri(tag, _XPLATSTR("global.json")); } } + + namespace is11_schemas + { + web::uri make_schema_uri(const utility::string_t& tag, const utility::string_t& ref = {}) + { + return{ _XPLATSTR("https://github.com/AMWA-TV/is-11/raw/") + tag + _XPLATSTR("/APIs/schemas/") + ref }; + } + + // See https://github.com/AMWA-TV/is-11/tree/v1.0-dev/APIs/schemas/ + namespace v1_0 + { + using namespace nmos::is11_schemas::v1_0_x; + const utility::string_t tag(_XPLATSTR("v1.0-dev")); + + const web::uri senders_active_constraints_put_request_uri = make_schema_uri(tag, _XPLATSTR("constraints_active.json")); + } + } + + namespace bcp00401_schemas + { + web::uri make_schema_uri(const utility::string_t& tag, const utility::string_t& ref = {}) + { + return{ _XPLATSTR("https://github.com/AMWA-TV/bcp-004-01/raw/") + tag + _XPLATSTR("/APIs/schemas/") + ref }; + } + + // See https://github.com/AMWA-TV/bcp-004-01/tree/v1.0.x/APIs/schemas/ + namespace v1_0 + { + using namespace nmos::bcp00401_schemas::v1_0_x; + const utility::string_t tag(_XPLATSTR("v1.0.x")); + } + } } namespace nmos @@ -310,6 +345,36 @@ namespace nmos }; } + static std::map make_is11_schemas() + { + using namespace nmos::is11_schemas; + + return + { + // v1.0 + { make_schema_uri(v1_0::tag, _XPLATSTR("constraints_active.json")), make_schema(v1_0::constraints_active) } + }; + } + + static std::map make_bcp00401_schemas() + { + using namespace nmos::bcp00401_schemas; + + return + { + // v1.0 + { make_schema_uri(v1_0::tag, _XPLATSTR("constraint_set.json")), make_schema(v1_0::constraint_set) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("constraint_sets.json")), make_schema(v1_0::constraint_sets) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("param_constraint_boolean.json")), make_schema(v1_0::param_constraint_boolean) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("param_constraint_integer.json")), make_schema(v1_0::param_constraint_integer) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("param_constraint.json")), make_schema(v1_0::param_constraint) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("param_constraint_number.json")), make_schema(v1_0::param_constraint_number) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("param_constraint_rational.json")), make_schema(v1_0::param_constraint_rational) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("param_constraint_string.json")), make_schema(v1_0::param_constraint_string) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("receiver_constraint_sets.json")), make_schema(v1_0::receiver_constraint_sets) }, + }; + } + inline void merge(std::map& to, std::map&& from) { to.insert(from.begin(), from.end()); // std::map::merge in C++17 @@ -321,6 +386,8 @@ namespace nmos merge(result, make_is05_schemas()); merge(result, make_is08_schemas()); merge(result, make_is09_schemas()); + merge(result, make_is11_schemas()); + merge(result, make_bcp00401_schemas()); return result; } @@ -382,6 +449,11 @@ namespace nmos return is08_schemas::v1_0::map_activations_post_request_uri; } + web::uri make_flowcompatibilityapi_senders_active_constraints_put_request_uri(const nmos::api_version& version) + { + return is11_schemas::v1_0::senders_active_constraints_put_request_uri; + } + // load the json schema for the specified base URI web::json::value load_json_schema(const web::uri& id) { diff --git a/Development/nmos/json_schema.h b/Development/nmos/json_schema.h index e938a513e..377421d90 100644 --- a/Development/nmos/json_schema.h +++ b/Development/nmos/json_schema.h @@ -29,6 +29,8 @@ namespace nmos web::uri make_channelmappingapi_map_activations_post_request_schema_uri(const nmos::api_version& version); + web::uri make_flowcompatibilityapi_senders_active_constraints_put_request_uri(const nmos::api_version& version); + // load the json schema for the specified base URI web::json::value load_json_schema(const web::uri& id); } diff --git a/Development/third_party/bcp-004-01/README.md b/Development/third_party/bcp-004-01/README.md new file mode 100644 index 000000000..57375e929 --- /dev/null +++ b/Development/third_party/bcp-004-01/README.md @@ -0,0 +1,8 @@ +# AMWA BCP-004-01 NMOS Receiver Capabilities + +This directory contains files from the [AMWA BCP-004-01 NMOS Receiver Capabilities](https://github.com/AMWA-TV/bcp-004-01), in particular tagged versions of the JSON schemas used by the API specifications. + +Original source code: + +- (c) AMWA 2022 +- Licensed under the Apache License, Version 2.0; http://www.apache.org/licenses/LICENSE-2.0 diff --git a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/constraint_set.json b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/constraint_set.json new file mode 100644 index 000000000..ab439d378 --- /dev/null +++ b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/constraint_set.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes a Constraint Set", + "title": "Constraint Set", + "type": "object", + "minProperties": 1, + "properties": { + "urn:x-nmos:cap:meta:label": { + "description": "Freeform string label for the Constraint Set", + "type": "string" + }, + "urn:x-nmos:cap:meta:preference": { + "description": "This value expresses the relative 'weight' that the Receiver assigns to its preference for the streams satisfied by the associated Constraint Set. The weight is an integer in the range -100 through 100, where -100 is least preferred and 100 is most preferred. When the attribute is omitted, the effective value for the associated Constraint Set is 0.", + "type": "integer", + "default": 0, + "maximum": 100, + "minimum": -100 + }, + "urn:x-nmos:cap:meta:enabled": { + "description": "This value indicates whether a Constraint Set is available to use immediately (true) or whether this is an offline capability which can be activated via some unspecified configuration mechanism (false). When the attribute is omitted its value is assumed to be true.", + "type": "boolean", + "default": true + } + }, + "patternProperties": { + "^urn:x-nmos:cap:(?!meta:)": { + "$ref": "param_constraint.json" + } + } +} diff --git a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/constraint_sets.json b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/constraint_sets.json new file mode 100644 index 000000000..68e9229f4 --- /dev/null +++ b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/constraint_sets.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes a list of Constraint Sets", + "title": "Constraint Sets", + "type": "array", + "items": { + "$ref": "constraint_set.json" + } +} diff --git a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint.json b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint.json new file mode 100644 index 000000000..0537109c3 --- /dev/null +++ b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes a Parameter Constraint", + "title": "Parameter Constraint", + "type": "object", + "anyOf": [ + { "$ref": "param_constraint_string.json" }, + { "$ref": "param_constraint_integer.json" }, + { "$ref": "param_constraint_number.json" }, + { "$ref": "param_constraint_boolean.json" }, + { "$ref": "param_constraint_rational.json" } + ] +} diff --git a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_boolean.json b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_boolean.json new file mode 100644 index 000000000..fac6b9966 --- /dev/null +++ b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_boolean.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes a Boolean Parameter Constraint", + "title": "Boolean Parameter Constraint", + "type": "object", + "properties": { + "enum": { + "type": "array", + "minItems": 1, + "items": { + "type": "boolean" + } + } + } +} diff --git a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_integer.json b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_integer.json new file mode 100644 index 000000000..35c5aec23 --- /dev/null +++ b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_integer.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes an Integer Parameter Constraint", + "title": "Integer Parameter Constraint", + "type": "object", + "properties": { + "enum": { + "type": "array", + "minItems": 1, + "items": { + "type": "integer" + } + }, + "minimum": { + "type": "integer" + }, + "maximum": { + "type": "integer" + } + } +} diff --git a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_number.json b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_number.json new file mode 100644 index 000000000..24a93c1d6 --- /dev/null +++ b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_number.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes a Number Parameter Constraint", + "title": "Number Parameter Constraint", + "type": "object", + "properties": { + "enum": { + "type": "array", + "minItems": 1, + "items": { + "type": "number" + } + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + } + } +} diff --git a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_rational.json b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_rational.json new file mode 100644 index 000000000..31a8db61c --- /dev/null +++ b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_rational.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes a Rational Parameter Constraint", + "title": "Rational Parameter Constraint", + "type": "object", + "properties": { + "enum": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/rational" + } + }, + "minimum": { + "$ref": "#/definitions/rational" + }, + "maximum": { + "$ref": "#/definitions/rational" + } + }, + "definitions": { + "rational": { + "type": "object", + "required": [ + "numerator" + ], + "properties": { + "numerator": { + "type": "integer" + }, + "denominator": { + "type": "integer", + "default": 1 + } + }, + "additionalProperties": false + } + } +} diff --git a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_string.json b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_string.json new file mode 100644 index 000000000..a86600836 --- /dev/null +++ b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_string.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes a String Parameter Constraint", + "title": "String Parameter Constraint", + "type": "object", + "properties": { + "enum": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + } +} diff --git a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/receiver_constraint_sets.json b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/receiver_constraint_sets.json new file mode 100644 index 000000000..cf5a10c8c --- /dev/null +++ b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/receiver_constraint_sets.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes a Receiver with Constraint Sets", + "title": "Receiver with Constraint Sets", + "type": "object", + "required": [ + "caps" + ], + "properties": { + "caps": { + "description": "Capabilities", + "type": "object", + "dependencies": { + "constraint_sets": [ + "version" + ] + }, + "properties": { + "version": { + "description": "String formatted TAI timestamp (:) indicating when an attribute of the 'caps' object last changed", + "type": "string", + "pattern": "^[0-9]+:[0-9]+$" + }, + "constraint_sets": { + "$ref": "constraint_sets.json" + } + } + } + } +} diff --git a/Development/third_party/is-11/README.md b/Development/third_party/is-11/README.md new file mode 100644 index 000000000..5b244c614 --- /dev/null +++ b/Development/third_party/is-11/README.md @@ -0,0 +1,8 @@ +# AMWA IS-11 NMOS Flow Compatibility Management + +This directory contains files from the [AMWA IS-11 NMOS Flow Compatibility Management](https://github.com/AMWA-TV/is-11), in particular tagged versions of the JSON schemas used by the API specifications. + +Original source code: + +- (c) AMWA 2022 +- Licensed under the Apache License, Version 2.0; http://www.apache.org/licenses/LICENSE-2.0 diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints-base.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints-base.json new file mode 100644 index 000000000..500930043 --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints-base.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "type": "array", + "description": "Describes the Flow Compatibility Management API /senders/{senderId}/constraints base resource", + "title": "Flow Compatibility Management API /senders/{senderId}/constraints base resource", + "items": { + "type": "string", + "enum": [ + "active/", + "supported/" + ] + }, + "minItems": 2, + "maxItems": 2, + "uniqueItems": true +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints_active.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints_active.json new file mode 100644 index 000000000..b9a9253ca --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints_active.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "description": "Describes Constraints", + "title": "Constraints", + "type": "object", + "required": [ + "constraint_sets" + ], + "properties": { + "constraint_sets": { + "type": "array", + "items": { + "$ref": "https://github.com/AMWA-TV/bcp-004-01/raw/v1.0.x/APIs/schemas/constraint_set.json" + } + } + } +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints_supported.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints_supported.json new file mode 100644 index 000000000..3f21436e1 --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints_supported.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "type": "object", + "required": [ + "parameter_constraints" + ], + "properties": { + "parameter_constraints": { + "type": "array", + "items": { + "type": "string", + "pattern": "^urn:x-nmos:cap:" + }, + "contains": { + "type": "string", + "enum": [ + "urn:x-nmos:cap:meta:label", + "urn:x-nmos:cap:meta:preference", + "urn:x-nmos:cap:meta:enabled", + "urn:x-nmos:cap:format:media_type", + "urn:x-nmos:cap:format:grain_rate", + "urn:x-nmos:cap:format:frame_width", + "urn:x-nmos:cap:format:frame_height", + "urn:x-nmos:cap:format:interlace_mode", + "urn:x-nmos:cap:format:color_sampling", + "urn:x-nmos:cap:format:component_depth", + "urn:x-nmos:cap:format:channel_count", + "urn:x-nmos:cap:format:sample_rate", + "urn:x-nmos:cap:format:sample_depth" + ] + }, + "minContains": 13, + "uniqueItems": true + } + } +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/edid.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/edid.json new file mode 100644 index 000000000..957136993 --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/edid.json @@ -0,0 +1,86 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "type": "object", + "required": [ + "manufacturer", + "manufacture_week", + "manufacture_year", + "screen_size", + "gamma", + "color_samplings", + "established_timings" + ], + "properties": { + "manufacturer": { + "type": "string" + }, + "manufacture_week": { + "type": "integer" + }, + "manufacture_year": { + "type": "integer" + }, + "screen_size": { + "type": "object", + "required": [ + "width", + "height" + ], + "properties": { + "width": { + "description": "Horizontal screen size, in centimetres", + "type": "integer" + }, + "height": { + "description": "Vertical screen size, in centimetres", + "type": "integer" + } + } + }, + "gamma": { + "type": "number" + }, + "color_samplings": { + "description": "Digital display type in terms of supported subsampling modes", + "type": "array", + "minItems": 1, + "maxItems": 3, + "uniqueItems": true, + "contains": { + "const": "RGB" + }, + "items": { + "type": "string", + "enum": [ + "RGB", + "YCbCr-4:4:4", + "YCbCr-4:2:2" + ] + } + }, + "established_timings": { + "type": "array", + "items": { + "$ref": "edid_timing.json" + } + }, + "standard_timings": { + "type": "array", + "items": { + "$ref": "edid_timing.json" + } + }, + "detailed_timings": { + "type": "array", + "items": { + "$ref": "edid_timing.json" + } + }, + "cta_861_timings": { + "type": "array", + "items": { + "$ref": "edid_timing.json" + } + } + } +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/edid_timing.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/edid_timing.json new file mode 100644 index 000000000..c2a1d4f4e --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/edid_timing.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "type": "object", + "required": [ + "frame_width", + "frame_height", + "grain_rate" + ], + "properties": { + "frame_width": { + "type": "integer" + }, + "frame_height": { + "type": "integer" + }, + "grain_rate": { + "description": "Frame rate in Hz", + "type": "object", + "required" : [ + "numerator" + ], + "properties" : { + "numerator" : { + "description" : "Numerator", + "type" : "integer" + }, + "denominator" : { + "description" : "Denominator", + "type" : "integer", + "default" : 1 + } + } + }, + "interlace_mode" : { + "description" : "Interlaced video mode for frames", + "type" : "string", + "default": "progressive", + "enum" : [ + "progressive", + "interlaced_tff", + "interlaced_bff", + "interlaced_psf" + ] + } + } +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/empty_constraints_active.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/empty_constraints_active.json new file mode 100644 index 000000000..6a9627b83 --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/empty_constraints_active.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "description": "Describes empty Constraints", + "title": "Constraints", + "required": [ + "constraint_sets" + ], + "properties": { + "constraint_sets": { + "type": "array", + "items": { + "$ref": "https://raw.githubusercontent.com/AMWA-TV/nmos-receiver-capabilities/v1.0.0/APIs/schemas/constraint_set.json" + }, + "minItems": 0, + "maxItems": 0 + } + } +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/error.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/error.json new file mode 100644 index 000000000..d0db3f72b --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/error.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "type": "object", + "description": "Describes the standard error response which is returned with HTTP codes 400 and above", + "title": "Error response", + "required": [ + "code", + "error", + "debug" + ], + "properties": { + "code": { + "description": "HTTP error code", + "type": "integer", + "minimum": 400, + "maximum": 599 + }, + "error": { + "description": "Human readable message which is suitable for user interface display, and helpful to the user", + "type": "string" + }, + "debug": { + "description": "Debug information which may assist a programmer working with the API", + "type": ["null", "string"] + } + } +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/flowcompatibility-api-base.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/flowcompatibility-api-base.json new file mode 100644 index 000000000..06df2742f --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/flowcompatibility-api-base.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "type": "array", + "description": "Describes the Flow Compatibility Management API base resource", + "title": "Flow Compatibility Management API base resource", + "items": { + "type": "string", + "enum": [ + "inputs/", + "outputs/", + "senders/", + "receivers/" + ] + }, + "minItems": 4, + "maxItems": 4, + "uniqueItems": true +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/input-edid-base.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/input-edid-base.json new file mode 100644 index 000000000..fd35c85a6 --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/input-edid-base.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "type": "array", + "description": "Describes the Flow Compatibility Management API /inputs/{inputId}/edid base resource", + "title": "Flow Compatibility Management API /inputs/{inputId}/edid base resource", + "items": { + "type": "string", + "enum": [ + "base/", + "effective/" + ] + }, + "minItems": 2, + "maxItems": 2, + "uniqueItems": true +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/input-output-base.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/input-output-base.json new file mode 100644 index 000000000..c247667f0 --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/input-output-base.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "type": "array", + "description": "Describes the Flow Compatibility Management API /inputs/{inputId} and /outputs/{outputId} base resource", + "title": "Flow Compatibility Management API /inputs/{inputId} and /outputs/{outputId} base resource", + "items": { + "type": "string", + "enum": [ + "edid/", + "properties/" + ] + }, + "minItems": 2, + "maxItems": 2, + "uniqueItems": true +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/input.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/input.json new file mode 100644 index 000000000..d50efbf2c --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/input.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "type": "object", + "description": "Describes an Input", + "title": "Input resource", + "allOf": [ + { "$ref": "https://raw.githubusercontent.com/AMWA-TV/nmos-discovery-registration/v1.3.1/APIs/schemas/resource_core.json" }, + { + "type": "object", + "required": [ + "connected", + "edid_support" + ], + "properties": { + "base_edid": { "$ref": "edid.json" }, + "connected": { + "description": "Whether the upstream counterpart of this Input is connected", + "type": "boolean" + }, + "edid_support": { + "description": "Whether the Input supports EDID", + "type": "boolean" + }, + "effective_edid": { "$ref": "edid.json" } + } + } + ] +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/output.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/output.json new file mode 100644 index 000000000..a3d1ec61d --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/output.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "type": "object", + "description": "Describes an Output", + "title": "Output resource", + "allOf": [ + { "$ref": "https://raw.githubusercontent.com/AMWA-TV/nmos-discovery-registration/v1.3.1/APIs/schemas/resource_core.json" }, + { + "type": "object", + "required": [ + "connected", + "edid_support" + ], + "properties": { + "connected": { + "description": "Whether the downstream counterpart of this Output is connected", + "type": "boolean" + }, + "edid": { "$ref": "edid.json" }, + "edid_support": { + "description": "Whether the Output supports EDID", + "type": "boolean" + } + } + } + ] +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-base.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-base.json new file mode 100644 index 000000000..ea9530b39 --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-base.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "type": "array", + "description": "Describes the Flow Compatibility Management API /receivers/{receiverId} base resource", + "title": "Flow Compatibility Management API /receivers/{receiverId} base resource", + "items": { + "type": "string", + "enum": [ + "outputs/", + "status/" + ] + }, + "minItems": 2, + "maxItems": 2, + "uniqueItems": true +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-status.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-status.json new file mode 100644 index 000000000..08021ba60 --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-status.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "type": "object", + "description": "Status of Receiver", + "required": [ + "state" + ], + "properties": { + "state": { + "type": "string", + "enum": [ + "No Transport File", + "OK", + "Receiver Capabilities Violation" + ] + } + } +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/resource-list.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/resource-list.json new file mode 100644 index 000000000..8c44c9cb7 --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/resource-list.json @@ -0,0 +1,11 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "type": "array", + "description": "List of UUIDs", + "title": "List of resources", + "items": { + "type": "string", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/$" + }, + "uniqueItems": true +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/sender-base.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/sender-base.json new file mode 100644 index 000000000..46b755b99 --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/sender-base.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "type": "array", + "description": "Describes the Flow Compatibility Management API /senders/{senderId} base resource", + "title": "Flow Compatibility Management API /senders/{senderId} base resource", + "items": { + "type": "string", + "enum": [ + "constraints/", + "inputs/", + "status/" + ] + }, + "minItems": 3, + "maxItems": 3, + "uniqueItems": true +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/sender-status.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/sender-status.json new file mode 100644 index 000000000..19a706c46 --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/sender-status.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "type": "object", + "description": "Status of Sender", + "required": [ + "state" + ], + "properties": { + "state": { + "type": "string", + "enum": [ + "Unconstrained", + "Constrained", + "Active Constraints Violation", + "No Signal", + "Awaiting Signal" + ] + } + } +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/uuid-list.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/uuid-list.json new file mode 100644 index 000000000..02de49a1a --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/uuid-list.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "type": "array", + "description": "List of UUIDs", + "items": { + "type": "string", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + }, + "uniqueItems": true +} From ca8c804cf08027f2161724c882a4c86c08a61d89 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sun, 16 Jan 2022 23:59:43 +0300 Subject: [PATCH 006/109] Implement PUT and DELETE for /senders/{senderId}/constraints/active, move Flow Compatibility API helper functions into details namespace --- .../nmos-cpp-node/node_implementation.cpp | 14 +- Development/nmos/flowcompatibility_api.cpp | 416 +++++++++++++----- Development/nmos/flowcompatibility_api.h | 9 +- .../nmos/flowcompatibility_resources.cpp | 26 +- .../nmos/flowcompatibility_resources.h | 2 + Development/nmos/json_fields.h | 2 +- Development/nmos/node_server.cpp | 2 +- Development/nmos/node_server.h | 2 + 8 files changed, 354 insertions(+), 119 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 3a65b5cd9..1ee9f7b74 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1386,6 +1386,17 @@ nmos::experimental::details::flowcompatibility_effective_edid_setter make_node_i }; } +// Example Flow Compatibility Management API callback to update active constraints +nmos::experimental::details::flowcompatibility_active_constraints_put_handler make_node_implementation_active_constraints_handler(slog::base_gate& gate) +{ + return [&gate](const nmos::id& sender_id, const web::json::value& constraint_sets) -> bool + { + bool sender_can_adhere = true; + slog::log(gate, SLOG_FLF) << "Active constraints are updated for sender " << sender_id; + return sender_can_adhere; + }; +} + namespace impl { nmos::interlace_mode get_interlace_mode(const nmos::settings& settings) @@ -1506,5 +1517,6 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode .on_channelmapping_activated(make_node_implementation_channelmapping_activation_handler(gate)) .on_base_edid_changed(make_node_implementation_flowcompatibility_base_edid_put_handler(gate)) .on_base_edid_deleted(make_node_implementation_flowcompatibility_base_edid_delete_handler(gate)) - .on_set_effective_edid(make_node_implementation_effective_edid_setter(model.flowcompatibility_resources, gate)); + .on_set_effective_edid(make_node_implementation_effective_edid_setter(model.flowcompatibility_resources, gate)) + .on_active_constraints_changed(make_node_implementation_active_constraints_handler(gate)); } diff --git a/Development/nmos/flowcompatibility_api.cpp b/Development/nmos/flowcompatibility_api.cpp index d3edc64bc..582479515 100644 --- a/Development/nmos/flowcompatibility_api.cpp +++ b/Development/nmos/flowcompatibility_api.cpp @@ -1,70 +1,51 @@ #include "nmos/flowcompatibility_api.h" +#include #include #include #include "cpprest/containerstream.h" -#include "nmos/is11_versions.h" +#include "cpprest/json_validator.h" +#include "nmos/capabilities.h" // for nmos::fields::constraint_sets #include "nmos/flowcompatibility_resources.h" +#include "nmos/is11_versions.h" +#include "nmos/json_schema.h" #include "nmos/model.h" namespace nmos { namespace experimental { - web::http::experimental::listener::api_router make_unmounted_flowcompatibility_api(nmos::node_model& model, details::flowcompatibility_base_edid_put_handler base_edid_put_handler, details::flowcompatibility_base_edid_delete_handler base_edid_delete_handler, details::flowcompatibility_effective_edid_setter effective_edid_setter, slog::base_gate& gate); - - web::http::experimental::listener::api_router make_flowcompatibility_api(nmos::node_model& model, details::flowcompatibility_base_edid_put_handler base_edid_put_handler, details::flowcompatibility_base_edid_delete_handler base_edid_delete_handler, details::flowcompatibility_effective_edid_setter effective_edid_setter, slog::base_gate& gate) + namespace details { - using namespace web::http::experimental::listener::api_router_using_declarations; - - api_router flowcompatibility_api; - - flowcompatibility_api.support(U("/?"), methods::GET, [](http_request req, http_response res, const string_t&, const route_parameters&) - { - set_reply(res, status_codes::OK, nmos::make_sub_routes_body({ U("x-nmos/") }, req, res)); - return pplx::task_from_result(true); - }); - - flowcompatibility_api.support(U("/x-nmos/?"), methods::GET, [](http_request req, http_response res, const string_t&, const route_parameters&) + void set_edid_endpoint_as_reply(web::http::http_response& res, const std::pair& id_type, const web::json::value& edid_endpoint, nmos::api_gate& gate) { - set_reply(res, status_codes::OK, nmos::make_sub_routes_body({ U("flowcompatibility/") }, req, res)); - return pplx::task_from_result(true); - }); - - const auto versions = with_read_lock(model.mutex, [&model] { return nmos::is11_versions::from_settings(model.settings); }); - flowcompatibility_api.support(U("/x-nmos/") + nmos::patterns::flowcompatibility_api.pattern + U("/?"), methods::GET, [versions](http_request req, http_response res, const string_t&, const route_parameters&) - { - set_reply(res, status_codes::OK, nmos::make_sub_routes_body(nmos::make_api_version_sub_routes(versions), req, res)); - return pplx::task_from_result(true); - }); - - flowcompatibility_api.mount(U("/x-nmos/") + nmos::patterns::flowcompatibility_api.pattern + U("/") + nmos::patterns::version.pattern, make_unmounted_flowcompatibility_api(model, base_edid_put_handler, base_edid_delete_handler, effective_edid_setter, gate)); - - return flowcompatibility_api; - } + if (!edid_endpoint.is_null()) + { + // The base edid endpoint may be an empty object which signalizes that Base EDID is currently absent but it's allowed to be set + // The effective edid and edid endpoints should contain either edid_binary or edid_href + auto& edid_binary = nmos::fields::edid_binary(edid_endpoint); + auto& edid_href = nmos::fields::edid_href(edid_endpoint); - void set_edid_endpoint_as_reply(web::http::http_response& res, const std::pair& id_type, const web::json::value& edid_endpoint, nmos::api_gate& gate) - { - if (!edid_endpoint.is_null()) - { - // The base edid endpoint may be an empty object which signalizes that Base EDID is currently absent but it's allowed to be set - // The effective edid and edid endpoints should contain either edid_binary or edid_href - auto& edid_binary = nmos::fields::edid_binary(edid_endpoint); - auto& edid_href = nmos::fields::edid_href(edid_endpoint); + if (!edid_binary.is_null()) + { + slog::log(gate, SLOG_FLF) << "Returning EDID binary for " << id_type; - if (!edid_binary.is_null()) - { - slog::log(gate, SLOG_FLF) << "Returning EDID binary for " << id_type; + auto i_stream = concurrency::streams::bytestream::open_istream(edid_binary.as_string()); + set_reply(res, web::http::status_codes::OK, i_stream); + } + else if (!edid_href.is_null()) + { + slog::log(gate, SLOG_FLF) << "Redirecting to EDID file for " << id_type; - auto i_stream = concurrency::streams::bytestream::open_istream(edid_binary.as_string()); - set_reply(res, web::http::status_codes::OK, i_stream); - } - else if (!edid_href.is_null()) - { - slog::log(gate, SLOG_FLF) << "Redirecting to EDID file for " << id_type; + set_reply(res, web::http::status_codes::TemporaryRedirect); + res.headers().add(web::http::header_names::location, nmos::fields::edid_href(edid_endpoint)); + } + else + { + slog::log(gate, SLOG_FLF) << "EDID requested for " << id_type << " does not exist"; - set_reply(res, web::http::status_codes::TemporaryRedirect); - res.headers().add(web::http::header_names::location, nmos::fields::edid_href(edid_endpoint)); + set_error_reply(res, web::http::status_codes::NoContent); + } } else { @@ -73,69 +54,173 @@ namespace nmos set_error_reply(res, web::http::status_codes::NoContent); } } - else - { - slog::log(gate, SLOG_FLF) << "EDID requested for " << id_type << " does not exist"; - set_error_reply(res, web::http::status_codes::NoContent); + // it's expected that read lock is already catched for the model + bool all_resources_exist(nmos::resources& resources, const web::json::array& resource_ids, const nmos::type& type) + { + for (const auto& resource_id : resource_ids) + { + if (resources.end() == find_resource(resources, std::make_pair(resource_id.as_string(), type))) + { + return false; + } + } + return true; } - } - // it's expected that read lock is already catched for the model - bool all_resources_exist(nmos::resources& resources, const web::json::array& resource_ids, const nmos::type& type) - { - for (const auto& resource_id : resource_ids) + bool validate_constraint_sets(const web::json::array& constraint_sets, const std::unordered_set& supported_param_constraints) { - if (resources.end() == find_resource(resources, std::make_pair(resource_id.as_string(), type))) + for (const auto& constraint_set : constraint_sets) { - return false; + const auto& param_constraints = constraint_set.as_object(); + if (param_constraints.end() != std::find_if(param_constraints.begin(), param_constraints.end(), [&supported_param_constraints](const std::pair& constraint) + { + return supported_param_constraints.count(constraint.first) == 0; + })) { + return false; + } } + + return true; } - return true; - } - // it's expected that read lock is already catched for the model - void update_version(nmos::resources& resources, const web::json::array& resource_ids, const utility::string_t& new_version) - { - for (const auto& resource_id : resource_ids) + // it's expected that read lock is already catched for the model + void update_version(nmos::resources& resources, const nmos::id& resource_id, const utility::string_t& new_version) { - modify_resource(resources, resource_id.as_string(), [&new_version](nmos::resource& resource) + modify_resource(resources, resource_id, [&new_version](nmos::resource& resource) { resource.data[nmos::fields::version] = web::json::value::string(new_version); }); } - } - // it's expected that write lock is already catched for the model and an input with the resource_id exists - void update_effective_edid(nmos::node_model& model, const details::flowcompatibility_effective_edid_setter& effective_edid_setter, const utility::string_t resource_id) - { - boost::variant effective_edid; - bst::optional effective_edid_properties = boost::none; + // it's expected that read lock is already catched for the model + void update_version(nmos::resources& resources, const web::json::array& resource_ids, const utility::string_t& new_version) + { + for (const auto& resource_id : resource_ids) + { + update_version(resources, resource_id.as_string(), new_version); + } + } + + // it's expected that write lock is already catched for the model and an input with the resource_id exists + void update_effective_edid(nmos::node_model& model, const flowcompatibility_effective_edid_setter& effective_edid_setter, const utility::string_t resource_id) + { + boost::variant effective_edid; + bst::optional effective_edid_properties = boost::none; + + effective_edid_setter(resource_id, effective_edid, effective_edid_properties); + + utility::string_t updated_timestamp; + + modify_resource(model.flowcompatibility_resources, resource_id, [&effective_edid, &effective_edid_properties, &updated_timestamp](nmos::resource& input) + { + input.data[nmos::fields::endpoint_effective_edid] = boost::apply_visitor(edid_file_visitor(), effective_edid); - effective_edid_setter(resource_id, effective_edid, effective_edid_properties); + if (effective_edid_properties.has_value()) + { + input.data[nmos::fields::effective_edid_properties] = effective_edid_properties.value(); + } - utility::string_t updated_timestamp; + updated_timestamp = nmos::make_version(); + input.data[nmos::fields::version] = web::json::value::string(updated_timestamp); + }); + + const std::pair id_type{ resource_id, nmos::types::input }; + auto resource = find_resource(model.flowcompatibility_resources, id_type); + + update_version(model.node_resources, nmos::fields::senders(resource->data), updated_timestamp); + } - modify_resource(model.flowcompatibility_resources, resource_id, [&effective_edid, &effective_edid_properties, &updated_timestamp](nmos::resource& input) + // it's expected that write lock is already catched for the model and IS-11 sender exists + void set_active_constraints(nmos::node_model& model, const nmos::id& sender_id, const web::json::value& constraints, const flowcompatibility_effective_edid_setter& effective_edid_setter) { - input.data[nmos::fields::endpoint_effective_edid] = boost::apply_visitor(edid_file_visitor(), effective_edid); + const std::pair sender_id_type{ sender_id, nmos::types::sender }; + auto resource = find_resource(model.flowcompatibility_resources, sender_id_type); + auto matching_resource = find_resource(model.node_resources, sender_id_type); + if (model.node_resources.end() == matching_resource) + { + throw std::logic_error("matching IS-04 resource not found"); + } + + // Pre-check for resources existence before Active Constraints modified and effective_edid_setter executed + if (effective_edid_setter) + { + for (const auto& input_id : nmos::fields::inputs(resource->data)) + { + const std::pair input_id_type{ input_id.as_string(), nmos::types::input }; + auto input = find_resource(model.flowcompatibility_resources, input_id_type); + if (model.flowcompatibility_resources.end() != input) + { + if (!all_resources_exist(model.node_resources, nmos::fields::senders(input->data), nmos::types::sender)) + { + throw std::logic_error("associated IS-04 sender not found"); + } + } + else + { + throw std::logic_error("associated IS-11 input not found"); + } + } + } + + utility::string_t updated_timestamp; + + // Update Active Constraints in flowcompatibility_resources + modify_resource(model.flowcompatibility_resources, sender_id, [&constraints, &updated_timestamp](nmos::resource& sender) + { + sender.data[nmos::fields::endpoint_active_constraints] = make_flowcompatibility_active_constraints_endpoint(constraints); + + updated_timestamp = nmos::make_version(); + sender.data[nmos::fields::version] = web::json::value::string(updated_timestamp); + }); + + details::update_version(model.node_resources, sender_id, updated_timestamp); - if (effective_edid_properties.has_value()) + if (effective_edid_setter) { - input.data[nmos::fields::effective_edid_properties] = effective_edid_properties.value(); + for (const auto& input_id : nmos::fields::inputs(resource->data)) + { + details::update_effective_edid(model, effective_edid_setter, input_id.as_string()); + } } - updated_timestamp = nmos::make_version(); - input.data[nmos::fields::version] = web::json::value::string(updated_timestamp); + model.notify(); + } + } + + web::http::experimental::listener::api_router make_unmounted_flowcompatibility_api(nmos::node_model& model, details::flowcompatibility_base_edid_put_handler base_edid_put_handler, details::flowcompatibility_base_edid_delete_handler base_edid_delete_handler, details::flowcompatibility_effective_edid_setter effective_edid_setter, details::flowcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate); + + web::http::experimental::listener::api_router make_flowcompatibility_api(nmos::node_model& model, details::flowcompatibility_base_edid_put_handler base_edid_put_handler, details::flowcompatibility_base_edid_delete_handler base_edid_delete_handler, details::flowcompatibility_effective_edid_setter effective_edid_setter, details::flowcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate) + { + using namespace web::http::experimental::listener::api_router_using_declarations; + + api_router flowcompatibility_api; + + flowcompatibility_api.support(U("/?"), methods::GET, [](http_request req, http_response res, const string_t&, const route_parameters&) + { + set_reply(res, status_codes::OK, nmos::make_sub_routes_body({ U("x-nmos/") }, req, res)); + return pplx::task_from_result(true); + }); + + flowcompatibility_api.support(U("/x-nmos/?"), methods::GET, [](http_request req, http_response res, const string_t&, const route_parameters&) + { + set_reply(res, status_codes::OK, nmos::make_sub_routes_body({ U("flowcompatibility/") }, req, res)); + return pplx::task_from_result(true); + }); + + const auto versions = with_read_lock(model.mutex, [&model] { return nmos::is11_versions::from_settings(model.settings); }); + flowcompatibility_api.support(U("/x-nmos/") + nmos::patterns::flowcompatibility_api.pattern + U("/?"), methods::GET, [versions](http_request req, http_response res, const string_t&, const route_parameters&) + { + set_reply(res, status_codes::OK, nmos::make_sub_routes_body(nmos::make_api_version_sub_routes(versions), req, res)); + return pplx::task_from_result(true); }); - const std::pair id_type{ resource_id, nmos::types::input }; - auto resource = find_resource(model.flowcompatibility_resources, id_type); + flowcompatibility_api.mount(U("/x-nmos/") + nmos::patterns::flowcompatibility_api.pattern + U("/") + nmos::patterns::version.pattern, make_unmounted_flowcompatibility_api(model, base_edid_put_handler, base_edid_delete_handler, effective_edid_setter, active_constraints_handler, gate)); - update_version(model.node_resources, nmos::fields::senders(resource->data), updated_timestamp); + return flowcompatibility_api; } - web::http::experimental::listener::api_router make_unmounted_flowcompatibility_api(nmos::node_model& model, details::flowcompatibility_base_edid_put_handler base_edid_put_handler, details::flowcompatibility_base_edid_delete_handler base_edid_delete_handler, details::flowcompatibility_effective_edid_setter effective_edid_setter, slog::base_gate& gate_) + web::http::experimental::listener::api_router make_unmounted_flowcompatibility_api(nmos::node_model& model, details::flowcompatibility_base_edid_put_handler base_edid_put_handler, details::flowcompatibility_base_edid_delete_handler base_edid_delete_handler, details::flowcompatibility_effective_edid_setter effective_edid_setter, details::flowcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate_) { using namespace web::http::experimental::listener::api_router_using_declarations; @@ -144,6 +229,12 @@ namespace nmos // check for supported API version const auto versions = with_read_lock(model.mutex, [&model] { return nmos::is11_versions::from_settings(model.settings); }); + const web::json::experimental::json_validator validator + { + nmos::experimental::load_json_schema, + boost::copy_range>(versions | boost::adaptors::transformed(experimental::make_flowcompatibilityapi_senders_active_constraints_put_request_uri)) + }; + flowcompatibility_api.support(U(".*"), nmos::details::make_api_version_handler(versions, gate_)); flowcompatibility_api.support(U("/?"), methods::GET, [](http_request req, http_response res, const string_t&, const route_parameters&) @@ -152,12 +243,6 @@ namespace nmos return pplx::task_from_result(true); }); - flowcompatibility_api.support(U("/") + nmos::patterns::flowCompatibilityResourceType.pattern + U("/?"), methods::GET, [](http_request req, http_response res, const string_t&, const route_parameters&) - { - set_reply(res, status_codes::OK, web::json::value::array()); - return pplx::task_from_result(true); - }); - flowcompatibility_api.support(U("/") + nmos::patterns::flowCompatibilityResourceType.pattern + U("/?"), methods::GET, [&model, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) { nmos::api_gate gate(gate_, req, parameters); @@ -381,7 +466,7 @@ namespace nmos } if ("active" == constraintsType) { - set_reply(res, status_codes::OK, nmos::fields::active_constraint_sets(resource->data)); + set_reply(res, status_codes::OK, nmos::fields::active_constraint_sets(nmos::fields::endpoint_active_constraints(resource->data))); } else if ("supported" == constraintsType) { set_reply(res, status_codes::OK, nmos::fields::supported_param_constraints(resource->data)); @@ -472,7 +557,7 @@ namespace nmos slog::log(gate, SLOG_FLF) << "EDID requested for " << id_type; - set_edid_endpoint_as_reply(res, id_type, edid_endpoint, gate); + details::set_edid_endpoint_as_reply(res, id_type, edid_endpoint, gate); } else { set_reply(res, status_codes::NotImplemented); @@ -511,7 +596,7 @@ namespace nmos slog::log(gate, SLOG_FLF) << edidType << " EDID requested for " << id_type; - set_edid_endpoint_as_reply(res, id_type, edid_endpoint, gate); + details::set_edid_endpoint_as_reply(res, id_type, edid_endpoint, gate); } else if (nmos::details::is_erased_resource(resources, id_type)) { @@ -555,10 +640,13 @@ namespace nmos base_edid_put_handler(resourceId, base_edid, base_edid_properties); } - // Check senders exist before modifying the input and therefore versions of associated senders - if (!all_resources_exist(model.node_resources, nmos::fields::senders(resource->data), nmos::types::sender)) + // Pre-check for resources existence before Base EDID modified and effective_edid_setter executed + if (effective_edid_setter) { - throw std::logic_error("associated IS-04 sender not found"); + if (!details::all_resources_exist(model.node_resources, nmos::fields::senders(resource->data), nmos::types::sender)) + { + throw std::logic_error("associated IS-04 sender not found"); + } } utility::string_t updated_timestamp; @@ -577,11 +665,11 @@ namespace nmos input.data[nmos::fields::version] = web::json::value::string(updated_timestamp); }); - update_version(model.node_resources, nmos::fields::senders(resource->data), updated_timestamp); + details::update_version(model.node_resources, nmos::fields::senders(resource->data), updated_timestamp); if (effective_edid_setter) { - update_effective_edid(model, effective_edid_setter, resourceId); + details::update_effective_edid(model, effective_edid_setter, resourceId); } model.notify(); @@ -590,7 +678,7 @@ namespace nmos } else { - slog::log(gate, SLOG_FLF) << "Base EDID update is requested for " << id_type << " but this input is locked"; + slog::log(gate, SLOG_FLF) << "Base EDID update is requested for " << id_type << " but this operation is locked"; set_error_reply(res, status_codes::Locked); } @@ -639,10 +727,13 @@ namespace nmos base_edid_delete_handler(resourceId); } - // Check senders exist before modifying the input and therefore versions of associated senders - if (!all_resources_exist(model.node_resources, nmos::fields::senders(resource->data), nmos::types::sender)) + // Pre-check for resources existence before Base EDID modified and effective_edid_setter executed + if (effective_edid_setter) { - throw std::logic_error("associated IS-04 sender not found"); + if (!details::all_resources_exist(model.node_resources, nmos::fields::senders(resource->data), nmos::types::sender)) + { + throw std::logic_error("associated IS-04 sender not found"); + } } utility::string_t updated_timestamp; @@ -655,11 +746,11 @@ namespace nmos input.data[nmos::fields::version] = web::json::value::string(updated_timestamp); }); - update_version(model.node_resources, nmos::fields::senders(resource->data), updated_timestamp); + details::update_version(model.node_resources, nmos::fields::senders(resource->data), updated_timestamp); if (effective_edid_setter) { - update_effective_edid(model, effective_edid_setter, resourceId); + details::update_effective_edid(model, effective_edid_setter, resourceId); } model.notify(); @@ -668,7 +759,7 @@ namespace nmos } else { - slog::log(gate, SLOG_FLF) << "Base EDID deletion is requested for " << id_type << " but this input is locked"; + slog::log(gate, SLOG_FLF) << "Base EDID deletion is requested for " << id_type << " but this operation is locked"; set_error_reply(res, status_codes::Locked); } @@ -692,6 +783,119 @@ namespace nmos return pplx::task_from_result(true); }); + flowcompatibility_api.support(U("/") + nmos::patterns::senderType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/constraints/active/?"), methods::PUT, [&model, validator, active_constraints_handler, effective_edid_setter, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + { + nmos::api_gate gate(gate_, req, parameters); + return nmos::details::extract_json(req, gate).then([&model, req, res, parameters, &validator, &active_constraints_handler, &effective_edid_setter, gate](value data) mutable + { + const nmos::api_version version = nmos::parse_api_version(parameters.at(nmos::patterns::version.name)); + + validator.validate(data, experimental::make_flowcompatibilityapi_senders_active_constraints_put_request_uri(version)); + + auto lock = model.write_lock(); + auto& resources = model.flowcompatibility_resources; + + const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); + + const std::pair id_type{ resourceId, nmos::types::sender }; + auto resource = find_resource(resources, id_type); + if (resources.end() != resource) + { + const auto supported_param_constraints = boost::copy_range>(nmos::fields::parameter_constraints(nmos::fields::supported_param_constraints(resource->data)) | boost::adaptors::transformed([](const web::json::value& param_constraint) + { + return std::unordered_set::value_type{ param_constraint.as_string() }; + })); + + if (details::validate_constraint_sets(nmos::fields::constraint_sets(data).as_array(), supported_param_constraints)) + { + auto& endpoint_active_constraints = nmos::fields::endpoint_active_constraints(resource->data); + + if (!nmos::fields::temporarily_locked(endpoint_active_constraints)) + { + slog::log(gate, SLOG_FLF) << "Active Constraints update is requested for " << id_type; + + bool can_adhere = true; + + if (active_constraints_handler) + { + can_adhere = active_constraints_handler(resourceId, data); + } + + if (can_adhere) + { + details::set_active_constraints(model, resourceId, data, effective_edid_setter); + set_reply(res, status_codes::OK, data); + } + else + { + slog::log(gate, SLOG_FLF) << "Active Constraints update is requested for " << id_type << " but this sender can't adhere to these Constraints"; + set_error_reply(res, status_codes::UnprocessableEntity); + } + } + else + { + slog::log(gate, SLOG_FLF) << "Active Constraints update is requested for " << id_type << " but this operation is locked"; + set_error_reply(res, status_codes::Locked); + } + } + else + { + set_error_reply(res, status_codes::BadRequest, U("The requested Constraint Set uses Parameter Constraints unsupported by this Sender.")); + } + } + else if (nmos::details::is_erased_resource(resources, id_type)) + { + set_error_reply(res, status_codes::NotFound, U("Not Found; ") + nmos::details::make_erased_resource_error()); + } + else + { + set_reply(res, status_codes::NotFound); + } + + return true; + }); + }); + + flowcompatibility_api.support(U("/") + nmos::patterns::senderType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/constraints/active/?"), methods::DEL, [&model, effective_edid_setter, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + { + nmos::api_gate gate(gate_, req, parameters); + auto lock = model.write_lock(); + auto& resources = model.flowcompatibility_resources; + + const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); + + const std::pair id_type{ resourceId, nmos::types::sender }; + auto resource = find_resource(resources, id_type); + if (resources.end() != resource) + { + auto& endpoint_active_constraints = nmos::fields::endpoint_active_constraints(resource->data); + + if (!nmos::fields::temporarily_locked(endpoint_active_constraints)) + { + slog::log(gate, SLOG_FLF) << "Active Constraints deletion is requested for " << id_type; + + auto data = web::json::value::array(); + details::set_active_constraints(model, resourceId, data, effective_edid_setter); + set_reply(res, status_codes::OK, nmos::fields::active_constraint_sets(nmos::fields::endpoint_active_constraints(resource->data))); + } + else + { + slog::log(gate, SLOG_FLF) << "Active Constraints update is requested for " << id_type << " but this operation is locked"; + set_error_reply(res, status_codes::Locked); + } + } + else if (nmos::details::is_erased_resource(resources, id_type)) + { + set_error_reply(res, status_codes::NotFound, U("Not Found; ") + nmos::details::make_erased_resource_error()); + } + else + { + set_reply(res, status_codes::NotFound); + } + + return pplx::task_from_result(true); + }); + return flowcompatibility_api; } } diff --git a/Development/nmos/flowcompatibility_api.h b/Development/nmos/flowcompatibility_api.h index f722cadb1..5d2d0e82b 100644 --- a/Development/nmos/flowcompatibility_api.h +++ b/Development/nmos/flowcompatibility_api.h @@ -20,13 +20,18 @@ namespace nmos // it can be used to perform any final validation of the specified Base EDID and set parsed Base EDID properties // it may throw web::json::json_exception, which will be mapped to a 400 Bad Request status code with NMOS error "debug" information including the exception message // or std::runtime_error, which will be mapped to a 500 Internal Error status code with NMOS error "debug" information including the exception message - // if base_edid has no value, the handler should not throw any exceptions typedef std::function& base_edid_properties)> flowcompatibility_base_edid_put_handler; // a flowcompatibility_base_edid_delete_handler is a notification that the Base EDID for the specified IS-11 input has been deleted // this callback should not throw exceptions, as the Base EDID will already have been changed and those changes will not be rolled back typedef std::function flowcompatibility_base_edid_delete_handler; + // a flowcompatibility_active_constraints_put_handler is a notification that the Active Constraints for the specified IS-11 sender has changed + // it can be used to perform any final validation of the specified Active Constraints + // it may throw web::json::json_exception, which will be mapped to a 400 Bad Request status code with NMOS error "debug" information including the exception message + // or std::runtime_error, which will be mapped to a 500 Internal Error status code with NMOS error "debug" information including the exception message + typedef std::function flowcompatibility_active_constraints_put_handler; + // a flowcompatibility_effective_edid_setter updates the specified Effective EDID for the specified IS-11 input // effective EDID of the input is updated when either Active Constraints of a Sender associated with this input are updated // or base EDID of the input is updated or deleted @@ -34,7 +39,7 @@ namespace nmos typedef std::function& effective_edid, bst::optional& effective_edid_properties)> flowcompatibility_effective_edid_setter; } - web::http::experimental::listener::api_router make_flowcompatibility_api(nmos::node_model& model, details::flowcompatibility_base_edid_put_handler base_edid_put_handler, details::flowcompatibility_base_edid_delete_handler base_edid_delete_handler, details::flowcompatibility_effective_edid_setter effective_edid_setter, slog::base_gate& gate); + web::http::experimental::listener::api_router make_flowcompatibility_api(nmos::node_model& model, details::flowcompatibility_base_edid_put_handler base_edid_put_handler, details::flowcompatibility_base_edid_delete_handler base_edid_delete_handler, details::flowcompatibility_effective_edid_setter effective_edid_setter, details::flowcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate); } } diff --git a/Development/nmos/flowcompatibility_resources.cpp b/Development/nmos/flowcompatibility_resources.cpp index 76ff2af38..b2673adce 100644 --- a/Development/nmos/flowcompatibility_resources.cpp +++ b/Development/nmos/flowcompatibility_resources.cpp @@ -1,7 +1,7 @@ #include "nmos/flowcompatibility_resources.h" -#include -#include "nmos/capabilities.h" +#include +#include "nmos/capabilities.h" // for nmos::fields::constraint_sets #include "nmos/is11_versions.h" #include "nmos/resource.h" @@ -9,13 +9,27 @@ namespace nmos { namespace experimental { + web::json::value make_flowcompatibility_active_constraints_endpoint(const web::json::value& constraint_sets, bool locked) + { + using web::json::value_of; + + auto active_constraint_sets = value_of({ + { nmos::fields::constraint_sets, constraint_sets }, + }); + + return value_of({ + { nmos::fields::active_constraint_sets, active_constraint_sets }, + { nmos::fields::temporarily_locked, locked }, + }); + } + nmos::resource make_flowcompatibility_sender(const nmos::id& id, const std::vector& inputs, const std::vector& param_constraints) { using web::json::value; using web::json::value_of; using web::json::value_from_elements; - std::set parameter_constraints{ + std::unordered_set parameter_constraints{ nmos::caps::meta::label.key, nmos::caps::meta::preference.key, nmos::caps::meta::enabled.key, @@ -38,10 +52,6 @@ namespace nmos { nmos::fields::state, U("Unconstrained") }, }); - auto active_constraint_sets = value_of({ - { nmos::fields::constraint_sets, value::array() }, - }); - auto supported_param_constraints = value_of({ { nmos::fields::parameter_constraints, value_from_elements(parameter_constraints) }, }); @@ -49,7 +59,7 @@ namespace nmos auto data = value_of({ { nmos::fields::id, id }, { nmos::fields::device_id, U("these are not the droids you are looking for") }, - { nmos::fields::active_constraint_sets, active_constraint_sets }, + { nmos::fields::endpoint_active_constraints, make_flowcompatibility_active_constraints_endpoint(value::array()) }, { nmos::fields::inputs, value_from_elements(inputs) }, { nmos::fields::supported_param_constraints, supported_param_constraints }, { nmos::fields::status, status }, diff --git a/Development/nmos/flowcompatibility_resources.h b/Development/nmos/flowcompatibility_resources.h index ef863de66..fb6684867 100644 --- a/Development/nmos/flowcompatibility_resources.h +++ b/Development/nmos/flowcompatibility_resources.h @@ -14,6 +14,8 @@ namespace nmos namespace experimental { + web::json::value make_flowcompatibility_active_constraints_endpoint(const web::json::value& constraint_sets, bool locked = false); + nmos::resource make_flowcompatibility_sender(const nmos::id& id, const std::vector& inputs, const std::vector& param_constraints); nmos::resource make_flowcompatibility_receiver(const nmos::id& id, const std::vector& outputs); diff --git a/Development/nmos/json_fields.h b/Development/nmos/json_fields.h index 4cb75f569..61790d9aa 100644 --- a/Development/nmos/json_fields.h +++ b/Development/nmos/json_fields.h @@ -245,7 +245,7 @@ namespace nmos const web::json::field_as_object edid_properties{ U("edid") }; // for sender - const web::json::field_as_object active_constraints{ U("active_constraints") }; + const web::json::field_as_value endpoint_active_constraints{ U("endpoint_active_constraints") }; // object const web::json::field_as_value active_constraint_sets{ U("active_constraint_sets") }; // object const web::json::field_as_value supported_param_constraints{ U("supported_param_constraints") }; // object const web::json::field_as_array parameter_constraints{ U("parameter_constraints") }; diff --git a/Development/nmos/node_server.cpp b/Development/nmos/node_server.cpp index c675c21d7..61a49747f 100644 --- a/Development/nmos/node_server.cpp +++ b/Development/nmos/node_server.cpp @@ -63,7 +63,7 @@ namespace nmos node_server.api_routers[{ {}, nmos::fields::channelmapping_port(node_model.settings) }].mount({}, nmos::make_channelmapping_api(node_model, node_implementation.validate_map, gate)); // Configure the Flow Compatibility API - node_server.api_routers[{ {}, nmos::fields::flowcompatibility_port(node_model.settings) }].mount({}, nmos::experimental::make_flowcompatibility_api(node_model, node_implementation.base_edid_changed, node_implementation.base_edid_deleted, node_implementation.set_effective_edid, gate)); + node_server.api_routers[{ {}, nmos::fields::flowcompatibility_port(node_model.settings) }].mount({}, nmos::experimental::make_flowcompatibility_api(node_model, node_implementation.base_edid_changed, node_implementation.base_edid_deleted, node_implementation.set_effective_edid, node_implementation.active_constraints_changed, gate)); auto& events_ws_api = node_server.ws_handlers[{ {}, nmos::fields::events_ws_port(node_model.settings) }]; events_ws_api.first = nmos::make_events_ws_api(node_model, events_ws_api.second, gate); diff --git a/Development/nmos/node_server.h b/Development/nmos/node_server.h index 109f5cfc4..13092ad3c 100644 --- a/Development/nmos/node_server.h +++ b/Development/nmos/node_server.h @@ -61,6 +61,7 @@ namespace nmos node_implementation& on_base_edid_changed(nmos::experimental::details::flowcompatibility_base_edid_put_handler base_edid_changed) { this->base_edid_changed = std::move(base_edid_changed); return *this; } node_implementation& on_base_edid_deleted(nmos::experimental::details::flowcompatibility_base_edid_delete_handler base_edid_deleted) { this->base_edid_deleted = std::move(base_edid_deleted); return *this; } node_implementation& on_set_effective_edid(nmos::experimental::details::flowcompatibility_effective_edid_setter set_effective_edid) { this->set_effective_edid = std::move(set_effective_edid); return *this; } + node_implementation& on_active_constraints_changed(nmos::experimental::details::flowcompatibility_active_constraints_put_handler active_constraints_changed) { this->active_constraints_changed = std::move(active_constraints_changed); return *this; } // deprecated, use on_validate_connection_resource_patch node_implementation& on_validate_merged(nmos::details::connection_resource_patch_validator validate_merged) { return on_validate_connection_resource_patch(std::move(validate_merged)); } @@ -94,6 +95,7 @@ namespace nmos nmos::experimental::details::flowcompatibility_base_edid_put_handler base_edid_changed; nmos::experimental::details::flowcompatibility_base_edid_delete_handler base_edid_deleted; nmos::experimental::details::flowcompatibility_effective_edid_setter set_effective_edid; + nmos::experimental::details::flowcompatibility_active_constraints_put_handler active_constraints_changed; }; // Construct a server instance for an NMOS Node, implementing the IS-04 Node API, IS-05 Connection API, IS-07 Events API From 4b8f590ebfe85ee3e28ac7df326d378b9eaf01f9 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Mon, 24 Jan 2022 15:20:59 +0300 Subject: [PATCH 007/109] Implement States of Sender and Receiver --- Development/cmake/NmosCppLibraries.cmake | 9 +- Development/nmos/constraints.cpp | 79 ++++++ Development/nmos/constraints.h | 16 ++ Development/nmos/flowcompatibility_api.cpp | 30 +- .../nmos/flowcompatibility_behaviour.cpp | 268 ++++++++++++++++++ .../nmos/flowcompatibility_behaviour.h | 19 ++ .../nmos/flowcompatibility_resources.cpp | 31 +- Development/nmos/flowcompatibility_state.h | 42 +++ Development/nmos/flowcompatibility_utils.cpp | 25 ++ Development/nmos/flowcompatibility_utils.h | 11 + Development/nmos/json_fields.h | 4 + Development/nmos/node_resources.cpp | 2 +- Development/nmos/node_server.cpp | 4 +- Development/nmos/sdp_utils.cpp | 36 --- Development/nmos/sdp_utils.h | 53 +++- 15 files changed, 557 insertions(+), 72 deletions(-) create mode 100644 Development/nmos/constraints.cpp create mode 100644 Development/nmos/constraints.h create mode 100644 Development/nmos/flowcompatibility_behaviour.cpp create mode 100644 Development/nmos/flowcompatibility_behaviour.h create mode 100644 Development/nmos/flowcompatibility_state.h create mode 100644 Development/nmos/flowcompatibility_utils.cpp create mode 100644 Development/nmos/flowcompatibility_utils.h diff --git a/Development/cmake/NmosCppLibraries.cmake b/Development/cmake/NmosCppLibraries.cmake index c852c7d41..95634a558 100644 --- a/Development/cmake/NmosCppLibraries.cmake +++ b/Development/cmake/NmosCppLibraries.cmake @@ -906,7 +906,6 @@ set(NMOS_CPP_NMOS_SOURCES nmos/certificate_handlers.cpp nmos/channelmapping_activation.cpp nmos/channelmapping_api.cpp - nmos/flowcompatibility_resources.cpp nmos/channelmapping_resources.cpp nmos/channels.cpp nmos/client_utils.cpp @@ -915,6 +914,7 @@ set(NMOS_CPP_NMOS_SOURCES nmos/connection_api.cpp nmos/connection_events_activation.cpp nmos/connection_resources.cpp + nmos/constraints.cpp nmos/did_sdid.cpp nmos/events_api.cpp nmos/events_resources.cpp @@ -922,6 +922,9 @@ set(NMOS_CPP_NMOS_SOURCES nmos/events_ws_client.cpp nmos/filesystem_route.cpp nmos/flowcompatibility_api.cpp + nmos/flowcompatibility_behaviour.cpp + nmos/flowcompatibility_resources.cpp + nmos/flowcompatibility_utils.cpp nmos/group_hint.cpp nmos/id.cpp nmos/lldp_handler.cpp @@ -987,6 +990,7 @@ set(NMOS_CPP_NMOS_HEADERS nmos/connection_api.h nmos/connection_events_activation.h nmos/connection_resources.h + nmos/constraints.h nmos/device_type.h nmos/did_sdid.h nmos/event_type.h @@ -996,7 +1000,9 @@ set(NMOS_CPP_NMOS_HEADERS nmos/events_ws_client.h nmos/filesystem_route.h nmos/flowcompatibility_api.h + nmos/flowcompatibility_behaviour.h nmos/flowcompatibility_resources.h + nmos/flowcompatibility_utils.h nmos/format.h nmos/group_hint.h nmos/health.h @@ -1048,6 +1054,7 @@ set(NMOS_CPP_NMOS_HEADERS nmos/resources.h nmos/schemas_api.h nmos/sdp_utils.h + nmos/flowcompatibility_state.h nmos/server.h nmos/server_utils.h nmos/settings.h diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp new file mode 100644 index 000000000..222f33e16 --- /dev/null +++ b/Development/nmos/constraints.cpp @@ -0,0 +1,79 @@ +#include +#include "nmos/capabilities.h" +#include "nmos/constraints.h" +#include "nmos/json_fields.h" +#include "nmos/sdp_utils.h" + +namespace nmos +{ + namespace experimental + { + bool match_source_parameters_constraint_set(const web::json::value& source, const web::json::value& constraint_set) + { + using web::json::value; + + if (!nmos::caps::meta::enabled(constraint_set)) return true; + + // NMOS Parameter Registers - Capabilities register + // See https://github.com/AMWA-TV/nmos-parameter-registers/blob/main/capabilities/README.md + static const std::map> match_constraints + { + // Audio Constraints + + { nmos::caps::format::channel_count, [](const web::json::value& source, const value& con) { return nmos::match_integer_constraint((uint32_t)nmos::fields::channels(source).size(), con); } } + }; + + const auto& constraints = constraint_set.as_object(); + return constraints.end() == std::find_if(constraints.begin(), constraints.end(), [&](const std::pair& constraint) + { + const auto& found = match_constraints.find(constraint.first); + return match_constraints.end() != found && !found->second(source, constraint.second); + }); + } + + bool match_flow_parameters_constraint_set(const web::json::value& flow, const web::json::value& constraint_set) + { + using web::json::value; + + if (!nmos::caps::meta::enabled(constraint_set)) return true; + + // NMOS Parameter Registers - Capabilities register + // See https://github.com/AMWA-TV/nmos-parameter-registers/blob/main/capabilities/README.md + static const std::map> match_constraints + { + // General Constraints + + { nmos::caps::format::media_type, [](const web::json::value& flow, const value& con) { return nmos::match_string_constraint(flow.at(U("media_type")).as_string(), con); } }, + { nmos::caps::format::grain_rate, [](const web::json::value& flow, const value& con) { + auto grain_rate = nmos::rational(nmos::fields::numerator(nmos::fields::grain_rate(flow)), nmos::fields::denominator(nmos::fields::grain_rate(flow))); + return nmos::match_rational_constraint(grain_rate, con); } + }, + + // Video Constraints + + { nmos::caps::format::frame_height, [](const web::json::value& flow, const value& con) { return nmos::match_integer_constraint(nmos::fields::frame_height(flow), con); } }, + { nmos::caps::format::frame_width, [](const web::json::value& flow, const value& con) { return nmos::match_integer_constraint(nmos::fields::frame_width(flow), con); } }, + { nmos::caps::format::color_sampling, [](const web::json::value& flow, const value& con) { return nmos::match_string_constraint(nmos::details::make_sampling(nmos::fields::components(flow)).name, con); } }, + { nmos::caps::format::interlace_mode, [](const web::json::value& flow, const value& con) { return nmos::match_string_constraint(nmos::fields::interlace_mode(flow), con); } }, + { nmos::caps::format::colorspace, [](const web::json::value& flow, const value& con) { return nmos::match_string_constraint(nmos::fields::colorspace(flow), con); } }, + { nmos::caps::format::transfer_characteristic, [](const web::json::value& flow, const value& con) { return nmos::match_string_constraint(nmos::fields::transfer_characteristic(flow), con); } }, + { nmos::caps::format::component_depth, [](const web::json::value& flow, const value& con) { return nmos::match_integer_constraint(nmos::fields::bit_depth(nmos::fields::components(flow).at(0)), con); } }, + + // Audio Constraints + + { nmos::caps::format::sample_rate, [](const web::json::value& flow, const value& con) { + auto sample_rate = nmos::rational(nmos::fields::numerator(nmos::fields::sample_rate(flow)), nmos::fields::denominator(nmos::fields::sample_rate(flow))); + return nmos::match_rational_constraint(sample_rate, con); } + }, + { nmos::caps::format::sample_depth, [](const web::json::value& flow, const value& con) { return nmos::match_integer_constraint(nmos::fields::bit_depth(flow), con); } }, + }; + + const auto& constraints = constraint_set.as_object(); + return constraints.end() == std::find_if(constraints.begin(), constraints.end(), [&](const std::pair& constraint) + { + const auto& found = match_constraints.find(constraint.first); + return match_constraints.end() != found && !found->second(flow, constraint.second); + }); + } + } +} diff --git a/Development/nmos/constraints.h b/Development/nmos/constraints.h new file mode 100644 index 000000000..e1763c8a5 --- /dev/null +++ b/Development/nmos/constraints.h @@ -0,0 +1,16 @@ +namespace web +{ + namespace json + { + class value; + } +} + +namespace nmos +{ + namespace experimental + { + bool match_source_parameters_constraint_set(const web::json::value& source, const web::json::value& constraint_set); + bool match_flow_parameters_constraint_set(const web::json::value& flow, const web::json::value& constraint_set); + } +} diff --git a/Development/nmos/flowcompatibility_api.cpp b/Development/nmos/flowcompatibility_api.cpp index 582479515..dbdd3f5ee 100644 --- a/Development/nmos/flowcompatibility_api.cpp +++ b/Development/nmos/flowcompatibility_api.cpp @@ -1,4 +1,5 @@ #include "nmos/flowcompatibility_api.h" +#include "nmos/flowcompatibility_utils.h" #include #include @@ -84,24 +85,6 @@ namespace nmos return true; } - // it's expected that read lock is already catched for the model - void update_version(nmos::resources& resources, const nmos::id& resource_id, const utility::string_t& new_version) - { - modify_resource(resources, resource_id, [&new_version](nmos::resource& resource) - { - resource.data[nmos::fields::version] = web::json::value::string(new_version); - }); - } - - // it's expected that read lock is already catched for the model - void update_version(nmos::resources& resources, const web::json::array& resource_ids, const utility::string_t& new_version) - { - for (const auto& resource_id : resource_ids) - { - update_version(resources, resource_id.as_string(), new_version); - } - } - // it's expected that write lock is already catched for the model and an input with the resource_id exists void update_effective_edid(nmos::node_model& model, const flowcompatibility_effective_edid_setter& effective_edid_setter, const utility::string_t resource_id) { @@ -174,7 +157,7 @@ namespace nmos sender.data[nmos::fields::version] = web::json::value::string(updated_timestamp); }); - details::update_version(model.node_resources, sender_id, updated_timestamp); + update_version(model.node_resources, sender_id, updated_timestamp); if (effective_edid_setter) { @@ -400,7 +383,7 @@ namespace nmos throw std::logic_error("matching IS-04 resource not found"); } - set_reply(res, status_codes::OK, nmos::fields::status(resource->data)); + set_reply(res, status_codes::OK, nmos::fields::status(nmos::fields::endpoint_status(resource->data))); } else if (nmos::details::is_erased_resource(resources, id_type)) { @@ -501,6 +484,7 @@ namespace nmos { auto data = resource->data; + // EDID endpoints hold information about EDID binary and they shouldn't be included in the response if (nmos::types::input == nmos::type_from_resourceType(resourceType)) { if (!nmos::fields::endpoint_base_edid(resource->data).is_null()) @@ -665,7 +649,7 @@ namespace nmos input.data[nmos::fields::version] = web::json::value::string(updated_timestamp); }); - details::update_version(model.node_resources, nmos::fields::senders(resource->data), updated_timestamp); + update_version(model.node_resources, nmos::fields::senders(resource->data), updated_timestamp); if (effective_edid_setter) { @@ -746,7 +730,7 @@ namespace nmos input.data[nmos::fields::version] = web::json::value::string(updated_timestamp); }); - details::update_version(model.node_resources, nmos::fields::senders(resource->data), updated_timestamp); + update_version(model.node_resources, nmos::fields::senders(resource->data), updated_timestamp); if (effective_edid_setter) { @@ -823,7 +807,7 @@ namespace nmos if (can_adhere) { - details::set_active_constraints(model, resourceId, data, effective_edid_setter); + details::set_active_constraints(model, resourceId, nmos::fields::constraint_sets(data), effective_edid_setter); set_reply(res, status_codes::OK, data); } else diff --git a/Development/nmos/flowcompatibility_behaviour.cpp b/Development/nmos/flowcompatibility_behaviour.cpp new file mode 100644 index 000000000..fb023f10b --- /dev/null +++ b/Development/nmos/flowcompatibility_behaviour.cpp @@ -0,0 +1,268 @@ +#include "nmos/flowcompatibility_behaviour.h" +#include "nmos/flowcompatibility_state.h" +#include "nmos/flowcompatibility_utils.h" + +#include +#include +#include +#include "nmos/capabilities.h" // for constraint_sets +#include "nmos/constraints.h" +#include "nmos/id.h" +#include "nmos/media_type.h" +#include "nmos/model.h" +#include "nmos/resources.h" +#include "nmos/sdp_utils.h" +#include "nmos/slog.h" +#include "sdp/sdp.h" + +namespace nmos +{ + namespace experimental + { + utility::string_t get_sdp_data(const web::json::value& transport_file) + { + // "'data' and 'type' must both be strings or both be null" + // See https://specs.amwa.tv/is-05/releases/v1.0.2/APIs/schemas/with-refs/v1.0-receiver-response-schema.html + // and https://specs.amwa.tv/is-05/releases/v1.1.0/APIs/schemas/with-refs/receiver-transport-file.html + + if (!transport_file.has_field(nmos::fields::data)) throw std::logic_error("data is required"); + + auto& transport_data = transport_file.at(nmos::fields::data); + if (transport_data.is_null()) throw std::logic_error("data is required"); + + if (!transport_file.has_field(nmos::fields::type)) throw std::logic_error("type is required"); + + auto& transport_type = transport_file.at(nmos::fields::type); + if (transport_type.is_null() || transport_type.as_string().empty()) throw std::logic_error("type is required"); + + if (nmos::media_types::application_sdp.name != transport_type.as_string()) throw std::logic_error("transport file type is not SDP"); + + return transport_data.as_string(); + } + + std::vector get_resources_ids(const nmos::resources& resources, const nmos::type& type) + { + return boost::copy_range>(resources | boost::adaptors::filtered([&type] (const nmos::resource& resource) + { + return type == resource.type; + }) | boost::adaptors::transformed([](const nmos::resource& resource) + { + return std::vector::value_type{ resource.id }; + })); + } + + nmos::sender_state validate_sender_resources(const web::json::value& transport_file, const web::json::value& flow, const web::json::value& source, const web::json::array& constraint_sets) + { + nmos::sender_state sender_state; + + if (!web::json::empty(constraint_sets)) + { + bool constrained = true; + + auto source_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return match_source_parameters_constraint_set(source, constraint_set); }); + auto flow_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return match_flow_parameters_constraint_set(flow, constraint_set); }); + + constrained = constraint_sets.end() != source_found && constraint_sets.end() != flow_found; + + if (!transport_file.is_null() && !transport_file.as_object().empty()) + { + utility::string_t sdp_data = get_sdp_data(transport_file); + const auto session_description = sdp::parse_session_description(utility::us2s(sdp_data)); + auto sdp_params = nmos::parse_session_description(session_description).first; + + const auto format_params = details::get_format_parameters(sdp_params); + const auto sdp_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return nmos::details::match_sdp_parameters_constraint_set(details::format_constraints, sdp_params, format_params, constraint_set); }); + + constrained = constrained && constraint_sets.end() != sdp_found; + } + + sender_state = constrained ? nmos::sender_states::constrained : nmos::sender_states::active_constraints_violation; + } + else + { + sender_state = nmos::sender_states::unconstrained; + } + + return sender_state; + } + + nmos::receiver_state validate_receiver_resources(const web::json::value& transport_file, const web::json::value& receiver) + { + nmos::receiver_state receiver_state; + + if (!transport_file.is_null() && !transport_file.as_object().empty()) + { + utility::string_t sdp_data = get_sdp_data(transport_file); + const auto session_description = sdp::parse_session_description(utility::us2s(sdp_data)); + auto sdp_params = nmos::parse_session_description(session_description).first; + + receiver_state = nmos::receiver_states::ok; + + try + { + validate_sdp_parameters(receiver, sdp_params); + } + catch (const std::runtime_error& e) + { + receiver_state = nmos::receiver_states::receiver_capabilities_violation; + } + } + else + { + receiver_state = nmos::receiver_states::no_transport_file; + } + + return receiver_state; + } + + void flowcompatibility_behaviour_thread(nmos::node_model& model, slog::base_gate& gate) + { + using web::json::value; + using web::json::value_of; + + auto lock = model.write_lock(); // in order to update the resources + auto& node_resources = model.node_resources; + auto& connection_resources = model.connection_resources; + auto& flowcompatibility_resources = model.flowcompatibility_resources; + + auto most_recent_update = nmos::tai_min(); + + for (;;) + { + model.wait(lock, [&] { return model.shutdown || most_recent_update < nmos::most_recent_update(node_resources); }); + if (model.shutdown) break; + + auto flowcompatibility_senders_ids = get_resources_ids(flowcompatibility_resources, nmos::types::sender); + auto flowcompatibility_receivers_ids = get_resources_ids(flowcompatibility_resources, nmos::types::receiver); + + // find IS-11 recently updated Senders and Senders with recently updated Flow or Source + for (const nmos::id& sender_id : flowcompatibility_senders_ids) + { + try + { + bool updated = false; + + const std::pair sender_id_type{ sender_id, nmos::types::sender }; + auto sender = find_resource(node_resources, sender_id_type); + if (node_resources.end() == sender) throw std::logic_error("Matching IS-04 sender not found"); + + const std::pair flow_id_type{ nmos::fields::flow_id(sender->data).as_string(), nmos::types::flow }; + auto flow = find_resource(node_resources, flow_id_type); + if (node_resources.end() == flow) throw std::logic_error("Matching IS-04 flow not found"); + + const std::pair source_id_type{ nmos::fields::source_id(flow->data), nmos::types::source }; + auto source = find_resource(node_resources, source_id_type); + if (node_resources.end() == source) throw std::logic_error("Matching IS-04 source not found"); + + updated = most_recent_update < sender->updated || + most_recent_update < flow->updated || + most_recent_update < source->updated; + + if (updated) + { + slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " or its Flow or Source has been updated recently and Sender State is being updated as well"; + + const std::pair flowcompatibility_sender_id_type{ sender_id, nmos::types::sender }; + auto flowcompatibility_sender = find_resource(flowcompatibility_resources, flowcompatibility_sender_id_type); + if (flowcompatibility_resources.end() == flowcompatibility_sender) throw std::logic_error("Matching IS-11 sender not found"); + + nmos::signal_state signal_state(nmos::fields::signal_state(nmos::fields::endpoint_status(flowcompatibility_sender->data))); + nmos::sender_state sender_state(signal_state.name); + + if (signal_state == nmos::signal_states::signal_is_present) + { + const auto& constraint_sets = nmos::fields::constraint_sets(nmos::fields::active_constraint_sets(nmos::fields::endpoint_active_constraints(flowcompatibility_sender->data))).as_array(); + + const std::pair connection_sender_id_type{ sender_id, nmos::types::sender }; + auto connection_sender = find_resource(connection_resources, connection_sender_id_type); + if (connection_resources.end() == connection_sender) throw std::logic_error("Matching IS-05 sender not found"); + + auto& transport_file = nmos::fields::endpoint_transportfile(connection_sender->data); + + slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " is being validated with its Flow, Source and transport file"; + sender_state = validate_sender_resources(transport_file, flow->data, source->data, constraint_sets); + } + + if (nmos::fields::state(nmos::fields::status(nmos::fields::endpoint_status(flowcompatibility_sender->data))) != sender_state.name) + { + utility::string_t updated_timestamp; + + modify_resource(flowcompatibility_resources, sender_id, [&sender_state, &updated_timestamp, &gate](nmos::resource& sender) + { + nmos::fields::status(nmos::fields::endpoint_status(sender.data))[nmos::fields::state] = web::json::value::string(sender_state.name); + + updated_timestamp = nmos::make_version(); + sender.data[nmos::fields::version] = web::json::value::string(updated_timestamp); + }); + + update_version(node_resources, sender_id, updated_timestamp); + } + } + } + catch (const std::exception& e) + { + slog::log(gate, SLOG_FLF) << "Updating sender status for " << sender_id << " raised exception: " << e.what(); + continue; + } + } + + // find IS-11 Receivers with recently updated "caps" to check whether the active transport file still satisfies them + for (const nmos::id& receiver_id : flowcompatibility_receivers_ids) + { + try + { + bool updated = false; + + const std::pair receiver_id_type{ receiver_id, nmos::types::receiver }; + auto receiver = find_resource(node_resources, receiver_id_type); + if (node_resources.end() == receiver) throw std::logic_error("Matching IS-04 receiver not found"); + + updated = most_recent_update < nmos::fields::version(nmos::fields::caps(receiver->data)); + + if (updated) + { + slog::log(gate, SLOG_FLF) << "Receiver " << receiver_id << " has been updated recently and Receiver State is being updated as well"; + + const std::pair flowcompatibility_receiver_id_type{ receiver_id, nmos::types::receiver }; + auto flowcompatibility_receiver = find_resource(flowcompatibility_resources, flowcompatibility_receiver_id_type); + if (flowcompatibility_resources.end() == flowcompatibility_receiver) throw std::logic_error("Matching IS-11 receiver not found"); + + nmos::receiver_state receiver_state(nmos::receiver_states::no_transport_file); + + const std::pair connection_receiver_id_type{ receiver_id, nmos::types::receiver }; + auto connection_receiver = find_resource(connection_resources, connection_receiver_id_type); + if (connection_resources.end() == connection_receiver) throw std::logic_error("Matching IS-05 receiver not found"); + + auto& transport_file = nmos::fields::transport_file(nmos::fields::endpoint_staged(connection_receiver->data)); + + receiver_state = validate_receiver_resources(transport_file, receiver->data); + + if (nmos::fields::state(nmos::fields::status(nmos::fields::endpoint_status(flowcompatibility_receiver->data))) != receiver_state.name) + { + utility::string_t updated_timestamp; + + modify_resource(flowcompatibility_resources, receiver_id, [&receiver_state, &updated_timestamp, &gate](nmos::resource& receiver) + { + nmos::fields::status(nmos::fields::endpoint_status(receiver.data))[nmos::fields::state] = web::json::value::string(receiver_state.name); + + updated_timestamp = nmos::make_version(); + receiver.data[nmos::fields::version] = web::json::value::string(updated_timestamp); + }); + + update_version(node_resources, receiver_id, updated_timestamp); + } + } + } + catch (const std::exception& e) + { + slog::log(gate, SLOG_FLF) << "Updating receiver status for " << receiver_id << " raised exception: " << e.what(); + continue; + } + } + + model.notify(); + most_recent_update = nmos::most_recent_update(node_resources); + } + } + } +} diff --git a/Development/nmos/flowcompatibility_behaviour.h b/Development/nmos/flowcompatibility_behaviour.h new file mode 100644 index 000000000..e2a4d0745 --- /dev/null +++ b/Development/nmos/flowcompatibility_behaviour.h @@ -0,0 +1,19 @@ +#ifndef NMOS_FLOWCOMPATIBILITY_BEHAVIOUR_H +#define NMOS_FLOWCOMPATIBILITY_BEHAVIOUR_H + +namespace slog +{ + class base_gate; +} + +namespace nmos +{ + struct node_model; + + namespace experimental + { + void flowcompatibility_behaviour_thread(nmos::node_model& model, slog::base_gate& gate); + } +} + +#endif diff --git a/Development/nmos/flowcompatibility_resources.cpp b/Development/nmos/flowcompatibility_resources.cpp index b2673adce..bb4e0a5d9 100644 --- a/Development/nmos/flowcompatibility_resources.cpp +++ b/Development/nmos/flowcompatibility_resources.cpp @@ -4,6 +4,7 @@ #include "nmos/capabilities.h" // for nmos::fields::constraint_sets #include "nmos/is11_versions.h" #include "nmos/resource.h" +#include "nmos/flowcompatibility_state.h" namespace nmos { @@ -23,6 +24,20 @@ namespace nmos }); } + web::json::value make_flowcompatibility_sender_status_endpoint(nmos::sender_state sender_state, nmos::signal_state signal_state) + { + using web::json::value_of; + + auto sender_status = value_of({ + { nmos::fields::state, sender_state.name }, + }); + + return value_of({ + { nmos::fields::status, sender_status }, + { nmos::fields::signal_state, signal_state.name }, + }); + } + nmos::resource make_flowcompatibility_sender(const nmos::id& id, const std::vector& inputs, const std::vector& param_constraints) { using web::json::value; @@ -48,10 +63,6 @@ namespace nmos parameter_constraints.insert(param_constraints.begin(), param_constraints.end()); - auto status = value_of({ - { nmos::fields::state, U("Unconstrained") }, - }); - auto supported_param_constraints = value_of({ { nmos::fields::parameter_constraints, value_from_elements(parameter_constraints) }, }); @@ -62,7 +73,7 @@ namespace nmos { nmos::fields::endpoint_active_constraints, make_flowcompatibility_active_constraints_endpoint(value::array()) }, { nmos::fields::inputs, value_from_elements(inputs) }, { nmos::fields::supported_param_constraints, supported_param_constraints }, - { nmos::fields::status, status }, + { nmos::fields::endpoint_status, make_flowcompatibility_sender_status_endpoint(nmos::sender_states::unconstrained, nmos::signal_states::signal_is_present) }, }); return{ is11_versions::v1_0, types::sender, std::move(data), id, false }; @@ -73,15 +84,19 @@ namespace nmos using web::json::value_of; using web::json::value_from_elements; - auto status = value_of({ - { nmos::fields::state, U("OK") }, + auto receiver_status = value_of({ + { nmos::fields::state, nmos::receiver_states::no_transport_file.name }, + }); + + auto endpoint_status = value_of({ + { nmos::fields::status, receiver_status }, }); auto data = value_of({ { nmos::fields::id, id }, { nmos::fields::device_id, U("these are not the droids you are looking for") }, { nmos::fields::outputs, value_from_elements(outputs) }, - { nmos::fields::status, status }, + { nmos::fields::endpoint_status, endpoint_status }, }); return{ is11_versions::v1_0, types::receiver, std::move(data), id, false }; diff --git a/Development/nmos/flowcompatibility_state.h b/Development/nmos/flowcompatibility_state.h new file mode 100644 index 000000000..8d12560da --- /dev/null +++ b/Development/nmos/flowcompatibility_state.h @@ -0,0 +1,42 @@ +#ifndef NMOS_SENDER_STATE_H +#define NMOS_SENDER_STATE_H + +#include "nmos/string_enum.h" + +namespace nmos +{ + // Flow Compatibility Sender states + // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#state-of-sender + // and https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/sender-status.html + DEFINE_STRING_ENUM(sender_state) + namespace sender_states + { + const sender_state no_signal{ U("No Signal") }; + const sender_state awaiting_signal{ U("Awaiting Signal") }; + const sender_state unconstrained{ U("Unconstrained") }; + const sender_state constrained{ U("Constrained") }; + const sender_state active_constraints_violation{ U("Active Constraints Violation") }; + } + + // State of the signal coming into a Sender from its Inputs. It is used to set Sender Status properly. + DEFINE_STRING_ENUM(signal_state) + namespace signal_states + { + const signal_state no_signal{ U("No Signal") }; + const signal_state awaiting_signal{ U("Awaiting Signal") }; + const signal_state signal_is_present{ U("Signal is Present") }; + } + + // Flow Compatibility Receiver states + // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#state-of-receiver + // and https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/receiver-status.html + DEFINE_STRING_ENUM(receiver_state) + namespace receiver_states + { + const receiver_state no_transport_file{ U("No Transport File") }; + const receiver_state ok{ U("OK") }; + const receiver_state receiver_capabilities_violation{ U("Receiver Capabilities Violation") }; + } +} + +#endif diff --git a/Development/nmos/flowcompatibility_utils.cpp b/Development/nmos/flowcompatibility_utils.cpp new file mode 100644 index 000000000..3354a0c5a --- /dev/null +++ b/Development/nmos/flowcompatibility_utils.cpp @@ -0,0 +1,25 @@ +#include "flowcompatibility_utils.h" + +namespace nmos +{ + namespace experimental + { + // it's expected that write lock is already catched for the model + void update_version(nmos::resources& resources, const nmos::id& resource_id, const utility::string_t& new_version) + { + modify_resource(resources, resource_id, [&new_version](nmos::resource& resource) + { + resource.data[nmos::fields::version] = web::json::value::string(new_version); + }); + } + + // it's expected that write lock is already catched for the model + void update_version(nmos::resources& resources, const web::json::array& resource_ids, const utility::string_t& new_version) + { + for (const auto& resource_id : resource_ids) + { + update_version(resources, resource_id.as_string(), new_version); + } + } + } +} diff --git a/Development/nmos/flowcompatibility_utils.h b/Development/nmos/flowcompatibility_utils.h new file mode 100644 index 000000000..fedcb8ea3 --- /dev/null +++ b/Development/nmos/flowcompatibility_utils.h @@ -0,0 +1,11 @@ +#include "nmos/id.h" +#include "nmos/resources.h" + +namespace nmos +{ + namespace experimental + { + void update_version(nmos::resources& resources, const nmos::id& resource_id, const utility::string_t& new_version); + void update_version(nmos::resources& resources, const web::json::array& resource_ids, const utility::string_t& new_version); + } +} diff --git a/Development/nmos/json_fields.h b/Development/nmos/json_fields.h index 61790d9aa..49044e537 100644 --- a/Development/nmos/json_fields.h +++ b/Development/nmos/json_fields.h @@ -249,6 +249,10 @@ namespace nmos const web::json::field_as_value active_constraint_sets{ U("active_constraint_sets") }; // object const web::json::field_as_value supported_param_constraints{ U("supported_param_constraints") }; // object const web::json::field_as_array parameter_constraints{ U("parameter_constraints") }; + + // for status + const web::json::field_as_value endpoint_status{ U("endpoint_status") }; // object + const web::json::field_as_string signal_state{ U("signal_state") }; const web::json::field_as_value status{ U("status") }; // object const web::json::field_as_string state{ U("state") }; diff --git a/Development/nmos/node_resources.cpp b/Development/nmos/node_resources.cpp index 0cd9530c7..93545ccd6 100644 --- a/Development/nmos/node_resources.cpp +++ b/Development/nmos/node_resources.cpp @@ -115,7 +115,7 @@ namespace nmos .set_scheme(nmos::http_scheme(settings)) .set_port(nmos::fields::flowcompatibility_port(settings)) .set_path(U("/x-nmos/flowcompatibility/") + make_api_version(version)); - auto type = U("urn:x-nmos:control:fcm/") + make_api_version(version); + auto type = U("urn:x-nmos:control:fc-ctrl/") + make_api_version(version); for (const auto& host : hosts) { diff --git a/Development/nmos/node_server.cpp b/Development/nmos/node_server.cpp index 61a49747f..a2461e70c 100644 --- a/Development/nmos/node_server.cpp +++ b/Development/nmos/node_server.cpp @@ -6,6 +6,7 @@ #include "nmos/events_api.h" #include "nmos/events_ws_api.h" #include "nmos/flowcompatibility_api.h" +#include "nmos/flowcompatibility_behaviour.h" #include "nmos/logging_api.h" #include "nmos/manifest_api.h" #include "nmos/model.h" @@ -110,7 +111,8 @@ namespace nmos [&] { nmos::send_events_ws_messages_thread(events_ws_listener, node_model, events_ws_api.second, gate); }, [&] { nmos::erase_expired_events_resources_thread(node_model, gate); }, [&, resolve_auto, set_transportfile, connection_activated] { nmos::connection_activation_thread(node_model, resolve_auto, set_transportfile, connection_activated, gate); }, - [&, channelmapping_activated] { nmos::channelmapping_activation_thread(node_model, channelmapping_activated, gate); } + [&, channelmapping_activated] { nmos::channelmapping_activation_thread(node_model, channelmapping_activated, gate); }, + [&] { nmos::experimental::flowcompatibility_behaviour_thread(node_model, gate); } }); auto system_changed = node_implementation.system_changed; diff --git a/Development/nmos/sdp_utils.cpp b/Development/nmos/sdp_utils.cpp index 2bc863982..5c4411360 100644 --- a/Development/nmos/sdp_utils.cpp +++ b/Development/nmos/sdp_utils.cpp @@ -1530,42 +1530,6 @@ namespace nmos else return{}; } - // NMOS Parameter Registers - Capabilities register - // See https://specs.amwa.tv/nmos-parameter-registers/branches/main/capabilities/ -#define CAPS_ARGS const sdp_parameters& sdp, const format_parameters& format, const web::json::value& con - static const std::map> format_constraints - { - // General Constraints - - { nmos::caps::format::media_type, [](CAPS_ARGS) { return nmos::match_string_constraint(get_media_type(sdp).name, con); } }, - // hm, how best to match (rational) nmos::caps::format::grain_rate against (double) framerate e.g. for video/SMPTE2022-6? - // is 23.976 a match for 24000/1001? how about 23.98, or 23.9? or even 23?! - { nmos::caps::format::grain_rate, [](CAPS_ARGS) { auto exactframerate = get_exactframerate(&format); return nmos::rational{} == exactframerate || nmos::match_rational_constraint(exactframerate, con); } }, - - // Video Constraints - - { nmos::caps::format::frame_height, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_integer_constraint(video->height, con); } }, - { nmos::caps::format::frame_width, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_integer_constraint(video->width, con); } }, - { nmos::caps::format::color_sampling, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_string_constraint(video->sampling.name, con); } }, - { nmos::caps::format::interlace_mode, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::details::match_interlace_mode_constraint(video->interlace, video->segmented, con); } }, - { nmos::caps::format::colorspace, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_string_constraint(video->colorimetry.name, con); } }, - { nmos::caps::format::transfer_characteristic, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_string_constraint(!video->tcs.empty() ? video->tcs.name : sdp::transfer_characteristic_systems::SDR.name, con); } }, - { nmos::caps::format::component_depth, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_integer_constraint(video->depth, con); } }, - - // Audio Constraints - - { nmos::caps::format::channel_count, [](CAPS_ARGS) { auto audio = get_audio(&format); return audio && nmos::match_integer_constraint(audio->channel_count, con); } }, - { nmos::caps::format::sample_rate, [](CAPS_ARGS) { auto audio = get_audio(&format); return audio && nmos::match_rational_constraint(audio->sample_rate, con); } }, - { nmos::caps::format::sample_depth, [](CAPS_ARGS) { auto audio = get_audio(&format); return audio && nmos::match_integer_constraint(audio->bit_depth, con); } }, - - // Transport Constraints - - { nmos::caps::transport::packet_time, [](CAPS_ARGS) { return 0 == sdp.packet_time || nmos::match_number_constraint(sdp.packet_time, con); } }, - { nmos::caps::transport::max_packet_time, [](CAPS_ARGS) { return 0 == sdp.max_packet_time || nmos::match_number_constraint(sdp.max_packet_time, con); } }, - { nmos::caps::transport::st2110_21_sender_type, [](CAPS_ARGS) { if (auto video = get_video(&format)) return nmos::match_string_constraint(video->tp.name, con); else if (auto mux = get_mux(&format)) return nmos::match_string_constraint(mux->tp.name, con); else return false; } } - }; -#undef CAPS_ARGS - // Check the specified SDP parameters and format-specific parameters against the specified constraint set // using the specified parameter constraint functions bool match_sdp_parameters_constraint_set(const sdp_parameter_constraints& constraints, const sdp_parameters& sdp_params, const format_parameters& format_params, const web::json::value& constraint_set_) diff --git a/Development/nmos/sdp_utils.h b/Development/nmos/sdp_utils.h index cb03c7bde..9e192111b 100644 --- a/Development/nmos/sdp_utils.h +++ b/Development/nmos/sdp_utils.h @@ -9,14 +9,16 @@ #include "bst/optional.h" #include "sdp/json.h" #include "sdp/ntp.h" +#include "nmos/capabilities.h" #include "nmos/did_sdid.h" +#include "nmos/interlace_mode.h" +#include "nmos/media_type.h" #include "nmos/rational.h" #include "nmos/vpid_code.h" namespace nmos { struct format; - struct media_type; struct sdp_parameters; // defined below @@ -190,7 +192,7 @@ namespace nmos // For now, only the default payload format is covered. //std::vector> alternative_rtpmap_fmtp; - + // Timestamp Reference Clock Source Signalling ("a=ts-refclk:") // See https://tools.ietf.org/html/rfc7273#section-4 struct ts_refclk_t @@ -569,6 +571,8 @@ namespace nmos : sdp_parameters(make_sdp_parameters(session_name, mux, payload_type, media_stream_ids, ts_refclk)) {} + media_type get_media_type(const sdp_parameters& sdp_params); + // Helper functions for implementing format-specific functions namespace details { @@ -601,12 +605,21 @@ namespace nmos // e.g. can hold a video_raw_parameters, an audio_L_parameters, etc. typedef bst::any format_parameters; + format_parameters get_format_parameters(const sdp_parameters& sdp_params); + template inline const FormatParameters* get(const format_parameters* any) { return bst::any_cast(any); } + // for a little brevity, cf. sdp_parameters member type names + const video_raw_parameters* get_video(const format_parameters* format); + const audio_L_parameters* get_audio(const format_parameters* format); + const video_smpte291_parameters* get_data(const format_parameters* format); + const video_SMPTE2022_6_parameters* get_mux(const format_parameters* format); + nmos::rational get_exactframerate(const format_parameters* format); + // a function to check the specified SDP parameters and format-specific parameters // against the specified parameter constraint value, see nmos/capabilities.h typedef std::function sdp_parameter_constraint; @@ -660,6 +673,42 @@ namespace nmos // "Alternatively, payload types may be set by other means in accordance with RFC 3550." // See SMPTE ST 2022-6:2012 Section 6.3 RTP/UDP/IP Header const uint64_t payload_type_mux_default = 98; + + // NMOS Parameter Registers - Capabilities register + // See https://specs.amwa.tv/nmos-parameter-registers/branches/main/capabilities/ +#define CAPS_ARGS const sdp_parameters& sdp, const format_parameters& format, const web::json::value& con + const std::map> format_constraints + { + // General Constraints + + { nmos::caps::format::media_type, [](CAPS_ARGS) { return nmos::match_string_constraint(get_media_type(sdp).name, con); } }, + // hm, how best to match (rational) nmos::caps::format::grain_rate against (double) framerate e.g. for video/SMPTE2022-6? + // is 23.976 a match for 24000/1001? how about 23.98, or 23.9? or even 23?! + { nmos::caps::format::grain_rate, [](CAPS_ARGS) { auto exactframerate = get_exactframerate(&format); return nmos::rational{} == exactframerate || nmos::match_rational_constraint(exactframerate, con); } }, + + // Video Constraints + + { nmos::caps::format::frame_height, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_integer_constraint(video->height, con); } }, + { nmos::caps::format::frame_width, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_integer_constraint(video->width, con); } }, + { nmos::caps::format::color_sampling, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_string_constraint(video->sampling.name, con); } }, + { nmos::caps::format::interlace_mode, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::details::match_interlace_mode_constraint(video->interlace, video->segmented, con); } }, + { nmos::caps::format::colorspace, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_string_constraint(video->colorimetry.name, con); } }, + { nmos::caps::format::transfer_characteristic, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_string_constraint(!video->tcs.empty() ? video->tcs.name : sdp::transfer_characteristic_systems::SDR.name, con); } }, + { nmos::caps::format::component_depth, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_integer_constraint(video->depth, con); } }, + + // Audio Constraints + + { nmos::caps::format::channel_count, [](CAPS_ARGS) { auto audio = get_audio(&format); return audio && nmos::match_integer_constraint(audio->channel_count, con); } }, + { nmos::caps::format::sample_rate, [](CAPS_ARGS) { auto audio = get_audio(&format); return audio && nmos::match_rational_constraint(audio->sample_rate, con); } }, + { nmos::caps::format::sample_depth, [](CAPS_ARGS) { auto audio = get_audio(&format); return audio && nmos::match_integer_constraint(audio->bit_depth, con); } }, + + // Transport Constraints + + { nmos::caps::transport::packet_time, [](CAPS_ARGS) { return 0 == sdp.packet_time || nmos::match_number_constraint(sdp.packet_time, con); } }, + { nmos::caps::transport::max_packet_time, [](CAPS_ARGS) { return 0 == sdp.max_packet_time || nmos::match_number_constraint(sdp.max_packet_time, con); } }, + { nmos::caps::transport::st2110_21_sender_type, [](CAPS_ARGS) { if (auto video = get_video(&format)) return nmos::match_string_constraint(video->tp.name, con); else if (auto mux = get_mux(&format)) return nmos::match_string_constraint(mux->tp.name, con); else return false; } } + }; +#undef CAPS_ARGS } } From fff7420c9f4a5cf090f09324772d956c8bc67889 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Thu, 3 Feb 2022 16:42:21 +0300 Subject: [PATCH 008/109] Fix bst::optional nullopt values --- Development/nmos-cpp-node/node_implementation.cpp | 6 +++--- Development/nmos/flowcompatibility_api.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 1ee9f7b74..6d3d914e9 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1313,7 +1313,7 @@ nmos::experimental::details::flowcompatibility_base_edid_put_handler make_node_i { return [&gate](const nmos::id& input_id, const utility::string_t& base_edid, bst::optional& base_edid_properties) { - base_edid_properties = boost::none; + base_edid_properties = bst::nullopt; slog::log(gate, SLOG_FLF) << "Base EDID updated for input " << input_id; }; @@ -1352,9 +1352,9 @@ nmos::experimental::details::flowcompatibility_effective_edid_setter make_node_i 0x37, 0x38, 0x57, 0x46, 0x50, 0x0a, 0x00, 0x78 }; - effective_edid_properties = boost::none; + effective_edid_properties = bst::nullopt; - bst::optional base_edid = boost::none; + bst::optional base_edid = bst::nullopt; const std::pair id_type{ input_id, nmos::types::input }; auto resource = find_resource(flowcompatibility_resources, id_type); diff --git a/Development/nmos/flowcompatibility_api.cpp b/Development/nmos/flowcompatibility_api.cpp index dbdd3f5ee..5eedeb612 100644 --- a/Development/nmos/flowcompatibility_api.cpp +++ b/Development/nmos/flowcompatibility_api.cpp @@ -89,7 +89,7 @@ namespace nmos void update_effective_edid(nmos::node_model& model, const flowcompatibility_effective_edid_setter& effective_edid_setter, const utility::string_t resource_id) { boost::variant effective_edid; - bst::optional effective_edid_properties = boost::none; + bst::optional effective_edid_properties = bst::nullopt; effective_edid_setter(resource_id, effective_edid, effective_edid_properties); @@ -612,7 +612,7 @@ namespace nmos { if (!nmos::fields::temporarily_locked(endpoint_base_edid)) { - bst::optional base_edid_properties = boost::none; + bst::optional base_edid_properties = bst::nullopt; const auto request_body = req.content_ready().get().extract_vector().get(); const utility::string_t base_edid{ request_body.begin(), request_body.end() }; From 3de8159b6a2f916bcad29c3299d3f521481076b3 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Mon, 21 Feb 2022 11:16:02 +0300 Subject: [PATCH 009/109] Allow making Outputs without EDID --- Development/nmos-cpp-node/node_implementation.cpp | 2 +- Development/nmos/flowcompatibility_resources.cpp | 7 +++++-- Development/nmos/flowcompatibility_resources.h | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 6d3d914e9..a43a9d690 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -933,7 +933,7 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) receiver_ids.push_back(impl::make_id(seed_id, nmos::types::receiver, port, index)); } - auto output = nmos::experimental::make_flowcompatibility_output(output_id, false, edid, boost::none, receiver_ids, model.settings); + auto output = nmos::experimental::make_flowcompatibility_output(output_id, false, boost::variant(edid), bst::nullopt, receiver_ids, model.settings); impl::set_label_description(output, impl::ports::mux, 0); // The single Output consumes both video and audio signals if (!insert_resource_after(delay_millis, model.flowcompatibility_resources, std::move(output), gate)) return; diff --git a/Development/nmos/flowcompatibility_resources.cpp b/Development/nmos/flowcompatibility_resources.cpp index bb4e0a5d9..f639b4cdc 100644 --- a/Development/nmos/flowcompatibility_resources.cpp +++ b/Development/nmos/flowcompatibility_resources.cpp @@ -186,14 +186,17 @@ namespace nmos return{ is11_versions::v1_0, types::output, std::move(data), id, false }; } - nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const boost::variant& edid, const bst::optional& edid_properties, const std::vector& receivers, const nmos::settings& settings) + nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const bst::optional>& edid, const bst::optional& edid_properties, const std::vector& receivers, const nmos::settings& settings) { using web::json::value_from_elements; auto data = make_flowcompatibility_input_output_base(id, connected, true, settings); data[nmos::fields::receivers] = value_from_elements(receivers); - data[nmos::fields::endpoint_edid] = boost::apply_visitor(edid_file_visitor(), edid); + if (edid.has_value()) + { + data[nmos::fields::endpoint_edid] = boost::apply_visitor(edid_file_visitor(), edid.value()); + } if (edid_properties.has_value()) { diff --git a/Development/nmos/flowcompatibility_resources.h b/Development/nmos/flowcompatibility_resources.h index fb6684867..979bb250b 100644 --- a/Development/nmos/flowcompatibility_resources.h +++ b/Development/nmos/flowcompatibility_resources.h @@ -34,7 +34,7 @@ namespace nmos // Makes an output without EDID support nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const std::vector& receivers, const nmos::settings& settings); // Makes an output with EDID support - nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const boost::variant& edid, const bst::optional& edid_properties, const std::vector& receivers, const nmos::settings& settings); + nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const bst::optional>& edid, const bst::optional& edid_properties, const std::vector& receivers, const nmos::settings& settings); struct edid_file_visitor : public boost::static_visitor { From 142f765430b17940de0401e69d6580cf3afc6b81 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Mon, 21 Feb 2022 11:16:36 +0300 Subject: [PATCH 010/109] Remove "senders"/"receivers" arrays from Input/Output properties --- Development/nmos/flowcompatibility_api.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Development/nmos/flowcompatibility_api.cpp b/Development/nmos/flowcompatibility_api.cpp index 5eedeb612..b8504bd5b 100644 --- a/Development/nmos/flowcompatibility_api.cpp +++ b/Development/nmos/flowcompatibility_api.cpp @@ -495,6 +495,7 @@ namespace nmos { data.erase(nmos::fields::endpoint_effective_edid); } + data.erase(nmos::fields::senders); } else if (nmos::types::output == nmos::type_from_resourceType(resourceType)) { @@ -502,6 +503,7 @@ namespace nmos { data.erase(nmos::fields::endpoint_edid); } + data.erase(nmos::fields::receivers); } set_reply(res, status_codes::OK, data); From 7ba0287209bed5d24979aa1193e30b1fe19777df Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 11 Mar 2022 21:10:08 +0300 Subject: [PATCH 011/109] Add Sender Capabilities to flowcompatibility_active_constraints_put_handler --- .../nmos-cpp-node/node_implementation.cpp | 47 +++++- Development/nmos/capabilities.cpp | 37 ----- Development/nmos/capabilities.h | 38 +++++ Development/nmos/constraints.cpp | 142 ++++++++++++++++++ Development/nmos/constraints.h | 9 ++ 5 files changed, 232 insertions(+), 41 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index a43a9d690..220001f7c 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -15,6 +15,7 @@ #endif #include "nmos/activation_mode.h" #include "nmos/capabilities.h" +#include "nmos/constraints.h" #include "nmos/channels.h" #include "nmos/channelmapping_resources.h" #include "nmos/clock_name.h" @@ -1389,11 +1390,49 @@ nmos::experimental::details::flowcompatibility_effective_edid_setter make_node_i // Example Flow Compatibility Management API callback to update active constraints nmos::experimental::details::flowcompatibility_active_constraints_put_handler make_node_implementation_active_constraints_handler(slog::base_gate& gate) { - return [&gate](const nmos::id& sender_id, const web::json::value& constraint_sets) -> bool + using web::json::value_of; + + auto sender_capabilities = value_of({ + value_of({ + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25, nmos::rates::rate29_97 }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, + { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ sdp::samplings::YCbCr_4_2_2.name }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_bff.name, nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, + { nmos::caps::format::colorspace, nmos::make_caps_string_constraint({ sdp::colorimetries::BT2020.name, sdp::colorimetries::BT709.name }) }, + { nmos::caps::format::transfer_characteristic, nmos::make_caps_string_constraint({ sdp::transfer_characteristic_systems::SDR.name }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({}, 8, 12) }, + { nmos::caps::transport::st2110_21_sender_type, nmos::make_caps_string_constraint({ sdp::type_parameters::type_N.name }) } + }), + value_of({ + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25, nmos::rates::rate29_97 }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 3840 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 2160 }) }, + { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ sdp::samplings::YCbCr_4_2_2.name }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_bff.name, nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, + { nmos::caps::format::colorspace, nmos::make_caps_string_constraint({ sdp::colorimetries::BT2020.name, sdp::colorimetries::BT709.name }) }, + { nmos::caps::format::transfer_characteristic, nmos::make_caps_string_constraint({ sdp::transfer_characteristic_systems::SDR.name }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({}, 8, 12) }, + { nmos::caps::transport::st2110_21_sender_type, nmos::make_caps_string_constraint({ sdp::type_parameters::type_N.name }) } + }) + }); + + return [&gate, sender_capabilities](const nmos::id& sender_id, const web::json::value& active_constraints) -> bool { - bool sender_can_adhere = true; - slog::log(gate, SLOG_FLF) << "Active constraints are updated for sender " << sender_id; - return sender_can_adhere; + for (const auto& constraint_set : nmos::fields::constraint_sets(active_constraints).as_array()) + { + for (const auto& sender_caps_constraint_set : sender_capabilities.as_array()) + { + if (nmos::experimental::is_constraint_subset(sender_caps_constraint_set, constraint_set)) + { + return true; + } + } + } + slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " doesn't support proposed Active Constraints"; + return false; }; } diff --git a/Development/nmos/capabilities.cpp b/Development/nmos/capabilities.cpp index 4ba723e2c..c4d3540da 100644 --- a/Development/nmos/capabilities.cpp +++ b/Development/nmos/capabilities.cpp @@ -56,43 +56,6 @@ namespace nmos }); } - namespace details - { - // cf. nmos::details::make_constraints_schema in nmos/connection_api.cpp - template - bool match_constraint(const T& value, const web::json::value& constraint, Parse parse) - { - if (constraint.has_field(nmos::fields::constraint_enum)) - { - const auto& enum_values = nmos::fields::constraint_enum(constraint).as_array(); - if (enum_values.end() == std::find_if(enum_values.begin(), enum_values.end(), [&parse, &value](const web::json::value& enum_value) - { - return parse(enum_value) == value; - })) - { - return false; - } - } - if (constraint.has_field(nmos::fields::constraint_minimum)) - { - const auto& minimum = nmos::fields::constraint_minimum(constraint); - if (parse(minimum) > value) - { - return false; - } - } - if (constraint.has_field(nmos::fields::constraint_maximum)) - { - const auto& maximum = nmos::fields::constraint_maximum(constraint); - if (parse(maximum) < value) - { - return false; - } - } - return true; - } - } - bool match_string_constraint(const utility::string_t& value, const web::json::value& constraint) { return details::match_constraint(value, constraint, [](const web::json::value& enum_value) diff --git a/Development/nmos/capabilities.h b/Development/nmos/capabilities.h index 56c4022d7..a296a2ee3 100644 --- a/Development/nmos/capabilities.h +++ b/Development/nmos/capabilities.h @@ -2,6 +2,7 @@ #define NMOS_CAPABILITIES_H #include "cpprest/json_utils.h" +#include "nmos/json_fields.h" #include "nmos/rational.h" namespace nmos @@ -34,6 +35,43 @@ namespace nmos // See https://specs.amwa.tv/bcp-004-01/releases/v1.0.0/docs/1.0._Receiver_Capabilities.html#rational-constraint-keywords web::json::value make_caps_rational_constraint(const std::vector& enum_values = {}, const nmos::rational& minimum = no_minimum(), const nmos::rational& maximum = no_maximum()); + namespace details + { + // cf. nmos::details::make_constraints_schema in nmos/connection_api.cpp + template + bool match_constraint(const T& value, const web::json::value& constraint, Parse parse) + { + if (constraint.has_field(nmos::fields::constraint_enum)) + { + const auto& enum_values = nmos::fields::constraint_enum(constraint).as_array(); + if (enum_values.end() == std::find_if(enum_values.begin(), enum_values.end(), [&parse, &value](const web::json::value& enum_value) + { + return parse(enum_value) == value; + })) + { + return false; + } + } + if (constraint.has_field(nmos::fields::constraint_minimum)) + { + const auto& minimum = nmos::fields::constraint_minimum(constraint); + if (parse(minimum) > value) + { + return false; + } + } + if (constraint.has_field(nmos::fields::constraint_maximum)) + { + const auto& maximum = nmos::fields::constraint_maximum(constraint); + if (parse(maximum) < value) + { + return false; + } + } + return true; + } + } + bool match_string_constraint(const utility::string_t& value, const web::json::value& constraint); bool match_integer_constraint(int64_t value, const web::json::value& constraint); bool match_number_constraint(double value, const web::json::value& constraint); diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index 222f33e16..e46d77932 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -75,5 +75,147 @@ namespace nmos return match_constraints.end() != found && !found->second(flow, constraint.second); }); } + + template + bool is_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint, Parse parse) + { + // subconstraint should have enum if constraint has enum + if (constraint.has_field(nmos::fields::constraint_enum) && !subconstraint.has_field(nmos::fields::constraint_enum)) + { + return false; + } + // subconstraint should have minimum or enum if constraint has minimum + if (constraint.has_field(nmos::fields::constraint_minimum) && !subconstraint.has_field(nmos::fields::constraint_enum) && !subconstraint.has_field(nmos::fields::constraint_minimum)) + { + return false; + } + // subconstraint should have maximum or enum if constraint has maximum + if (constraint.has_field(nmos::fields::constraint_maximum) && !subconstraint.has_field(nmos::fields::constraint_enum) && !subconstraint.has_field(nmos::fields::constraint_maximum)) + { + return false; + } + if (constraint.has_field(nmos::fields::constraint_minimum) && subconstraint.has_field(nmos::fields::constraint_minimum)) + { + const auto& constraint_minimum = nmos::fields::constraint_minimum(constraint); + const auto& subconstraint_minimum = nmos::fields::constraint_minimum(subconstraint); + if (parse(constraint_minimum) > parse(subconstraint_minimum)) + { + return false; + } + } + if (constraint.has_field(nmos::fields::constraint_maximum) && subconstraint.has_field(nmos::fields::constraint_maximum)) + { + const auto& constraint_maximum = nmos::fields::constraint_maximum(constraint); + const auto& subconstraint_maximum = nmos::fields::constraint_maximum(subconstraint); + if (parse(constraint_maximum) < parse(subconstraint_maximum)) + { + return false; + } + } + + // subconstraint enum values should match constraint + const auto& subconstraint_enum_values = nmos::fields::constraint_enum(subconstraint).as_array(); + if (subconstraint_enum_values.end() == std::find_if(subconstraint_enum_values.begin(), subconstraint_enum_values.end(), [&parse, &constraint](const web::json::value& enum_value) + { + return details::match_constraint(parse(enum_value), constraint, parse); + })) + { + return false; + } + return true; + } + + bool is_string_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint) + { + return is_subconstraint(constraint, subconstraint, [](const web::json::value& enum_value) + { + return enum_value.as_string(); + }); + } + + bool is_integer_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint) + { + return is_subconstraint(constraint, subconstraint, [](const web::json::value& enum_value) + { + return enum_value.as_integer(); + }); + } + + bool is_number_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint) + { + return is_subconstraint(constraint, subconstraint, [](const web::json::value& enum_value) + { + return enum_value.as_double(); + }); + } + + bool is_boolean_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint) + { + return is_subconstraint(constraint, subconstraint, [](const web::json::value& enum_value) + { + return enum_value.as_bool(); + }); + } + + bool is_rational_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint) + { + return is_subconstraint(constraint, subconstraint, [](const web::json::value& enum_value) + { + return nmos::parse_rational(enum_value); + }); + } + +#define CAPS_ARGS const web::json::value& constraint, const web::json::value& subconstraint + static const std::map> format_constraints + { + // General Constraints + + { nmos::caps::format::media_type, [](CAPS_ARGS) { return is_string_subconstraint(constraint, subconstraint); } }, + // hm, how best to match (rational) nmos::caps::format::grain_rate against (double) framerate e.g. for video/SMPTE2022-6? + // is 23.976 a match for 24000/1001? how about 23.98, or 23.9? or even 23?! + { nmos::caps::format::grain_rate, [](CAPS_ARGS) { return is_rational_subconstraint(constraint, subconstraint); } }, + + // Video Constraints + + { nmos::caps::format::frame_height, [](CAPS_ARGS) { return is_integer_subconstraint(constraint, subconstraint); } }, + { nmos::caps::format::frame_width, [](CAPS_ARGS) { return is_integer_subconstraint(constraint, subconstraint); } }, + { nmos::caps::format::color_sampling, [](CAPS_ARGS) { return is_string_subconstraint(constraint, subconstraint); } }, + { nmos::caps::format::interlace_mode, [](CAPS_ARGS) { return is_string_subconstraint(constraint, subconstraint); } }, + { nmos::caps::format::colorspace, [](CAPS_ARGS) { return is_string_subconstraint(constraint, subconstraint); } }, + { nmos::caps::format::transfer_characteristic, [](CAPS_ARGS) { return is_string_subconstraint(constraint, subconstraint); } }, + { nmos::caps::format::component_depth, [](CAPS_ARGS) { return is_integer_subconstraint(constraint, subconstraint); } }, + + // Audio Constraints + + { nmos::caps::format::channel_count, [](CAPS_ARGS) { return is_integer_subconstraint(constraint, subconstraint); } }, + { nmos::caps::format::sample_rate, [](CAPS_ARGS) { return is_rational_subconstraint(constraint, subconstraint); } }, + { nmos::caps::format::sample_depth, [](CAPS_ARGS) { return is_integer_subconstraint(constraint, subconstraint); } }, + + // Transport Constraints + + { nmos::caps::transport::packet_time, [](CAPS_ARGS) { return is_number_subconstraint(constraint, subconstraint); } }, + { nmos::caps::transport::max_packet_time, [](CAPS_ARGS) { return is_number_subconstraint(constraint, subconstraint); } }, + { nmos::caps::transport::st2110_21_sender_type, [](CAPS_ARGS) { return is_string_subconstraint(constraint, subconstraint); } }, + }; +#undef CAPS_ARGS + + bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset) + { + using web::json::value; + + if (!nmos::caps::meta::enabled(constraint_subset)) return true; + + const auto& param_constraints_set = constraint_set.as_object(); + const auto& param_constraints_subset = constraint_subset.as_object(); + + return param_constraints_subset.end() == std::find_if(param_constraints_subset.begin(), param_constraints_subset.end(), [&](const std::pair& subconstraint) + { + if (subconstraint.first == nmos::caps::meta::label.key || subconstraint.first == nmos::caps::meta::preference.key) return false; + + const auto& found = format_constraints.find(subconstraint.first); + const auto& constraint = param_constraints_set.find(subconstraint.first); + return param_constraints_set.end() == constraint || (found != format_constraints.end() && !found->second(constraint->second, subconstraint.second)); + }); + } } } diff --git a/Development/nmos/constraints.h b/Development/nmos/constraints.h index e1763c8a5..87e785d1a 100644 --- a/Development/nmos/constraints.h +++ b/Development/nmos/constraints.h @@ -1,3 +1,9 @@ +#ifndef NMOS_CONSTRAINTS_H +#define NMOS_CONSTRAINTS_H + +#include "nmos/capabilities.h" +#include "nmos/json_fields.h" + namespace web { namespace json @@ -12,5 +18,8 @@ namespace nmos { bool match_source_parameters_constraint_set(const web::json::value& source, const web::json::value& constraint_set); bool match_flow_parameters_constraint_set(const web::json::value& flow, const web::json::value& constraint_set); + bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset); } } + +#endif From b11402e9ead030618836e6b354063ae655d260cf Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sat, 14 May 2022 20:30:40 +0300 Subject: [PATCH 012/109] IS-11: update schemas, apply the new spec name to the sources --- Development/cmake/NmosCppLibraries.cmake | 20 +++++----- .../nmos-cpp-node/node_implementation.cpp | 2 +- Development/nmos/node_server.cpp | 4 +- Development/nmos/node_server.h | 2 +- ...ty_api.cpp => streamcompatibility_api.cpp} | 6 +-- ...bility_api.h => streamcompatibility_api.h} | 0 ....cpp => streamcompatibility_behaviour.cpp} | 6 +-- ...iour.h => streamcompatibility_behaviour.h} | 0 ....cpp => streamcompatibility_resources.cpp} | 4 +- ...rces.h => streamcompatibility_resources.h} | 0 ...ty_state.h => streamcompatibility_state.h} | 0 ...tils.cpp => streamcompatibility_utils.cpp} | 2 +- ...ty_utils.h => streamcompatibility_utils.h} | 0 .../v1.0.x/APIs/schemas/constraints-base.json | 4 +- .../APIs/schemas/constraints_supported.json | 40 ++++++++++--------- .../is-11/v1.0.x/APIs/schemas/edid.json | 3 -- .../v1.0.x/APIs/schemas/input-edid-base.json | 4 +- .../APIs/schemas/input-output-base.json | 4 +- .../v1.0.x/APIs/schemas/receiver-base.json | 4 +- .../v1.0.x/APIs/schemas/receiver-status.json | 6 +-- .../v1.0.x/APIs/schemas/sender-base.json | 4 +- .../v1.0.x/APIs/schemas/sender-status.json | 10 ++--- ...json => streamcompatibility-api-base.json} | 4 +- 23 files changed, 64 insertions(+), 65 deletions(-) rename Development/nmos/{flowcompatibility_api.cpp => streamcompatibility_api.cpp} (99%) rename Development/nmos/{flowcompatibility_api.h => streamcompatibility_api.h} (100%) rename Development/nmos/{flowcompatibility_behaviour.cpp => streamcompatibility_behaviour.cpp} (99%) rename Development/nmos/{flowcompatibility_behaviour.h => streamcompatibility_behaviour.h} (100%) rename Development/nmos/{flowcompatibility_resources.cpp => streamcompatibility_resources.cpp} (98%) rename Development/nmos/{flowcompatibility_resources.h => streamcompatibility_resources.h} (100%) rename Development/nmos/{flowcompatibility_state.h => streamcompatibility_state.h} (100%) rename Development/nmos/{flowcompatibility_utils.cpp => streamcompatibility_utils.cpp} (95%) rename Development/nmos/{flowcompatibility_utils.h => streamcompatibility_utils.h} (100%) rename Development/third_party/is-11/v1.0.x/APIs/schemas/{flowcompatibility-api-base.json => streamcompatibility-api-base.json} (64%) diff --git a/Development/cmake/NmosCppLibraries.cmake b/Development/cmake/NmosCppLibraries.cmake index 95634a558..89fb7aa5d 100644 --- a/Development/cmake/NmosCppLibraries.cmake +++ b/Development/cmake/NmosCppLibraries.cmake @@ -702,7 +702,6 @@ set(NMOS_IS11_V1_0_SCHEMAS_JSON third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/edid_timing.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/empty_constraints_active.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/error.json - third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/flowcompatibility-api-base.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/input-edid-base.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/input.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/input-output-base.json @@ -712,6 +711,7 @@ set(NMOS_IS11_V1_0_SCHEMAS_JSON third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/resource-list.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/sender-base.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/sender-status.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/streamcompatibility-api-base.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/uuid-list.json ) @@ -921,10 +921,10 @@ set(NMOS_CPP_NMOS_SOURCES nmos/events_ws_api.cpp nmos/events_ws_client.cpp nmos/filesystem_route.cpp - nmos/flowcompatibility_api.cpp - nmos/flowcompatibility_behaviour.cpp - nmos/flowcompatibility_resources.cpp - nmos/flowcompatibility_utils.cpp + nmos/streamcompatibility_api.cpp + nmos/streamcompatibility_behaviour.cpp + nmos/streamcompatibility_resources.cpp + nmos/streamcompatibility_utils.cpp nmos/group_hint.cpp nmos/id.cpp nmos/lldp_handler.cpp @@ -999,10 +999,10 @@ set(NMOS_CPP_NMOS_HEADERS nmos/events_ws_api.h nmos/events_ws_client.h nmos/filesystem_route.h - nmos/flowcompatibility_api.h - nmos/flowcompatibility_behaviour.h - nmos/flowcompatibility_resources.h - nmos/flowcompatibility_utils.h + nmos/streamcompatibility_api.h + nmos/streamcompatibility_behaviour.h + nmos/streamcompatibility_resources.h + nmos/streamcompatibility_utils.h nmos/format.h nmos/group_hint.h nmos/health.h @@ -1054,7 +1054,7 @@ set(NMOS_CPP_NMOS_HEADERS nmos/resources.h nmos/schemas_api.h nmos/sdp_utils.h - nmos/flowcompatibility_state.h + nmos/streamcompatibility_state.h nmos/server.h nmos/server_utils.h nmos/settings.h diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 220001f7c..214cd22b5 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -24,7 +24,7 @@ #include "nmos/connection_events_activation.h" #include "nmos/events_resources.h" #include "nmos/format.h" -#include "nmos/flowcompatibility_resources.h" +#include "nmos/streamcompatibility_resources.h" #include "nmos/group_hint.h" #include "nmos/interlace_mode.h" #ifdef HAVE_LLDP diff --git a/Development/nmos/node_server.cpp b/Development/nmos/node_server.cpp index a2461e70c..e70f3f1fc 100644 --- a/Development/nmos/node_server.cpp +++ b/Development/nmos/node_server.cpp @@ -5,8 +5,8 @@ #include "nmos/channelmapping_activation.h" #include "nmos/events_api.h" #include "nmos/events_ws_api.h" -#include "nmos/flowcompatibility_api.h" -#include "nmos/flowcompatibility_behaviour.h" +#include "nmos/streamcompatibility_api.h" +#include "nmos/streamcompatibility_behaviour.h" #include "nmos/logging_api.h" #include "nmos/manifest_api.h" #include "nmos/model.h" diff --git a/Development/nmos/node_server.h b/Development/nmos/node_server.h index 13092ad3c..8e93f4f00 100644 --- a/Development/nmos/node_server.h +++ b/Development/nmos/node_server.h @@ -6,7 +6,7 @@ #include "nmos/channelmapping_activation.h" #include "nmos/connection_api.h" #include "nmos/connection_activation.h" -#include "nmos/flowcompatibility_api.h" +#include "nmos/streamcompatibility_api.h" #include "nmos/node_behaviour.h" #include "nmos/node_system_behaviour.h" #include "nmos/ocsp_response_handler.h" diff --git a/Development/nmos/flowcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp similarity index 99% rename from Development/nmos/flowcompatibility_api.cpp rename to Development/nmos/streamcompatibility_api.cpp index b8504bd5b..c63f96fe1 100644 --- a/Development/nmos/flowcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -1,5 +1,5 @@ -#include "nmos/flowcompatibility_api.h" -#include "nmos/flowcompatibility_utils.h" +#include "nmos/streamcompatibility_api.h" +#include "nmos/streamcompatibility_utils.h" #include #include @@ -7,7 +7,7 @@ #include "cpprest/containerstream.h" #include "cpprest/json_validator.h" #include "nmos/capabilities.h" // for nmos::fields::constraint_sets -#include "nmos/flowcompatibility_resources.h" +#include "nmos/streamcompatibility_resources.h" #include "nmos/is11_versions.h" #include "nmos/json_schema.h" #include "nmos/model.h" diff --git a/Development/nmos/flowcompatibility_api.h b/Development/nmos/streamcompatibility_api.h similarity index 100% rename from Development/nmos/flowcompatibility_api.h rename to Development/nmos/streamcompatibility_api.h diff --git a/Development/nmos/flowcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp similarity index 99% rename from Development/nmos/flowcompatibility_behaviour.cpp rename to Development/nmos/streamcompatibility_behaviour.cpp index fb023f10b..788f73431 100644 --- a/Development/nmos/flowcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -1,6 +1,6 @@ -#include "nmos/flowcompatibility_behaviour.h" -#include "nmos/flowcompatibility_state.h" -#include "nmos/flowcompatibility_utils.h" +#include "nmos/streamcompatibility_behaviour.h" +#include "nmos/streamcompatibility_state.h" +#include "nmos/streamcompatibility_utils.h" #include #include diff --git a/Development/nmos/flowcompatibility_behaviour.h b/Development/nmos/streamcompatibility_behaviour.h similarity index 100% rename from Development/nmos/flowcompatibility_behaviour.h rename to Development/nmos/streamcompatibility_behaviour.h diff --git a/Development/nmos/flowcompatibility_resources.cpp b/Development/nmos/streamcompatibility_resources.cpp similarity index 98% rename from Development/nmos/flowcompatibility_resources.cpp rename to Development/nmos/streamcompatibility_resources.cpp index f639b4cdc..7063d9d39 100644 --- a/Development/nmos/flowcompatibility_resources.cpp +++ b/Development/nmos/streamcompatibility_resources.cpp @@ -1,10 +1,10 @@ -#include "nmos/flowcompatibility_resources.h" +#include "nmos/streamcompatibility_resources.h" #include #include "nmos/capabilities.h" // for nmos::fields::constraint_sets #include "nmos/is11_versions.h" #include "nmos/resource.h" -#include "nmos/flowcompatibility_state.h" +#include "nmos/streamcompatibility_state.h" namespace nmos { diff --git a/Development/nmos/flowcompatibility_resources.h b/Development/nmos/streamcompatibility_resources.h similarity index 100% rename from Development/nmos/flowcompatibility_resources.h rename to Development/nmos/streamcompatibility_resources.h diff --git a/Development/nmos/flowcompatibility_state.h b/Development/nmos/streamcompatibility_state.h similarity index 100% rename from Development/nmos/flowcompatibility_state.h rename to Development/nmos/streamcompatibility_state.h diff --git a/Development/nmos/flowcompatibility_utils.cpp b/Development/nmos/streamcompatibility_utils.cpp similarity index 95% rename from Development/nmos/flowcompatibility_utils.cpp rename to Development/nmos/streamcompatibility_utils.cpp index 3354a0c5a..9f04318b1 100644 --- a/Development/nmos/flowcompatibility_utils.cpp +++ b/Development/nmos/streamcompatibility_utils.cpp @@ -1,4 +1,4 @@ -#include "flowcompatibility_utils.h" +#include "nmos/streamcompatibility_utils.h" namespace nmos { diff --git a/Development/nmos/flowcompatibility_utils.h b/Development/nmos/streamcompatibility_utils.h similarity index 100% rename from Development/nmos/flowcompatibility_utils.h rename to Development/nmos/streamcompatibility_utils.h diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints-base.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints-base.json index 500930043..df2cf92f1 100644 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints-base.json +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints-base.json @@ -1,8 +1,8 @@ { "$schema": "http://json-schema.org/draft-04/schema", "type": "array", - "description": "Describes the Flow Compatibility Management API /senders/{senderId}/constraints base resource", - "title": "Flow Compatibility Management API /senders/{senderId}/constraints base resource", + "description": "Describes the Stream Compatibility Management API /senders/{senderId}/constraints base resource", + "title": "Stream Compatibility Management API /senders/{senderId}/constraints base resource", "items": { "type": "string", "enum": [ diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints_supported.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints_supported.json index 3f21436e1..b1d39351c 100644 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints_supported.json +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints_supported.json @@ -9,27 +9,29 @@ "type": "array", "items": { "type": "string", - "pattern": "^urn:x-nmos:cap:" - }, - "contains": { - "type": "string", - "enum": [ - "urn:x-nmos:cap:meta:label", - "urn:x-nmos:cap:meta:preference", - "urn:x-nmos:cap:meta:enabled", - "urn:x-nmos:cap:format:media_type", - "urn:x-nmos:cap:format:grain_rate", - "urn:x-nmos:cap:format:frame_width", - "urn:x-nmos:cap:format:frame_height", - "urn:x-nmos:cap:format:interlace_mode", - "urn:x-nmos:cap:format:color_sampling", - "urn:x-nmos:cap:format:component_depth", - "urn:x-nmos:cap:format:channel_count", - "urn:x-nmos:cap:format:sample_rate", - "urn:x-nmos:cap:format:sample_depth" + "anyOf": [ + { + "enum": [ + "urn:x-nmos:cap:meta:label", + "urn:x-nmos:cap:meta:preference", + "urn:x-nmos:cap:meta:enabled", + "urn:x-nmos:cap:format:media_type", + "urn:x-nmos:cap:format:grain_rate", + "urn:x-nmos:cap:format:frame_width", + "urn:x-nmos:cap:format:frame_height", + "urn:x-nmos:cap:format:interlace_mode", + "urn:x-nmos:cap:format:color_sampling", + "urn:x-nmos:cap:format:component_depth", + "urn:x-nmos:cap:format:channel_count", + "urn:x-nmos:cap:format:sample_rate", + "urn:x-nmos:cap:format:sample_depth" + ] + }, + { + "pattern": "^urn:x-nmos:cap:" + } ] }, - "minContains": 13, "uniqueItems": true } } diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/edid.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/edid.json index 957136993..dad38ccb9 100644 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/edid.json +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/edid.json @@ -46,9 +46,6 @@ "minItems": 1, "maxItems": 3, "uniqueItems": true, - "contains": { - "const": "RGB" - }, "items": { "type": "string", "enum": [ diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/input-edid-base.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/input-edid-base.json index fd35c85a6..21c6f35f3 100644 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/input-edid-base.json +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/input-edid-base.json @@ -1,8 +1,8 @@ { "$schema": "http://json-schema.org/draft-04/schema", "type": "array", - "description": "Describes the Flow Compatibility Management API /inputs/{inputId}/edid base resource", - "title": "Flow Compatibility Management API /inputs/{inputId}/edid base resource", + "description": "Describes the Stream Compatibility Management API /inputs/{inputId}/edid base resource", + "title": "Stream Compatibility Management API /inputs/{inputId}/edid base resource", "items": { "type": "string", "enum": [ diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/input-output-base.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/input-output-base.json index c247667f0..3ea9f5cb3 100644 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/input-output-base.json +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/input-output-base.json @@ -1,8 +1,8 @@ { "$schema": "http://json-schema.org/draft-04/schema", "type": "array", - "description": "Describes the Flow Compatibility Management API /inputs/{inputId} and /outputs/{outputId} base resource", - "title": "Flow Compatibility Management API /inputs/{inputId} and /outputs/{outputId} base resource", + "description": "Describes the Stream Compatibility Management API /inputs/{inputId} and /outputs/{outputId} base resource", + "title": "Stream Compatibility Management API /inputs/{inputId} and /outputs/{outputId} base resource", "items": { "type": "string", "enum": [ diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-base.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-base.json index ea9530b39..a9383368f 100644 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-base.json +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-base.json @@ -1,8 +1,8 @@ { "$schema": "http://json-schema.org/draft-04/schema", "type": "array", - "description": "Describes the Flow Compatibility Management API /receivers/{receiverId} base resource", - "title": "Flow Compatibility Management API /receivers/{receiverId} base resource", + "description": "Describes the Stream Compatibility Management API /receivers/{receiverId} base resource", + "title": "Stream Compatibility Management API /receivers/{receiverId} base resource", "items": { "type": "string", "enum": [ diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-status.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-status.json index 08021ba60..8644a65fb 100644 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-status.json +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-status.json @@ -9,9 +9,9 @@ "state": { "type": "string", "enum": [ - "No Transport File", - "OK", - "Receiver Capabilities Violation" + "unknown", + "compliant_stream", + "non_compliant_stream" ] } } diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/sender-base.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/sender-base.json index 46b755b99..d68fcc2ba 100644 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/sender-base.json +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/sender-base.json @@ -1,8 +1,8 @@ { "$schema": "http://json-schema.org/draft-04/schema", "type": "array", - "description": "Describes the Flow Compatibility Management API /senders/{senderId} base resource", - "title": "Flow Compatibility Management API /senders/{senderId} base resource", + "description": "Describes the Stream Compatibility Management API /senders/{senderId} base resource", + "title": "Stream Compatibility Management API /senders/{senderId} base resource", "items": { "type": "string", "enum": [ diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/sender-status.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/sender-status.json index 19a706c46..9eb98218e 100644 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/sender-status.json +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/sender-status.json @@ -9,11 +9,11 @@ "state": { "type": "string", "enum": [ - "Unconstrained", - "Constrained", - "Active Constraints Violation", - "No Signal", - "Awaiting Signal" + "unconstrained", + "constrained", + "active_constraints_violation", + "no_signal", + "awaiting_signal" ] } } diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/flowcompatibility-api-base.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/streamcompatibility-api-base.json similarity index 64% rename from Development/third_party/is-11/v1.0.x/APIs/schemas/flowcompatibility-api-base.json rename to Development/third_party/is-11/v1.0.x/APIs/schemas/streamcompatibility-api-base.json index 06df2742f..dd99300b8 100644 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/flowcompatibility-api-base.json +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/streamcompatibility-api-base.json @@ -1,8 +1,8 @@ { "$schema": "http://json-schema.org/draft-04/schema", "type": "array", - "description": "Describes the Flow Compatibility Management API base resource", - "title": "Flow Compatibility Management API base resource", + "description": "Describes the Stream Compatibility Management API base resource", + "title": "Stream Compatibility Management API base resource", "items": { "type": "string", "enum": [ From ae50dd1a547bafcd7cf252249783b667661a366b Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sat, 14 May 2022 20:42:52 +0300 Subject: [PATCH 013/109] flowcompatibility -> streamcompatibility --- Development/nmos-cpp-node/config.json | 2 +- .../nmos-cpp-node/node_implementation.cpp | 38 +++--- Development/nmos/api_utils.h | 2 +- Development/nmos/is11_schemas/is11_schemas.h | 2 +- Development/nmos/json_fields.h | 2 +- Development/nmos/json_schema.cpp | 2 +- Development/nmos/json_schema.h | 2 +- Development/nmos/model.h | 4 +- Development/nmos/node_resources.cpp | 10 +- Development/nmos/node_server.cpp | 4 +- Development/nmos/node_server.h | 16 +-- Development/nmos/settings.cpp | 2 +- Development/nmos/settings.h | 2 +- Development/nmos/streamcompatibility_api.cpp | 110 +++++++++--------- Development/nmos/streamcompatibility_api.h | 18 +-- .../nmos/streamcompatibility_behaviour.cpp | 36 +++--- .../nmos/streamcompatibility_behaviour.h | 2 +- .../nmos/streamcompatibility_resources.cpp | 38 +++--- .../nmos/streamcompatibility_resources.h | 24 ++-- 19 files changed, 158 insertions(+), 158 deletions(-) diff --git a/Development/nmos-cpp-node/config.json b/Development/nmos-cpp-node/config.json index dafe1ab0f..26d20d9f8 100644 --- a/Development/nmos-cpp-node/config.json +++ b/Development/nmos-cpp-node/config.json @@ -119,7 +119,7 @@ //"events_port": 3216, //"events_ws_port": 3217, //"channelmapping_port": 3215, - //"flowcompatibility_port": 3218, + //"streamcompatibility_port": 3218, // system_port [node]: used to construct request URLs for the System API (if not discovered via DNS-SD) //"system_port": 10641, diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 214cd22b5..f47ddd8a1 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -889,17 +889,17 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) sender_ids.push_back(impl::make_id(seed_id, nmos::types::sender, port, index)); } - auto input = nmos::experimental::make_flowcompatibility_input(input_id, true, true, edid, web::json::value::object(), sender_ids, model.settings); + auto input = nmos::experimental::make_streamcompatibility_input(input_id, true, true, edid, web::json::value::object(), sender_ids, model.settings); impl::set_label_description(input, impl::ports::mux, 0); // The single Input originates both video and audio signals - if (!insert_resource_after(delay_millis, model.flowcompatibility_resources, std::move(input), gate)) return; + if (!insert_resource_after(delay_millis, model.streamcompatibility_resources, std::move(input), gate)) return; for (const auto& sender_id : sender_ids) { const std::vector supported_param_constraints{ nmos::caps::transport::packet_time.key }; - auto flowcompatibility_sender = nmos::experimental::make_flowcompatibility_sender(sender_id, { input_id }, supported_param_constraints); - if (!insert_resource_after(delay_millis, model.flowcompatibility_resources, std::move(flowcompatibility_sender), gate)) return; + auto streamcompatibility_sender = nmos::experimental::make_streamcompatibility_sender(sender_id, { input_id }, supported_param_constraints); + if (!insert_resource_after(delay_millis, model.streamcompatibility_resources, std::move(streamcompatibility_sender), gate)) return; } } @@ -934,14 +934,14 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) receiver_ids.push_back(impl::make_id(seed_id, nmos::types::receiver, port, index)); } - auto output = nmos::experimental::make_flowcompatibility_output(output_id, false, boost::variant(edid), bst::nullopt, receiver_ids, model.settings); + auto output = nmos::experimental::make_streamcompatibility_output(output_id, false, boost::variant(edid), bst::nullopt, receiver_ids, model.settings); impl::set_label_description(output, impl::ports::mux, 0); // The single Output consumes both video and audio signals - if (!insert_resource_after(delay_millis, model.flowcompatibility_resources, std::move(output), gate)) return; + if (!insert_resource_after(delay_millis, model.streamcompatibility_resources, std::move(output), gate)) return; for (const auto& receiver_id : receiver_ids) { - auto flowcompatibility_receiver = nmos::experimental::make_flowcompatibility_receiver(receiver_id, { output_id }); - if (!insert_resource_after(delay_millis, model.flowcompatibility_resources, std::move(flowcompatibility_receiver), gate)) return; + auto streamcompatibility_receiver = nmos::experimental::make_streamcompatibility_receiver(receiver_id, { output_id }); + if (!insert_resource_after(delay_millis, model.streamcompatibility_resources, std::move(streamcompatibility_receiver), gate)) return; } } } @@ -1310,7 +1310,7 @@ nmos::channelmapping_activation_handler make_node_implementation_channelmapping_ } // Example Flow Compatibility Management API base EDID update callback to perform application-specific operations to apply updated Base EDID -nmos::experimental::details::flowcompatibility_base_edid_put_handler make_node_implementation_flowcompatibility_base_edid_put_handler(slog::base_gate& gate) +nmos::experimental::details::streamcompatibility_base_edid_put_handler make_node_implementation_streamcompatibility_base_edid_put_handler(slog::base_gate& gate) { return [&gate](const nmos::id& input_id, const utility::string_t& base_edid, bst::optional& base_edid_properties) { @@ -1321,7 +1321,7 @@ nmos::experimental::details::flowcompatibility_base_edid_put_handler make_node_i } // Example Flow Compatibility Management API base EDID delete callback to perform application-specific operations in the case Base EDID is deleted -nmos::experimental::details::flowcompatibility_base_edid_delete_handler make_node_implementation_flowcompatibility_base_edid_delete_handler(slog::base_gate& gate) +nmos::experimental::details::streamcompatibility_base_edid_delete_handler make_node_implementation_streamcompatibility_base_edid_delete_handler(slog::base_gate& gate) { return [&gate](const nmos::id& input_id) { @@ -1329,10 +1329,10 @@ nmos::experimental::details::flowcompatibility_base_edid_delete_handler make_nod }; } -// Example Flow Compatibility Management API callback to update effective EDID - captures flowcompatibility_resources by reference! -nmos::experimental::details::flowcompatibility_effective_edid_setter make_node_implementation_effective_edid_setter(const nmos::resources& flowcompatibility_resources, slog::base_gate& gate) +// Example Flow Compatibility Management API callback to update effective EDID - captures streamcompatibility_resources by reference! +nmos::experimental::details::streamcompatibility_effective_edid_setter make_node_implementation_effective_edid_setter(const nmos::resources& streamcompatibility_resources, slog::base_gate& gate) { - return [&flowcompatibility_resources, &gate](const nmos::id& input_id, boost::variant& effective_edid, bst::optional& effective_edid_properties) + return [&streamcompatibility_resources, &gate](const nmos::id& input_id, boost::variant& effective_edid, bst::optional& effective_edid_properties) { unsigned char edid_bytes[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, @@ -1358,8 +1358,8 @@ nmos::experimental::details::flowcompatibility_effective_edid_setter make_node_i bst::optional base_edid = bst::nullopt; const std::pair id_type{ input_id, nmos::types::input }; - auto resource = find_resource(flowcompatibility_resources, id_type); - if (flowcompatibility_resources.end() != resource) + auto resource = find_resource(streamcompatibility_resources, id_type); + if (streamcompatibility_resources.end() != resource) { auto& edid_endpoint = nmos::fields::endpoint_base_edid(resource->data); @@ -1388,7 +1388,7 @@ nmos::experimental::details::flowcompatibility_effective_edid_setter make_node_i } // Example Flow Compatibility Management API callback to update active constraints -nmos::experimental::details::flowcompatibility_active_constraints_put_handler make_node_implementation_active_constraints_handler(slog::base_gate& gate) +nmos::experimental::details::streamcompatibility_active_constraints_put_handler make_node_implementation_active_constraints_handler(slog::base_gate& gate) { using web::json::value_of; @@ -1554,8 +1554,8 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode .on_connection_activated(make_node_implementation_connection_activation_handler(model, gate)) .on_validate_channelmapping_output_map(make_node_implementation_map_validator()) // may be omitted if not required .on_channelmapping_activated(make_node_implementation_channelmapping_activation_handler(gate)) - .on_base_edid_changed(make_node_implementation_flowcompatibility_base_edid_put_handler(gate)) - .on_base_edid_deleted(make_node_implementation_flowcompatibility_base_edid_delete_handler(gate)) - .on_set_effective_edid(make_node_implementation_effective_edid_setter(model.flowcompatibility_resources, gate)) + .on_base_edid_changed(make_node_implementation_streamcompatibility_base_edid_put_handler(gate)) + .on_base_edid_deleted(make_node_implementation_streamcompatibility_base_edid_delete_handler(gate)) + .on_set_effective_edid(make_node_implementation_effective_edid_setter(model.streamcompatibility_resources, gate)) .on_active_constraints_changed(make_node_implementation_active_constraints_handler(gate)); } diff --git a/Development/nmos/api_utils.h b/Development/nmos/api_utils.h index fae50c8fe..c3c66fbdd 100644 --- a/Development/nmos/api_utils.h +++ b/Development/nmos/api_utils.h @@ -51,7 +51,7 @@ namespace nmos // IS-09 System API (originally specified in JT-NM TR-1001-1:2018 Annex A) const route_pattern system_api = make_route_pattern(U("api"), U("system")); // IS-11 Flow Compatibility Management API - const route_pattern flowcompatibility_api = make_route_pattern(U("api"), U("flowcompatibility")); + const route_pattern streamcompatibility_api = make_route_pattern(U("api"), U("streamcompatibility")); // API version pattern const route_pattern version = make_route_pattern(U("version"), U("v[0-9]+\\.[0-9]+")); diff --git a/Development/nmos/is11_schemas/is11_schemas.h b/Development/nmos/is11_schemas/is11_schemas.h index ec6b1971b..40f554ef7 100644 --- a/Development/nmos/is11_schemas/is11_schemas.h +++ b/Development/nmos/is11_schemas/is11_schemas.h @@ -16,7 +16,7 @@ namespace nmos extern const char* edid_timing; extern const char* empty_constraints_active; extern const char* error; - extern const char* flowcompatibility_api_base; + extern const char* streamcompatibility_api_base; extern const char* input_edid_base; extern const char* input; extern const char* input_output_base; diff --git a/Development/nmos/json_fields.h b/Development/nmos/json_fields.h index 49044e537..9efc1613e 100644 --- a/Development/nmos/json_fields.h +++ b/Development/nmos/json_fields.h @@ -232,7 +232,7 @@ namespace nmos // IS-11 Flow Compatibility Management - // for flowcompatibility_api + // for streamcompatibility_api const web::json::field_as_array inputs{ U("inputs") }; const web::json::field_as_array outputs{ U("outputs") }; const web::json::field_as_bool temporarily_locked{ U("temporarily_locked") }; diff --git a/Development/nmos/json_schema.cpp b/Development/nmos/json_schema.cpp index 7ca692d38..f0cc43748 100644 --- a/Development/nmos/json_schema.cpp +++ b/Development/nmos/json_schema.cpp @@ -449,7 +449,7 @@ namespace nmos return is08_schemas::v1_0::map_activations_post_request_uri; } - web::uri make_flowcompatibilityapi_senders_active_constraints_put_request_uri(const nmos::api_version& version) + web::uri make_streamcompatibilityapi_senders_active_constraints_put_request_uri(const nmos::api_version& version) { return is11_schemas::v1_0::senders_active_constraints_put_request_uri; } diff --git a/Development/nmos/json_schema.h b/Development/nmos/json_schema.h index 377421d90..a096768af 100644 --- a/Development/nmos/json_schema.h +++ b/Development/nmos/json_schema.h @@ -29,7 +29,7 @@ namespace nmos web::uri make_channelmappingapi_map_activations_post_request_schema_uri(const nmos::api_version& version); - web::uri make_flowcompatibilityapi_senders_active_constraints_put_request_uri(const nmos::api_version& version); + web::uri make_streamcompatibilityapi_senders_active_constraints_put_request_uri(const nmos::api_version& version); // load the json schema for the specified base URI web::json::value load_json_schema(const web::uri& id); diff --git a/Development/nmos/model.h b/Development/nmos/model.h index 835db90a8..99d3db724 100644 --- a/Development/nmos/model.h +++ b/Development/nmos/model.h @@ -103,8 +103,8 @@ namespace nmos nmos::resources channelmapping_resources; // IS-11 senders, receivers, inputs and outputs for this node - // see nmos/flowcompatibility_resources.h - nmos::resources flowcompatibility_resources; + // see nmos/streamcompatibility_resources.h + nmos::resources streamcompatibility_resources; }; struct registry_model : model diff --git a/Development/nmos/node_resources.cpp b/Development/nmos/node_resources.cpp index 93545ccd6..e9764497b 100644 --- a/Development/nmos/node_resources.cpp +++ b/Development/nmos/node_resources.cpp @@ -107,20 +107,20 @@ namespace nmos } } - if (0 <= nmos::fields::flowcompatibility_port(settings)) + if (0 <= nmos::fields::streamcompatibility_port(settings)) { for (const auto& version : nmos::is11_versions::from_settings(settings)) { - auto flowcompatibility_uri = web::uri_builder() + auto streamcompatibility_uri = web::uri_builder() .set_scheme(nmos::http_scheme(settings)) - .set_port(nmos::fields::flowcompatibility_port(settings)) - .set_path(U("/x-nmos/flowcompatibility/") + make_api_version(version)); + .set_port(nmos::fields::streamcompatibility_port(settings)) + .set_path(U("/x-nmos/streamcompatibility/") + make_api_version(version)); auto type = U("urn:x-nmos:control:fc-ctrl/") + make_api_version(version); for (const auto& host : hosts) { web::json::push_back(data[U("controls")], value_of({ - { U("href"), flowcompatibility_uri.set_host(host).to_uri().to_string() }, + { U("href"), streamcompatibility_uri.set_host(host).to_uri().to_string() }, { U("type"), type } })); } diff --git a/Development/nmos/node_server.cpp b/Development/nmos/node_server.cpp index e70f3f1fc..051ce8d5d 100644 --- a/Development/nmos/node_server.cpp +++ b/Development/nmos/node_server.cpp @@ -64,7 +64,7 @@ namespace nmos node_server.api_routers[{ {}, nmos::fields::channelmapping_port(node_model.settings) }].mount({}, nmos::make_channelmapping_api(node_model, node_implementation.validate_map, gate)); // Configure the Flow Compatibility API - node_server.api_routers[{ {}, nmos::fields::flowcompatibility_port(node_model.settings) }].mount({}, nmos::experimental::make_flowcompatibility_api(node_model, node_implementation.base_edid_changed, node_implementation.base_edid_deleted, node_implementation.set_effective_edid, node_implementation.active_constraints_changed, gate)); + node_server.api_routers[{ {}, nmos::fields::streamcompatibility_port(node_model.settings) }].mount({}, nmos::experimental::make_streamcompatibility_api(node_model, node_implementation.base_edid_changed, node_implementation.base_edid_deleted, node_implementation.set_effective_edid, node_implementation.active_constraints_changed, gate)); auto& events_ws_api = node_server.ws_handlers[{ {}, nmos::fields::events_ws_port(node_model.settings) }]; events_ws_api.first = nmos::make_events_ws_api(node_model, events_ws_api.second, gate); @@ -112,7 +112,7 @@ namespace nmos [&] { nmos::erase_expired_events_resources_thread(node_model, gate); }, [&, resolve_auto, set_transportfile, connection_activated] { nmos::connection_activation_thread(node_model, resolve_auto, set_transportfile, connection_activated, gate); }, [&, channelmapping_activated] { nmos::channelmapping_activation_thread(node_model, channelmapping_activated, gate); }, - [&] { nmos::experimental::flowcompatibility_behaviour_thread(node_model, gate); } + [&] { nmos::experimental::streamcompatibility_behaviour_thread(node_model, gate); } }); auto system_changed = node_implementation.system_changed; diff --git a/Development/nmos/node_server.h b/Development/nmos/node_server.h index 8e93f4f00..fd9dbecb6 100644 --- a/Development/nmos/node_server.h +++ b/Development/nmos/node_server.h @@ -58,10 +58,10 @@ namespace nmos node_implementation& on_validate_channelmapping_output_map(nmos::details::channelmapping_output_map_validator validate_map) { this->validate_map = std::move(validate_map); return *this; } node_implementation& on_channelmapping_activated(nmos::channelmapping_activation_handler channelmapping_activated) { this->channelmapping_activated = std::move(channelmapping_activated); return *this; } node_implementation& on_get_ocsp_response(nmos::ocsp_response_handler get_ocsp_response) { this->get_ocsp_response = std::move(get_ocsp_response); return *this; } - node_implementation& on_base_edid_changed(nmos::experimental::details::flowcompatibility_base_edid_put_handler base_edid_changed) { this->base_edid_changed = std::move(base_edid_changed); return *this; } - node_implementation& on_base_edid_deleted(nmos::experimental::details::flowcompatibility_base_edid_delete_handler base_edid_deleted) { this->base_edid_deleted = std::move(base_edid_deleted); return *this; } - node_implementation& on_set_effective_edid(nmos::experimental::details::flowcompatibility_effective_edid_setter set_effective_edid) { this->set_effective_edid = std::move(set_effective_edid); return *this; } - node_implementation& on_active_constraints_changed(nmos::experimental::details::flowcompatibility_active_constraints_put_handler active_constraints_changed) { this->active_constraints_changed = std::move(active_constraints_changed); return *this; } + node_implementation& on_base_edid_changed(nmos::experimental::details::streamcompatibility_base_edid_put_handler base_edid_changed) { this->base_edid_changed = std::move(base_edid_changed); return *this; } + node_implementation& on_base_edid_deleted(nmos::experimental::details::streamcompatibility_base_edid_delete_handler base_edid_deleted) { this->base_edid_deleted = std::move(base_edid_deleted); return *this; } + node_implementation& on_set_effective_edid(nmos::experimental::details::streamcompatibility_effective_edid_setter set_effective_edid) { this->set_effective_edid = std::move(set_effective_edid); return *this; } + node_implementation& on_active_constraints_changed(nmos::experimental::details::streamcompatibility_active_constraints_put_handler active_constraints_changed) { this->active_constraints_changed = std::move(active_constraints_changed); return *this; } // deprecated, use on_validate_connection_resource_patch node_implementation& on_validate_merged(nmos::details::connection_resource_patch_validator validate_merged) { return on_validate_connection_resource_patch(std::move(validate_merged)); } @@ -92,10 +92,10 @@ namespace nmos nmos::ocsp_response_handler get_ocsp_response; - nmos::experimental::details::flowcompatibility_base_edid_put_handler base_edid_changed; - nmos::experimental::details::flowcompatibility_base_edid_delete_handler base_edid_deleted; - nmos::experimental::details::flowcompatibility_effective_edid_setter set_effective_edid; - nmos::experimental::details::flowcompatibility_active_constraints_put_handler active_constraints_changed; + nmos::experimental::details::streamcompatibility_base_edid_put_handler base_edid_changed; + nmos::experimental::details::streamcompatibility_base_edid_delete_handler base_edid_deleted; + nmos::experimental::details::streamcompatibility_effective_edid_setter set_effective_edid; + nmos::experimental::details::streamcompatibility_active_constraints_put_handler active_constraints_changed; }; // Construct a server instance for an NMOS Node, implementing the IS-04 Node API, IS-05 Connection API, IS-07 Events API diff --git a/Development/nmos/settings.cpp b/Development/nmos/settings.cpp index 189b2bfa6..3a237c6d0 100644 --- a/Development/nmos/settings.cpp +++ b/Development/nmos/settings.cpp @@ -73,7 +73,7 @@ namespace nmos if (!registry) web::json::insert(settings, std::make_pair(nmos::fields::connection_port, http_port)); if (!registry) web::json::insert(settings, std::make_pair(nmos::fields::events_port, http_port)); if (!registry) web::json::insert(settings, std::make_pair(nmos::fields::channelmapping_port, http_port)); - if (!registry) web::json::insert(settings, std::make_pair(nmos::fields::flowcompatibility_port, http_port)); + if (!registry) web::json::insert(settings, std::make_pair(nmos::fields::streamcompatibility_port, http_port)); // can't share a port between an http_listener and a websocket_listener, so don't apply this one... //if (!registry) web::json::insert(settings, std::make_pair(nmos::fields::events_ws_port, http_port)); if (!registry) web::json::insert(settings, std::make_pair(nmos::experimental::fields::manifest_port, http_port)); diff --git a/Development/nmos/settings.h b/Development/nmos/settings.h index 711ec8e19..7497be84e 100644 --- a/Development/nmos/settings.h +++ b/Development/nmos/settings.h @@ -137,7 +137,7 @@ namespace nmos const web::json::field_as_integer_or events_port{ U("events_port"), 3216 }; const web::json::field_as_integer_or events_ws_port{ U("events_ws_port"), 3217 }; const web::json::field_as_integer_or channelmapping_port{ U("channelmapping_port"), 3215 }; - const web::json::field_as_integer_or flowcompatibility_port{ U("flowcompatibility_port"), 3218 }; + const web::json::field_as_integer_or streamcompatibility_port{ U("streamcompatibility_port"), 3218 }; // system_port [node]: used to construct request URLs for the System API (if not discovered via DNS-SD) const web::json::field_as_integer_or system_port{ U("system_port"), 10641 }; diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index c63f96fe1..3586fd3a3 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -86,7 +86,7 @@ namespace nmos } // it's expected that write lock is already catched for the model and an input with the resource_id exists - void update_effective_edid(nmos::node_model& model, const flowcompatibility_effective_edid_setter& effective_edid_setter, const utility::string_t resource_id) + void update_effective_edid(nmos::node_model& model, const streamcompatibility_effective_edid_setter& effective_edid_setter, const utility::string_t resource_id) { boost::variant effective_edid; bst::optional effective_edid_properties = bst::nullopt; @@ -95,7 +95,7 @@ namespace nmos utility::string_t updated_timestamp; - modify_resource(model.flowcompatibility_resources, resource_id, [&effective_edid, &effective_edid_properties, &updated_timestamp](nmos::resource& input) + modify_resource(model.streamcompatibility_resources, resource_id, [&effective_edid, &effective_edid_properties, &updated_timestamp](nmos::resource& input) { input.data[nmos::fields::endpoint_effective_edid] = boost::apply_visitor(edid_file_visitor(), effective_edid); @@ -109,16 +109,16 @@ namespace nmos }); const std::pair id_type{ resource_id, nmos::types::input }; - auto resource = find_resource(model.flowcompatibility_resources, id_type); + auto resource = find_resource(model.streamcompatibility_resources, id_type); update_version(model.node_resources, nmos::fields::senders(resource->data), updated_timestamp); } // it's expected that write lock is already catched for the model and IS-11 sender exists - void set_active_constraints(nmos::node_model& model, const nmos::id& sender_id, const web::json::value& constraints, const flowcompatibility_effective_edid_setter& effective_edid_setter) + void set_active_constraints(nmos::node_model& model, const nmos::id& sender_id, const web::json::value& constraints, const streamcompatibility_effective_edid_setter& effective_edid_setter) { const std::pair sender_id_type{ sender_id, nmos::types::sender }; - auto resource = find_resource(model.flowcompatibility_resources, sender_id_type); + auto resource = find_resource(model.streamcompatibility_resources, sender_id_type); auto matching_resource = find_resource(model.node_resources, sender_id_type); if (model.node_resources.end() == matching_resource) { @@ -131,8 +131,8 @@ namespace nmos for (const auto& input_id : nmos::fields::inputs(resource->data)) { const std::pair input_id_type{ input_id.as_string(), nmos::types::input }; - auto input = find_resource(model.flowcompatibility_resources, input_id_type); - if (model.flowcompatibility_resources.end() != input) + auto input = find_resource(model.streamcompatibility_resources, input_id_type); + if (model.streamcompatibility_resources.end() != input) { if (!all_resources_exist(model.node_resources, nmos::fields::senders(input->data), nmos::types::sender)) { @@ -148,10 +148,10 @@ namespace nmos utility::string_t updated_timestamp; - // Update Active Constraints in flowcompatibility_resources - modify_resource(model.flowcompatibility_resources, sender_id, [&constraints, &updated_timestamp](nmos::resource& sender) + // Update Active Constraints in streamcompatibility_resources + modify_resource(model.streamcompatibility_resources, sender_id, [&constraints, &updated_timestamp](nmos::resource& sender) { - sender.data[nmos::fields::endpoint_active_constraints] = make_flowcompatibility_active_constraints_endpoint(constraints); + sender.data[nmos::fields::endpoint_active_constraints] = make_streamcompatibility_active_constraints_endpoint(constraints); updated_timestamp = nmos::make_version(); sender.data[nmos::fields::version] = web::json::value::string(updated_timestamp); @@ -171,43 +171,43 @@ namespace nmos } } - web::http::experimental::listener::api_router make_unmounted_flowcompatibility_api(nmos::node_model& model, details::flowcompatibility_base_edid_put_handler base_edid_put_handler, details::flowcompatibility_base_edid_delete_handler base_edid_delete_handler, details::flowcompatibility_effective_edid_setter effective_edid_setter, details::flowcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate); + web::http::experimental::listener::api_router make_unmounted_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_put_handler base_edid_put_handler, details::streamcompatibility_base_edid_delete_handler base_edid_delete_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate); - web::http::experimental::listener::api_router make_flowcompatibility_api(nmos::node_model& model, details::flowcompatibility_base_edid_put_handler base_edid_put_handler, details::flowcompatibility_base_edid_delete_handler base_edid_delete_handler, details::flowcompatibility_effective_edid_setter effective_edid_setter, details::flowcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate) + web::http::experimental::listener::api_router make_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_put_handler base_edid_put_handler, details::streamcompatibility_base_edid_delete_handler base_edid_delete_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate) { using namespace web::http::experimental::listener::api_router_using_declarations; - api_router flowcompatibility_api; + api_router streamcompatibility_api; - flowcompatibility_api.support(U("/?"), methods::GET, [](http_request req, http_response res, const string_t&, const route_parameters&) + streamcompatibility_api.support(U("/?"), methods::GET, [](http_request req, http_response res, const string_t&, const route_parameters&) { set_reply(res, status_codes::OK, nmos::make_sub_routes_body({ U("x-nmos/") }, req, res)); return pplx::task_from_result(true); }); - flowcompatibility_api.support(U("/x-nmos/?"), methods::GET, [](http_request req, http_response res, const string_t&, const route_parameters&) + streamcompatibility_api.support(U("/x-nmos/?"), methods::GET, [](http_request req, http_response res, const string_t&, const route_parameters&) { - set_reply(res, status_codes::OK, nmos::make_sub_routes_body({ U("flowcompatibility/") }, req, res)); + set_reply(res, status_codes::OK, nmos::make_sub_routes_body({ U("streamcompatibility/") }, req, res)); return pplx::task_from_result(true); }); const auto versions = with_read_lock(model.mutex, [&model] { return nmos::is11_versions::from_settings(model.settings); }); - flowcompatibility_api.support(U("/x-nmos/") + nmos::patterns::flowcompatibility_api.pattern + U("/?"), methods::GET, [versions](http_request req, http_response res, const string_t&, const route_parameters&) + streamcompatibility_api.support(U("/x-nmos/") + nmos::patterns::streamcompatibility_api.pattern + U("/?"), methods::GET, [versions](http_request req, http_response res, const string_t&, const route_parameters&) { set_reply(res, status_codes::OK, nmos::make_sub_routes_body(nmos::make_api_version_sub_routes(versions), req, res)); return pplx::task_from_result(true); }); - flowcompatibility_api.mount(U("/x-nmos/") + nmos::patterns::flowcompatibility_api.pattern + U("/") + nmos::patterns::version.pattern, make_unmounted_flowcompatibility_api(model, base_edid_put_handler, base_edid_delete_handler, effective_edid_setter, active_constraints_handler, gate)); + streamcompatibility_api.mount(U("/x-nmos/") + nmos::patterns::streamcompatibility_api.pattern + U("/") + nmos::patterns::version.pattern, make_unmounted_streamcompatibility_api(model, base_edid_put_handler, base_edid_delete_handler, effective_edid_setter, active_constraints_handler, gate)); - return flowcompatibility_api; + return streamcompatibility_api; } - web::http::experimental::listener::api_router make_unmounted_flowcompatibility_api(nmos::node_model& model, details::flowcompatibility_base_edid_put_handler base_edid_put_handler, details::flowcompatibility_base_edid_delete_handler base_edid_delete_handler, details::flowcompatibility_effective_edid_setter effective_edid_setter, details::flowcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate_) + web::http::experimental::listener::api_router make_unmounted_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_put_handler base_edid_put_handler, details::streamcompatibility_base_edid_delete_handler base_edid_delete_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate_) { using namespace web::http::experimental::listener::api_router_using_declarations; - api_router flowcompatibility_api; + api_router streamcompatibility_api; // check for supported API version const auto versions = with_read_lock(model.mutex, [&model] { return nmos::is11_versions::from_settings(model.settings); }); @@ -215,22 +215,22 @@ namespace nmos const web::json::experimental::json_validator validator { nmos::experimental::load_json_schema, - boost::copy_range>(versions | boost::adaptors::transformed(experimental::make_flowcompatibilityapi_senders_active_constraints_put_request_uri)) + boost::copy_range>(versions | boost::adaptors::transformed(experimental::make_streamcompatibilityapi_senders_active_constraints_put_request_uri)) }; - flowcompatibility_api.support(U(".*"), nmos::details::make_api_version_handler(versions, gate_)); + streamcompatibility_api.support(U(".*"), nmos::details::make_api_version_handler(versions, gate_)); - flowcompatibility_api.support(U("/?"), methods::GET, [](http_request req, http_response res, const string_t&, const route_parameters&) + streamcompatibility_api.support(U("/?"), methods::GET, [](http_request req, http_response res, const string_t&, const route_parameters&) { set_reply(res, status_codes::OK, nmos::make_sub_routes_body({ U("senders/"), U("receivers/"), U("inputs/"), U("outputs/") }, req, res)); return pplx::task_from_result(true); }); - flowcompatibility_api.support(U("/") + nmos::patterns::flowCompatibilityResourceType.pattern + U("/?"), methods::GET, [&model, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::flowCompatibilityResourceType.pattern + U("/?"), methods::GET, [&model, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) { nmos::api_gate gate(gate_, req, parameters); auto lock = model.read_lock(); - auto& resources = model.flowcompatibility_resources; + auto& resources = model.streamcompatibility_resources; const string_t resourceType = parameters.at(nmos::patterns::flowCompatibilityResourceType.name); @@ -266,10 +266,10 @@ namespace nmos return pplx::task_from_result(true); }); - flowcompatibility_api.support(U("/") + nmos::patterns::flowCompatibilityResourceType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::flowCompatibilityResourceType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) { auto lock = model.read_lock(); - auto& resources = model.flowcompatibility_resources; + auto& resources = model.streamcompatibility_resources; const string_t resourceType = parameters.at(nmos::patterns::flowCompatibilityResourceType.name); const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); @@ -316,10 +316,10 @@ namespace nmos return pplx::task_from_result(true); }); - flowcompatibility_api.support(U("/") + nmos::patterns::connectorType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/") + nmos::patterns::senderReceiverSubrouteType.pattern + U("/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::connectorType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/") + nmos::patterns::senderReceiverSubrouteType.pattern + U("/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) { auto lock = model.read_lock(); - auto& resources = model.flowcompatibility_resources; + auto& resources = model.streamcompatibility_resources; const string_t resourceType = parameters.at(nmos::patterns::connectorType.name); const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); @@ -365,10 +365,10 @@ namespace nmos return pplx::task_from_result(true); }); - flowcompatibility_api.support(U("/") + nmos::patterns::connectorType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/status/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::connectorType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/status/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) { auto lock = model.read_lock(); - auto& resources = model.flowcompatibility_resources; + auto& resources = model.streamcompatibility_resources; const string_t resourceType = parameters.at(nmos::patterns::connectorType.name); const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); @@ -397,10 +397,10 @@ namespace nmos return pplx::task_from_result(true); }); - flowcompatibility_api.support(U("/") + nmos::patterns::senderType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/constraints/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::senderType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/constraints/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) { auto lock = model.read_lock(); - auto& resources = model.flowcompatibility_resources; + auto& resources = model.streamcompatibility_resources; const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); @@ -430,10 +430,10 @@ namespace nmos return pplx::task_from_result(true); }); - flowcompatibility_api.support(U("/") + nmos::patterns::senderType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/constraints/") + nmos::patterns::constraintsType.pattern + U("/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::senderType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/constraints/") + nmos::patterns::constraintsType.pattern + U("/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) { auto lock = model.read_lock(); - auto& resources = model.flowcompatibility_resources; + auto& resources = model.streamcompatibility_resources; const string_t constraintsType = parameters.at(nmos::patterns::constraintsType.name); const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); @@ -470,10 +470,10 @@ namespace nmos return pplx::task_from_result(true); }); - flowcompatibility_api.support(U("/") + nmos::patterns::inputOutputType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/properties/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::inputOutputType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/properties/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) { auto lock = model.read_lock(); - auto& resources = model.flowcompatibility_resources; + auto& resources = model.streamcompatibility_resources; const string_t resourceType = parameters.at(nmos::patterns::inputOutputType.name); const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); @@ -520,11 +520,11 @@ namespace nmos return pplx::task_from_result(true); }); - flowcompatibility_api.support(U("/") + nmos::patterns::inputOutputType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/edid/?"), methods::GET, [&model, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::inputOutputType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/edid/?"), methods::GET, [&model, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) { nmos::api_gate gate(gate_, req, parameters); auto lock = model.read_lock(); - auto& resources = model.flowcompatibility_resources; + auto& resources = model.streamcompatibility_resources; const string_t resourceType = parameters.at(nmos::patterns::inputOutputType.name); const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); @@ -561,11 +561,11 @@ namespace nmos return pplx::task_from_result(true); }); - flowcompatibility_api.support(U("/") + nmos::patterns::inputType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/edid/") + nmos::patterns::edidType.pattern + U("/?"), methods::GET, [&model, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::inputType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/edid/") + nmos::patterns::edidType.pattern + U("/?"), methods::GET, [&model, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) { nmos::api_gate gate(gate_, req, parameters); auto lock = model.read_lock(); - auto& resources = model.flowcompatibility_resources; + auto& resources = model.streamcompatibility_resources; const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); const string_t edidType = parameters.at(nmos::patterns::edidType.name); @@ -596,11 +596,11 @@ namespace nmos return pplx::task_from_result(true); }); - flowcompatibility_api.support(U("/") + nmos::patterns::inputType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/edid/base/?"), methods::PUT, [&model, base_edid_put_handler, effective_edid_setter, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::inputType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/edid/base/?"), methods::PUT, [&model, base_edid_put_handler, effective_edid_setter, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) { nmos::api_gate gate(gate_, req, parameters); auto lock = model.write_lock(); - auto& resources = model.flowcompatibility_resources; + auto& resources = model.streamcompatibility_resources; const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); @@ -637,7 +637,7 @@ namespace nmos utility::string_t updated_timestamp; - // Update Base EDID in flowcompatibility_resources + // Update Base EDID in streamcompatibility_resources modify_resource(resources, resourceId, [&base_edid, &base_edid_properties, &updated_timestamp](nmos::resource& input) { if (base_edid_properties.has_value()) @@ -645,7 +645,7 @@ namespace nmos input.data[nmos::fields::base_edid_properties] = base_edid_properties.value(); } - input.data[nmos::fields::endpoint_base_edid] = make_flowcompatibility_edid_endpoint(base_edid); + input.data[nmos::fields::endpoint_base_edid] = make_streamcompatibility_edid_endpoint(base_edid); updated_timestamp = nmos::make_version(); input.data[nmos::fields::version] = web::json::value::string(updated_timestamp); @@ -688,11 +688,11 @@ namespace nmos return pplx::task_from_result(true); }); - flowcompatibility_api.support(U("/") + nmos::patterns::inputType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/edid/base/?"), methods::DEL, [&model, base_edid_delete_handler, effective_edid_setter, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::inputType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/edid/base/?"), methods::DEL, [&model, base_edid_delete_handler, effective_edid_setter, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) { nmos::api_gate gate(gate_, req, parameters); auto lock = model.write_lock(); - auto& resources = model.flowcompatibility_resources; + auto& resources = model.streamcompatibility_resources; const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); @@ -726,7 +726,7 @@ namespace nmos modify_resource(resources, resourceId, [&effective_edid_setter, &updated_timestamp](nmos::resource& input) { - input.data[nmos::fields::endpoint_base_edid] = make_flowcompatibility_dummy_edid_endpoint(); + input.data[nmos::fields::endpoint_base_edid] = make_streamcompatibility_dummy_edid_endpoint(); updated_timestamp = nmos::make_version(); input.data[nmos::fields::version] = web::json::value::string(updated_timestamp); @@ -769,17 +769,17 @@ namespace nmos return pplx::task_from_result(true); }); - flowcompatibility_api.support(U("/") + nmos::patterns::senderType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/constraints/active/?"), methods::PUT, [&model, validator, active_constraints_handler, effective_edid_setter, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::senderType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/constraints/active/?"), methods::PUT, [&model, validator, active_constraints_handler, effective_edid_setter, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) { nmos::api_gate gate(gate_, req, parameters); return nmos::details::extract_json(req, gate).then([&model, req, res, parameters, &validator, &active_constraints_handler, &effective_edid_setter, gate](value data) mutable { const nmos::api_version version = nmos::parse_api_version(parameters.at(nmos::patterns::version.name)); - validator.validate(data, experimental::make_flowcompatibilityapi_senders_active_constraints_put_request_uri(version)); + validator.validate(data, experimental::make_streamcompatibilityapi_senders_active_constraints_put_request_uri(version)); auto lock = model.write_lock(); - auto& resources = model.flowcompatibility_resources; + auto& resources = model.streamcompatibility_resources; const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); @@ -842,11 +842,11 @@ namespace nmos }); }); - flowcompatibility_api.support(U("/") + nmos::patterns::senderType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/constraints/active/?"), methods::DEL, [&model, effective_edid_setter, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::senderType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/constraints/active/?"), methods::DEL, [&model, effective_edid_setter, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) { nmos::api_gate gate(gate_, req, parameters); auto lock = model.write_lock(); - auto& resources = model.flowcompatibility_resources; + auto& resources = model.streamcompatibility_resources; const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); @@ -882,7 +882,7 @@ namespace nmos return pplx::task_from_result(true); }); - return flowcompatibility_api; + return streamcompatibility_api; } } } diff --git a/Development/nmos/streamcompatibility_api.h b/Development/nmos/streamcompatibility_api.h index 5d2d0e82b..13f815e0a 100644 --- a/Development/nmos/streamcompatibility_api.h +++ b/Development/nmos/streamcompatibility_api.h @@ -16,30 +16,30 @@ namespace nmos { namespace details { - // a flowcompatibility_base_edid_put_handler is a notification that the Base EDID for the specified IS-11 input has changed + // a streamcompatibility_base_edid_put_handler is a notification that the Base EDID for the specified IS-11 input has changed // it can be used to perform any final validation of the specified Base EDID and set parsed Base EDID properties // it may throw web::json::json_exception, which will be mapped to a 400 Bad Request status code with NMOS error "debug" information including the exception message // or std::runtime_error, which will be mapped to a 500 Internal Error status code with NMOS error "debug" information including the exception message - typedef std::function& base_edid_properties)> flowcompatibility_base_edid_put_handler; + typedef std::function& base_edid_properties)> streamcompatibility_base_edid_put_handler; - // a flowcompatibility_base_edid_delete_handler is a notification that the Base EDID for the specified IS-11 input has been deleted + // a streamcompatibility_base_edid_delete_handler is a notification that the Base EDID for the specified IS-11 input has been deleted // this callback should not throw exceptions, as the Base EDID will already have been changed and those changes will not be rolled back - typedef std::function flowcompatibility_base_edid_delete_handler; + typedef std::function streamcompatibility_base_edid_delete_handler; - // a flowcompatibility_active_constraints_put_handler is a notification that the Active Constraints for the specified IS-11 sender has changed + // a streamcompatibility_active_constraints_put_handler is a notification that the Active Constraints for the specified IS-11 sender has changed // it can be used to perform any final validation of the specified Active Constraints // it may throw web::json::json_exception, which will be mapped to a 400 Bad Request status code with NMOS error "debug" information including the exception message // or std::runtime_error, which will be mapped to a 500 Internal Error status code with NMOS error "debug" information including the exception message - typedef std::function flowcompatibility_active_constraints_put_handler; + typedef std::function streamcompatibility_active_constraints_put_handler; - // a flowcompatibility_effective_edid_setter updates the specified Effective EDID for the specified IS-11 input + // a streamcompatibility_effective_edid_setter updates the specified Effective EDID for the specified IS-11 input // effective EDID of the input is updated when either Active Constraints of a Sender associated with this input are updated // or base EDID of the input is updated or deleted // this callback should not throw exceptions, as it's called after the mentioned changes which will not be rolled back - typedef std::function& effective_edid, bst::optional& effective_edid_properties)> flowcompatibility_effective_edid_setter; + typedef std::function& effective_edid, bst::optional& effective_edid_properties)> streamcompatibility_effective_edid_setter; } - web::http::experimental::listener::api_router make_flowcompatibility_api(nmos::node_model& model, details::flowcompatibility_base_edid_put_handler base_edid_put_handler, details::flowcompatibility_base_edid_delete_handler base_edid_delete_handler, details::flowcompatibility_effective_edid_setter effective_edid_setter, details::flowcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate); + web::http::experimental::listener::api_router make_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_put_handler base_edid_put_handler, details::streamcompatibility_base_edid_delete_handler base_edid_delete_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate); } } diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index 788f73431..a628dea9b 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -115,7 +115,7 @@ namespace nmos return receiver_state; } - void flowcompatibility_behaviour_thread(nmos::node_model& model, slog::base_gate& gate) + void streamcompatibility_behaviour_thread(nmos::node_model& model, slog::base_gate& gate) { using web::json::value; using web::json::value_of; @@ -123,7 +123,7 @@ namespace nmos auto lock = model.write_lock(); // in order to update the resources auto& node_resources = model.node_resources; auto& connection_resources = model.connection_resources; - auto& flowcompatibility_resources = model.flowcompatibility_resources; + auto& streamcompatibility_resources = model.streamcompatibility_resources; auto most_recent_update = nmos::tai_min(); @@ -132,11 +132,11 @@ namespace nmos model.wait(lock, [&] { return model.shutdown || most_recent_update < nmos::most_recent_update(node_resources); }); if (model.shutdown) break; - auto flowcompatibility_senders_ids = get_resources_ids(flowcompatibility_resources, nmos::types::sender); - auto flowcompatibility_receivers_ids = get_resources_ids(flowcompatibility_resources, nmos::types::receiver); + auto streamcompatibility_senders_ids = get_resources_ids(streamcompatibility_resources, nmos::types::sender); + auto streamcompatibility_receivers_ids = get_resources_ids(streamcompatibility_resources, nmos::types::receiver); // find IS-11 recently updated Senders and Senders with recently updated Flow or Source - for (const nmos::id& sender_id : flowcompatibility_senders_ids) + for (const nmos::id& sender_id : streamcompatibility_senders_ids) { try { @@ -162,16 +162,16 @@ namespace nmos { slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " or its Flow or Source has been updated recently and Sender State is being updated as well"; - const std::pair flowcompatibility_sender_id_type{ sender_id, nmos::types::sender }; - auto flowcompatibility_sender = find_resource(flowcompatibility_resources, flowcompatibility_sender_id_type); - if (flowcompatibility_resources.end() == flowcompatibility_sender) throw std::logic_error("Matching IS-11 sender not found"); + const std::pair streamcompatibility_sender_id_type{ sender_id, nmos::types::sender }; + auto streamcompatibility_sender = find_resource(streamcompatibility_resources, streamcompatibility_sender_id_type); + if (streamcompatibility_resources.end() == streamcompatibility_sender) throw std::logic_error("Matching IS-11 sender not found"); - nmos::signal_state signal_state(nmos::fields::signal_state(nmos::fields::endpoint_status(flowcompatibility_sender->data))); + nmos::signal_state signal_state(nmos::fields::signal_state(nmos::fields::endpoint_status(streamcompatibility_sender->data))); nmos::sender_state sender_state(signal_state.name); if (signal_state == nmos::signal_states::signal_is_present) { - const auto& constraint_sets = nmos::fields::constraint_sets(nmos::fields::active_constraint_sets(nmos::fields::endpoint_active_constraints(flowcompatibility_sender->data))).as_array(); + const auto& constraint_sets = nmos::fields::constraint_sets(nmos::fields::active_constraint_sets(nmos::fields::endpoint_active_constraints(streamcompatibility_sender->data))).as_array(); const std::pair connection_sender_id_type{ sender_id, nmos::types::sender }; auto connection_sender = find_resource(connection_resources, connection_sender_id_type); @@ -183,11 +183,11 @@ namespace nmos sender_state = validate_sender_resources(transport_file, flow->data, source->data, constraint_sets); } - if (nmos::fields::state(nmos::fields::status(nmos::fields::endpoint_status(flowcompatibility_sender->data))) != sender_state.name) + if (nmos::fields::state(nmos::fields::status(nmos::fields::endpoint_status(streamcompatibility_sender->data))) != sender_state.name) { utility::string_t updated_timestamp; - modify_resource(flowcompatibility_resources, sender_id, [&sender_state, &updated_timestamp, &gate](nmos::resource& sender) + modify_resource(streamcompatibility_resources, sender_id, [&sender_state, &updated_timestamp, &gate](nmos::resource& sender) { nmos::fields::status(nmos::fields::endpoint_status(sender.data))[nmos::fields::state] = web::json::value::string(sender_state.name); @@ -207,7 +207,7 @@ namespace nmos } // find IS-11 Receivers with recently updated "caps" to check whether the active transport file still satisfies them - for (const nmos::id& receiver_id : flowcompatibility_receivers_ids) + for (const nmos::id& receiver_id : streamcompatibility_receivers_ids) { try { @@ -223,9 +223,9 @@ namespace nmos { slog::log(gate, SLOG_FLF) << "Receiver " << receiver_id << " has been updated recently and Receiver State is being updated as well"; - const std::pair flowcompatibility_receiver_id_type{ receiver_id, nmos::types::receiver }; - auto flowcompatibility_receiver = find_resource(flowcompatibility_resources, flowcompatibility_receiver_id_type); - if (flowcompatibility_resources.end() == flowcompatibility_receiver) throw std::logic_error("Matching IS-11 receiver not found"); + const std::pair streamcompatibility_receiver_id_type{ receiver_id, nmos::types::receiver }; + auto streamcompatibility_receiver = find_resource(streamcompatibility_resources, streamcompatibility_receiver_id_type); + if (streamcompatibility_resources.end() == streamcompatibility_receiver) throw std::logic_error("Matching IS-11 receiver not found"); nmos::receiver_state receiver_state(nmos::receiver_states::no_transport_file); @@ -237,11 +237,11 @@ namespace nmos receiver_state = validate_receiver_resources(transport_file, receiver->data); - if (nmos::fields::state(nmos::fields::status(nmos::fields::endpoint_status(flowcompatibility_receiver->data))) != receiver_state.name) + if (nmos::fields::state(nmos::fields::status(nmos::fields::endpoint_status(streamcompatibility_receiver->data))) != receiver_state.name) { utility::string_t updated_timestamp; - modify_resource(flowcompatibility_resources, receiver_id, [&receiver_state, &updated_timestamp, &gate](nmos::resource& receiver) + modify_resource(streamcompatibility_resources, receiver_id, [&receiver_state, &updated_timestamp, &gate](nmos::resource& receiver) { nmos::fields::status(nmos::fields::endpoint_status(receiver.data))[nmos::fields::state] = web::json::value::string(receiver_state.name); diff --git a/Development/nmos/streamcompatibility_behaviour.h b/Development/nmos/streamcompatibility_behaviour.h index e2a4d0745..498a0a569 100644 --- a/Development/nmos/streamcompatibility_behaviour.h +++ b/Development/nmos/streamcompatibility_behaviour.h @@ -12,7 +12,7 @@ namespace nmos namespace experimental { - void flowcompatibility_behaviour_thread(nmos::node_model& model, slog::base_gate& gate); + void streamcompatibility_behaviour_thread(nmos::node_model& model, slog::base_gate& gate); } } diff --git a/Development/nmos/streamcompatibility_resources.cpp b/Development/nmos/streamcompatibility_resources.cpp index 7063d9d39..f0fddd814 100644 --- a/Development/nmos/streamcompatibility_resources.cpp +++ b/Development/nmos/streamcompatibility_resources.cpp @@ -10,7 +10,7 @@ namespace nmos { namespace experimental { - web::json::value make_flowcompatibility_active_constraints_endpoint(const web::json::value& constraint_sets, bool locked) + web::json::value make_streamcompatibility_active_constraints_endpoint(const web::json::value& constraint_sets, bool locked) { using web::json::value_of; @@ -24,7 +24,7 @@ namespace nmos }); } - web::json::value make_flowcompatibility_sender_status_endpoint(nmos::sender_state sender_state, nmos::signal_state signal_state) + web::json::value make_streamcompatibility_sender_status_endpoint(nmos::sender_state sender_state, nmos::signal_state signal_state) { using web::json::value_of; @@ -38,7 +38,7 @@ namespace nmos }); } - nmos::resource make_flowcompatibility_sender(const nmos::id& id, const std::vector& inputs, const std::vector& param_constraints) + nmos::resource make_streamcompatibility_sender(const nmos::id& id, const std::vector& inputs, const std::vector& param_constraints) { using web::json::value; using web::json::value_of; @@ -70,16 +70,16 @@ namespace nmos auto data = value_of({ { nmos::fields::id, id }, { nmos::fields::device_id, U("these are not the droids you are looking for") }, - { nmos::fields::endpoint_active_constraints, make_flowcompatibility_active_constraints_endpoint(value::array()) }, + { nmos::fields::endpoint_active_constraints, make_streamcompatibility_active_constraints_endpoint(value::array()) }, { nmos::fields::inputs, value_from_elements(inputs) }, { nmos::fields::supported_param_constraints, supported_param_constraints }, - { nmos::fields::endpoint_status, make_flowcompatibility_sender_status_endpoint(nmos::sender_states::unconstrained, nmos::signal_states::signal_is_present) }, + { nmos::fields::endpoint_status, make_streamcompatibility_sender_status_endpoint(nmos::sender_states::unconstrained, nmos::signal_states::signal_is_present) }, }); return{ is11_versions::v1_0, types::sender, std::move(data), id, false }; } - nmos::resource make_flowcompatibility_receiver(const nmos::id& id, const std::vector& outputs) + nmos::resource make_streamcompatibility_receiver(const nmos::id& id, const std::vector& outputs) { using web::json::value_of; using web::json::value_from_elements; @@ -102,7 +102,7 @@ namespace nmos return{ is11_versions::v1_0, types::receiver, std::move(data), id, false }; } - web::json::value make_flowcompatibility_dummy_edid_endpoint(bool locked) + web::json::value make_streamcompatibility_dummy_edid_endpoint(bool locked) { using web::json::value_of; @@ -111,7 +111,7 @@ namespace nmos }); } - web::json::value make_flowcompatibility_edid_endpoint(const web::uri& edid_file, bool locked) + web::json::value make_streamcompatibility_edid_endpoint(const web::uri& edid_file, bool locked) { using web::json::value_of; @@ -121,7 +121,7 @@ namespace nmos }); } - web::json::value make_flowcompatibility_edid_endpoint(const utility::string_t& edid_file, bool locked) + web::json::value make_streamcompatibility_edid_endpoint(const utility::string_t& edid_file, bool locked) { using web::json::value_of; @@ -131,7 +131,7 @@ namespace nmos }); } - web::json::value make_flowcompatibility_input_output_base(const nmos::id& id, bool connected, bool edid_support, const nmos::settings& settings) + web::json::value make_streamcompatibility_input_output_base(const nmos::id& id, bool connected, bool edid_support, const nmos::settings& settings) { using web::json::value; @@ -143,25 +143,25 @@ namespace nmos return data; } - nmos::resource make_flowcompatibility_input(const nmos::id& id, bool connected, const std::vector& senders, const nmos::settings& settings) + nmos::resource make_streamcompatibility_input(const nmos::id& id, bool connected, const std::vector& senders, const nmos::settings& settings) { using web::json::value_from_elements; - auto data = make_flowcompatibility_input_output_base(id, connected, false, settings); + auto data = make_streamcompatibility_input_output_base(id, connected, false, settings); data[nmos::fields::senders] = value_from_elements(senders); return{ is11_versions::v1_0, types::input, std::move(data), id, false }; } - nmos::resource make_flowcompatibility_input(const nmos::id& id, bool connected, bool base_edid_changeable, const boost::variant& effective_edid, const bst::optional& effective_edid_properties, const std::vector& senders, const nmos::settings& settings) + nmos::resource make_streamcompatibility_input(const nmos::id& id, bool connected, bool base_edid_changeable, const boost::variant& effective_edid, const bst::optional& effective_edid_properties, const std::vector& senders, const nmos::settings& settings) { using web::json::value_from_elements; - auto data = make_flowcompatibility_input_output_base(id, connected, true, settings); + auto data = make_streamcompatibility_input_output_base(id, connected, true, settings); if (base_edid_changeable) { - data[nmos::fields::endpoint_base_edid] = make_flowcompatibility_dummy_edid_endpoint(false); + data[nmos::fields::endpoint_base_edid] = make_streamcompatibility_dummy_edid_endpoint(false); } data[nmos::fields::endpoint_effective_edid] = boost::apply_visitor(edid_file_visitor(), effective_edid); @@ -176,21 +176,21 @@ namespace nmos return{ is11_versions::v1_0, types::input, std::move(data), id, false }; } - nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const std::vector& receivers, const nmos::settings& settings) + nmos::resource make_streamcompatibility_output(const nmos::id& id, bool connected, const std::vector& receivers, const nmos::settings& settings) { using web::json::value_from_elements; - auto data = make_flowcompatibility_input_output_base(id, connected, false, settings); + auto data = make_streamcompatibility_input_output_base(id, connected, false, settings); data[nmos::fields::receivers] = value_from_elements(receivers); return{ is11_versions::v1_0, types::output, std::move(data), id, false }; } - nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const bst::optional>& edid, const bst::optional& edid_properties, const std::vector& receivers, const nmos::settings& settings) + nmos::resource make_streamcompatibility_output(const nmos::id& id, bool connected, const bst::optional>& edid, const bst::optional& edid_properties, const std::vector& receivers, const nmos::settings& settings) { using web::json::value_from_elements; - auto data = make_flowcompatibility_input_output_base(id, connected, true, settings); + auto data = make_streamcompatibility_input_output_base(id, connected, true, settings); data[nmos::fields::receivers] = value_from_elements(receivers); if (edid.has_value()) diff --git a/Development/nmos/streamcompatibility_resources.h b/Development/nmos/streamcompatibility_resources.h index 979bb250b..8c7c44949 100644 --- a/Development/nmos/streamcompatibility_resources.h +++ b/Development/nmos/streamcompatibility_resources.h @@ -14,38 +14,38 @@ namespace nmos namespace experimental { - web::json::value make_flowcompatibility_active_constraints_endpoint(const web::json::value& constraint_sets, bool locked = false); + web::json::value make_streamcompatibility_active_constraints_endpoint(const web::json::value& constraint_sets, bool locked = false); - nmos::resource make_flowcompatibility_sender(const nmos::id& id, const std::vector& inputs, const std::vector& param_constraints); - nmos::resource make_flowcompatibility_receiver(const nmos::id& id, const std::vector& outputs); + nmos::resource make_streamcompatibility_sender(const nmos::id& id, const std::vector& inputs, const std::vector& param_constraints); + nmos::resource make_streamcompatibility_receiver(const nmos::id& id, const std::vector& outputs); // Makes a dummy EDID endpoint to show that an input supports EDID of this type but it currently has no value - web::json::value make_flowcompatibility_dummy_edid_endpoint(bool locked = false); - web::json::value make_flowcompatibility_edid_endpoint(const web::uri& edid_file, bool locked = false); - web::json::value make_flowcompatibility_edid_endpoint(const utility::string_t& edid_file, bool locked = false); + web::json::value make_streamcompatibility_dummy_edid_endpoint(bool locked = false); + web::json::value make_streamcompatibility_edid_endpoint(const web::uri& edid_file, bool locked = false); + web::json::value make_streamcompatibility_edid_endpoint(const utility::string_t& edid_file, bool locked = false); // See https://github.com/AMWA-TV/is-11/blob/v1.0-dev/APIs/schemas/input.json // Makes an input without EDID support - nmos::resource make_flowcompatibility_input(const nmos::id& id, bool connected, const std::vector& senders, const nmos::settings& settings); + nmos::resource make_streamcompatibility_input(const nmos::id& id, bool connected, const std::vector& senders, const nmos::settings& settings); // Makes an input with EDID support - nmos::resource make_flowcompatibility_input(const nmos::id& id, bool connected, bool base_edid_changeable, const boost::variant& effective_edid, const bst::optional& effective_edid_properties, const std::vector& senders, const nmos::settings& settings); + nmos::resource make_streamcompatibility_input(const nmos::id& id, bool connected, bool base_edid_changeable, const boost::variant& effective_edid, const bst::optional& effective_edid_properties, const std::vector& senders, const nmos::settings& settings); // See https://github.com/AMWA-TV/is-11/blob/v1.0-dev/APIs/schemas/output.json // Makes an output without EDID support - nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const std::vector& receivers, const nmos::settings& settings); + nmos::resource make_streamcompatibility_output(const nmos::id& id, bool connected, const std::vector& receivers, const nmos::settings& settings); // Makes an output with EDID support - nmos::resource make_flowcompatibility_output(const nmos::id& id, bool connected, const bst::optional>& edid, const bst::optional& edid_properties, const std::vector& receivers, const nmos::settings& settings); + nmos::resource make_streamcompatibility_output(const nmos::id& id, bool connected, const bst::optional>& edid, const bst::optional& edid_properties, const std::vector& receivers, const nmos::settings& settings); struct edid_file_visitor : public boost::static_visitor { web::json::value operator()(utility::string_t edid_file) const { - return make_flowcompatibility_edid_endpoint(edid_file); + return make_streamcompatibility_edid_endpoint(edid_file); } web::json::value operator()(web::uri edid_file) const { - return make_flowcompatibility_edid_endpoint(edid_file); + return make_streamcompatibility_edid_endpoint(edid_file); } }; } From d230c3a8f562fe7745d68294779f8eb831451524 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sat, 14 May 2022 23:25:44 +0300 Subject: [PATCH 014/109] Node: replace EDID examples with dummies, add minor fixes --- .../nmos-cpp-node/node_implementation.cpp | 122 +++++++++--------- 1 file changed, 63 insertions(+), 59 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index f47ddd8a1..eb45966b7 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -858,43 +858,45 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) if (!insert_resource_after(delay_millis, model.channelmapping_resources, std::move(channelmapping_output), gate)) throw node_implementation_init_exception(); } - // example IS-11 input and senders + // Example IS-11 Input and Senders { unsigned char edid_bytes[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, - 0x10, 0xac, 0x16, 0xd0, 0x48, 0x4c, 0x46, 0x34, - 0x1a, 0x12, 0x01, 0x04, 0x6a, 0x25, 0x17, 0x78, - 0xef, 0xb6, 0x90, 0xa6, 0x54, 0x51, 0x91, 0x25, - 0x17, 0x50, 0x54, 0xa5, 0x4b, 0x00, 0x81, 0x80, - 0x71, 0x4f, 0x95, 0x00, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xab, 0x22, - 0xa0, 0xa0, 0x50, 0x84, 0x1a, 0x30, 0x30, 0x20, - 0x36, 0x00, 0x72, 0xe6, 0x10, 0x00, 0x00, 0x1a, - 0x00, 0x00, 0x00, 0xff, 0x00, 0x47, 0x33, 0x34, - 0x30, 0x48, 0x38, 0x36, 0x50, 0x34, 0x46, 0x4c, - 0x48, 0x0a, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x32, - 0x4d, 0x1e, 0x53, 0x0e, 0x04, 0x11, 0xb2, 0x05, - 0xf8, 0x58, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xfc, - 0x00, 0x44, 0x45, 0x4c, 0x4c, 0x20, 0x45, 0x31, - 0x37, 0x38, 0x57, 0x46, 0x50, 0x0a, 0x00, 0x78 + 0x04, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0a, 0x01, 0x04, 0x80, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; utility::string_t edid(edid_bytes, edid_bytes + sizeof(edid_bytes)); const auto input_id = impl::make_id(seed_id, nmos::types::input); + // Associate the last created video and audio Senders with this Input std::vector sender_ids; - int index = how_many - 1; // Make the last created Sender IS-11 compatible + int index = how_many - 1; for (const auto& port : { impl::ports::video, impl::ports::audio }) { sender_ids.push_back(impl::make_id(seed_id, nmos::types::sender, port, index)); } - auto input = nmos::experimental::make_streamcompatibility_input(input_id, true, true, edid, web::json::value::object(), sender_ids, model.settings); - impl::set_label_description(input, impl::ports::mux, 0); // The single Input originates both video and audio signals + auto input = nmos::experimental::make_streamcompatibility_input(input_id, true, true, edid, bst::nullopt, sender_ids, model.settings); + impl::set_label_description(input, impl::ports::mux, 0); // The single Input consumes both video and audio signals if (!insert_resource_after(delay_millis, model.streamcompatibility_resources, std::move(input), gate)) return; for (const auto& sender_id : sender_ids) { + // Add "packet_time" to the list of Parameter Constraints supported by these Senders const std::vector supported_param_constraints{ nmos::caps::transport::packet_time.key }; @@ -903,39 +905,40 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) } } - // example IS-11 output and receivers + // Example IS-11 Output and Receivers { unsigned char edid_bytes[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, - 0x10, 0xac, 0x16, 0xd0, 0x48, 0x4c, 0x46, 0x34, - 0x1a, 0x12, 0x01, 0x04, 0x6a, 0x25, 0x17, 0x78, - 0xef, 0xb6, 0x90, 0xa6, 0x54, 0x51, 0x91, 0x25, - 0x17, 0x50, 0x54, 0xa5, 0x4b, 0x00, 0x81, 0x80, - 0x71, 0x4f, 0x95, 0x00, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xab, 0x22, - 0xa0, 0xa0, 0x50, 0x84, 0x1a, 0x30, 0x30, 0x20, - 0x36, 0x00, 0x72, 0xe6, 0x10, 0x00, 0x00, 0x1a, - 0x00, 0x00, 0x00, 0xff, 0x00, 0x47, 0x33, 0x34, - 0x30, 0x48, 0x38, 0x36, 0x50, 0x34, 0x46, 0x4c, - 0x48, 0x0a, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x32, - 0x4d, 0x1e, 0x53, 0x0e, 0x04, 0x11, 0xb2, 0x05, - 0xf8, 0x58, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xfc, - 0x00, 0x44, 0x45, 0x4c, 0x4c, 0x20, 0x45, 0x31, - 0x37, 0x38, 0x57, 0x46, 0x50, 0x0a, 0x00, 0x78 + 0x04, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0a, 0x01, 0x04, 0x80, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; utility::string_t edid(edid_bytes, edid_bytes + sizeof(edid_bytes)); const auto output_id = impl::make_id(seed_id, nmos::types::output); + // Associate the last created video and audio Receivers with this Output std::vector receiver_ids; - int index = how_many - 1; // Make the last created Receiver IS-11 compatible + int index = how_many - 1; for (const auto& port : { impl::ports::video, impl::ports::audio }) { receiver_ids.push_back(impl::make_id(seed_id, nmos::types::receiver, port, index)); } auto output = nmos::experimental::make_streamcompatibility_output(output_id, false, boost::variant(edid), bst::nullopt, receiver_ids, model.settings); - impl::set_label_description(output, impl::ports::mux, 0); // The single Output consumes both video and audio signals + impl::set_label_description(output, impl::ports::mux, 0); // The single Output produces both video and audio signals if (!insert_resource_after(delay_millis, model.streamcompatibility_resources, std::move(output), gate)) return; for (const auto& receiver_id : receiver_ids) @@ -1309,48 +1312,49 @@ nmos::channelmapping_activation_handler make_node_implementation_channelmapping_ }; } -// Example Flow Compatibility Management API base EDID update callback to perform application-specific operations to apply updated Base EDID +// Example Stream Compatibility Management API Base EDID update callback to perform application-specific operations to apply updated Base EDID +// (e.g. providing the implementation with a parsed version of Base EDID) nmos::experimental::details::streamcompatibility_base_edid_put_handler make_node_implementation_streamcompatibility_base_edid_put_handler(slog::base_gate& gate) { return [&gate](const nmos::id& input_id, const utility::string_t& base_edid, bst::optional& base_edid_properties) { base_edid_properties = bst::nullopt; - slog::log(gate, SLOG_FLF) << "Base EDID updated for input " << input_id; + slog::log(gate, SLOG_FLF) << "Base EDID updated for Input " << input_id; }; } -// Example Flow Compatibility Management API base EDID delete callback to perform application-specific operations in the case Base EDID is deleted +// Example Stream Compatibility Management API Base EDID delete callback to perform application-specific operations on the Base EDID deletion nmos::experimental::details::streamcompatibility_base_edid_delete_handler make_node_implementation_streamcompatibility_base_edid_delete_handler(slog::base_gate& gate) { return [&gate](const nmos::id& input_id) { - slog::log(gate, SLOG_FLF) << "Base EDID deleted for input " << input_id; + slog::log(gate, SLOG_FLF) << "Base EDID deleted for Input " << input_id; }; } -// Example Flow Compatibility Management API callback to update effective EDID - captures streamcompatibility_resources by reference! +// Example Stream Compatibility Management API callback to update Effective EDID - captures streamcompatibility_resources by reference! nmos::experimental::details::streamcompatibility_effective_edid_setter make_node_implementation_effective_edid_setter(const nmos::resources& streamcompatibility_resources, slog::base_gate& gate) { return [&streamcompatibility_resources, &gate](const nmos::id& input_id, boost::variant& effective_edid, bst::optional& effective_edid_properties) { unsigned char edid_bytes[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, - 0x10, 0xac, 0x16, 0xd0, 0x48, 0x4c, 0x46, 0x34, - 0x1a, 0x12, 0x01, 0x04, 0x6a, 0x25, 0x17, 0x78, - 0xef, 0xb6, 0x90, 0xa6, 0x54, 0x51, 0x91, 0x25, - 0x17, 0x50, 0x54, 0xa5, 0x4b, 0x00, 0x81, 0x80, - 0x71, 0x4f, 0x95, 0x00, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xab, 0x22, - 0xa0, 0xa0, 0x50, 0x84, 0x1a, 0x30, 0x30, 0x20, - 0x36, 0x00, 0x72, 0xe6, 0x10, 0x00, 0x00, 0x1a, - 0x00, 0x00, 0x00, 0xff, 0x00, 0x47, 0x33, 0x34, - 0x30, 0x48, 0x38, 0x36, 0x50, 0x34, 0x46, 0x4c, - 0x48, 0x0a, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x32, - 0x4d, 0x1e, 0x53, 0x0e, 0x04, 0x11, 0xb2, 0x05, - 0xf8, 0x58, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xfc, - 0x00, 0x44, 0x45, 0x4c, 0x4c, 0x20, 0x45, 0x31, - 0x37, 0x38, 0x57, 0x46, 0x50, 0x0a, 0x00, 0x78 + 0x04, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0a, 0x01, 0x04, 0x80, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; effective_edid_properties = bst::nullopt; @@ -1383,11 +1387,11 @@ nmos::experimental::details::streamcompatibility_effective_edid_setter make_node effective_edid = utility::string_t(edid_bytes, edid_bytes + sizeof(edid_bytes)); } - slog::log(gate, SLOG_FLF) << "Effective EDID is set for input " << input_id; + slog::log(gate, SLOG_FLF) << "Effective EDID is set for Input " << input_id; }; } -// Example Flow Compatibility Management API callback to update active constraints +// Example Stream Compatibility Management API callback to update Active Constraints of a Sender nmos::experimental::details::streamcompatibility_active_constraints_put_handler make_node_implementation_active_constraints_handler(slog::base_gate& gate) { using web::json::value_of; From 2bcaba02034a5c9b9bb73ba64e72c7e864ba2f87 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sat, 14 May 2022 23:27:13 +0300 Subject: [PATCH 015/109] Sync Sender/Receiver states with IS-11 spec --- Development/nmos/api_utils.h | 6 ++--- Development/nmos/json_fields.h | 2 +- Development/nmos/streamcompatibility_api.cpp | 8 +++--- Development/nmos/streamcompatibility_api.h | 2 +- .../nmos/streamcompatibility_behaviour.cpp | 8 +++--- .../nmos/streamcompatibility_resources.cpp | 2 +- Development/nmos/streamcompatibility_state.h | 26 +++++++++---------- Development/third_party/is-11/README.md | 4 +-- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Development/nmos/api_utils.h b/Development/nmos/api_utils.h index c3c66fbdd..d83a79abe 100644 --- a/Development/nmos/api_utils.h +++ b/Development/nmos/api_utils.h @@ -50,7 +50,7 @@ namespace nmos const route_pattern channelmapping_api = make_route_pattern(U("api"), U("channelmapping")); // IS-09 System API (originally specified in JT-NM TR-1001-1:2018 Annex A) const route_pattern system_api = make_route_pattern(U("api"), U("system")); - // IS-11 Flow Compatibility Management API + // IS-11 Stream Compatibility Management API const route_pattern streamcompatibility_api = make_route_pattern(U("api"), U("streamcompatibility")); // API version pattern @@ -82,8 +82,8 @@ namespace nmos const route_pattern outputSubroute = make_route_pattern(U("outputSubroute"), U("properties|sourceid|channels|caps")); const route_pattern activationId = make_route_pattern(U("activationId"), U("[a-zA-Z0-9\\-_]+")); - // Flow Compatibility Management API - const route_pattern flowCompatibilityResourceType = make_route_pattern(U("resourceType"), U("senders|receivers|inputs|outputs")); + // Stream Compatibility Management API + const route_pattern streamCompatibilityResourceType = make_route_pattern(U("resourceType"), U("senders|receivers|inputs|outputs")); const route_pattern senderReceiverSubrouteType = make_route_pattern(U("senderReceiverSubroute"), U("inputs|outputs")); const route_pattern constraintsType = make_route_pattern(U("constraintsType"), U("active|supported")); const route_pattern edidType = make_route_pattern(U("edidType"), U("base|effective")); diff --git a/Development/nmos/json_fields.h b/Development/nmos/json_fields.h index 9efc1613e..700504470 100644 --- a/Development/nmos/json_fields.h +++ b/Development/nmos/json_fields.h @@ -230,7 +230,7 @@ namespace nmos const web::json::field_as_string hostname{ U("hostname") }; // hostname, ipv4 or ipv6 const web::json::field_as_integer port{ U("port") }; // 1..65535 - // IS-11 Flow Compatibility Management + // IS-11 Stream Compatibility Management // for streamcompatibility_api const web::json::field_as_array inputs{ U("inputs") }; diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index 3586fd3a3..5146d59b5 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -226,13 +226,13 @@ namespace nmos return pplx::task_from_result(true); }); - streamcompatibility_api.support(U("/") + nmos::patterns::flowCompatibilityResourceType.pattern + U("/?"), methods::GET, [&model, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::streamCompatibilityResourceType.pattern + U("/?"), methods::GET, [&model, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) { nmos::api_gate gate(gate_, req, parameters); auto lock = model.read_lock(); auto& resources = model.streamcompatibility_resources; - const string_t resourceType = parameters.at(nmos::patterns::flowCompatibilityResourceType.name); + const string_t resourceType = parameters.at(nmos::patterns::streamCompatibilityResourceType.name); const auto match = [&resourceType](const nmos::resources::value_type& resource) { return resource.type == nmos::type_from_resourceType(resourceType); }; @@ -266,12 +266,12 @@ namespace nmos return pplx::task_from_result(true); }); - streamcompatibility_api.support(U("/") + nmos::patterns::flowCompatibilityResourceType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::streamCompatibilityResourceType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) { auto lock = model.read_lock(); auto& resources = model.streamcompatibility_resources; - const string_t resourceType = parameters.at(nmos::patterns::flowCompatibilityResourceType.name); + const string_t resourceType = parameters.at(nmos::patterns::streamCompatibilityResourceType.name); const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); const std::pair id_type{ resourceId, nmos::type_from_resourceType(resourceType) }; diff --git a/Development/nmos/streamcompatibility_api.h b/Development/nmos/streamcompatibility_api.h index 13f815e0a..7731ce11f 100644 --- a/Development/nmos/streamcompatibility_api.h +++ b/Development/nmos/streamcompatibility_api.h @@ -6,7 +6,7 @@ #include "nmos/api_utils.h" #include "nmos/slog.h" -// Flow Compatibility Management API implementation +// Stream Compatibility Management API implementation // See https://github.com/AMWA-TV/is-11/blob/v1.0-dev/APIs/FlowCompatibilityManagementAPI.raml namespace nmos { diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index a628dea9b..e3f4aa98f 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -96,7 +96,7 @@ namespace nmos const auto session_description = sdp::parse_session_description(utility::us2s(sdp_data)); auto sdp_params = nmos::parse_session_description(session_description).first; - receiver_state = nmos::receiver_states::ok; + receiver_state = nmos::receiver_states::compliant_stream; try { @@ -104,12 +104,12 @@ namespace nmos } catch (const std::runtime_error& e) { - receiver_state = nmos::receiver_states::receiver_capabilities_violation; + receiver_state = nmos::receiver_states::non_compliant_stream; } } else { - receiver_state = nmos::receiver_states::no_transport_file; + receiver_state = nmos::receiver_states::unknown; } return receiver_state; @@ -227,7 +227,7 @@ namespace nmos auto streamcompatibility_receiver = find_resource(streamcompatibility_resources, streamcompatibility_receiver_id_type); if (streamcompatibility_resources.end() == streamcompatibility_receiver) throw std::logic_error("Matching IS-11 receiver not found"); - nmos::receiver_state receiver_state(nmos::receiver_states::no_transport_file); + nmos::receiver_state receiver_state(nmos::receiver_states::unknown); const std::pair connection_receiver_id_type{ receiver_id, nmos::types::receiver }; auto connection_receiver = find_resource(connection_resources, connection_receiver_id_type); diff --git a/Development/nmos/streamcompatibility_resources.cpp b/Development/nmos/streamcompatibility_resources.cpp index f0fddd814..3a9b3e862 100644 --- a/Development/nmos/streamcompatibility_resources.cpp +++ b/Development/nmos/streamcompatibility_resources.cpp @@ -85,7 +85,7 @@ namespace nmos using web::json::value_from_elements; auto receiver_status = value_of({ - { nmos::fields::state, nmos::receiver_states::no_transport_file.name }, + { nmos::fields::state, nmos::receiver_states::unknown.name }, }); auto endpoint_status = value_of({ diff --git a/Development/nmos/streamcompatibility_state.h b/Development/nmos/streamcompatibility_state.h index 8d12560da..5637d9886 100644 --- a/Development/nmos/streamcompatibility_state.h +++ b/Development/nmos/streamcompatibility_state.h @@ -5,37 +5,37 @@ namespace nmos { - // Flow Compatibility Sender states + // Stream Compatibility Sender states // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#state-of-sender // and https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/sender-status.html DEFINE_STRING_ENUM(sender_state) namespace sender_states { - const sender_state no_signal{ U("No Signal") }; - const sender_state awaiting_signal{ U("Awaiting Signal") }; - const sender_state unconstrained{ U("Unconstrained") }; - const sender_state constrained{ U("Constrained") }; - const sender_state active_constraints_violation{ U("Active Constraints Violation") }; + const sender_state no_signal{ U("no_signal") }; + const sender_state awaiting_signal{ U("awaiting_signal") }; + const sender_state unconstrained{ U("unconstrained") }; + const sender_state constrained{ U("constrained") }; + const sender_state active_constraints_violation{ U("active_constraints_violation") }; } // State of the signal coming into a Sender from its Inputs. It is used to set Sender Status properly. DEFINE_STRING_ENUM(signal_state) namespace signal_states { - const signal_state no_signal{ U("No Signal") }; - const signal_state awaiting_signal{ U("Awaiting Signal") }; - const signal_state signal_is_present{ U("Signal is Present") }; + const sender_state no_signal{ U("no_signal") }; + const sender_state awaiting_signal{ U("awaiting_signal") }; + const signal_state signal_is_present{ U("signal_is_present") }; } - // Flow Compatibility Receiver states + // Stream Compatibility Receiver states // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#state-of-receiver // and https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/receiver-status.html DEFINE_STRING_ENUM(receiver_state) namespace receiver_states { - const receiver_state no_transport_file{ U("No Transport File") }; - const receiver_state ok{ U("OK") }; - const receiver_state receiver_capabilities_violation{ U("Receiver Capabilities Violation") }; + const receiver_state unknown{ U("unknown") }; + const receiver_state compliant_stream{ U("compliant_stream") }; + const receiver_state non_compliant_stream{ U("non_compliant_stream") }; } } diff --git a/Development/third_party/is-11/README.md b/Development/third_party/is-11/README.md index 5b244c614..1e79bca2b 100644 --- a/Development/third_party/is-11/README.md +++ b/Development/third_party/is-11/README.md @@ -1,6 +1,6 @@ -# AMWA IS-11 NMOS Flow Compatibility Management +# AMWA IS-11 NMOS Stream Compatibility Management -This directory contains files from the [AMWA IS-11 NMOS Flow Compatibility Management](https://github.com/AMWA-TV/is-11), in particular tagged versions of the JSON schemas used by the API specifications. +This directory contains files from the [AMWA IS-11 NMOS Stream Compatibility Management](https://github.com/AMWA-TV/is-11), in particular tagged versions of the JSON schemas used by the API specifications. Original source code: From 16941f76421a04836e8cf91f42ecd17bf6416201 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sun, 15 May 2022 00:45:45 +0300 Subject: [PATCH 016/109] Prevent IS-11 restrictions violation --- Development/nmos/connection_api.cpp | 19 ++++++ .../nmos/streamcompatibility_behaviour.cpp | 65 ++++++++++++++----- 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/Development/nmos/connection_api.cpp b/Development/nmos/connection_api.cpp index 385a31c76..4799fbc37 100644 --- a/Development/nmos/connection_api.cpp +++ b/Development/nmos/connection_api.cpp @@ -15,6 +15,7 @@ #include "nmos/model.h" #include "nmos/sdp_utils.h" #include "nmos/slog.h" +#include "nmos/streamcompatibility_state.h" #include "nmos/transport.h" #include "nmos/thread_utils.h" #include "nmos/version.h" @@ -455,6 +456,24 @@ namespace nmos // find resource again just in case, since waiting releases and reacquires the lock resource = find_resource(resources, id_type); } + + // "At any time if State of an active Sender becomes active_constraints_violation, the Sender MUST become inactive. + // An inactive Sender in this state MUST NOT allow activations. + // At any time if State of an active Receiver becomes non_compliant_stream, the Receiver SHOULD become inactive. + // An inactive Receiver in this state SHOULD NOT allow activations." + // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#preventing-restrictions-violation + auto streamcompatibility_resource = find_resource(model.streamcompatibility_resources, id_type); + if (model.streamcompatibility_resources.end() != streamcompatibility_resource) + { + auto resource_state = nmos::fields::state(nmos::fields::status(nmos::fields::endpoint_status(streamcompatibility_resource->data))); + + if (resource_state == nmos::sender_states::active_constraints_violation.name || resource_state == nmos::receiver_states::non_compliant_stream.name) + { + slog::log(gate, SLOG_FLF) << "Rejecting PATCH request for " << id_type << " due to its state: " << resource_state; + + return details::make_connection_resource_patch_error_response(status_codes::InternalError); + } + } } else { diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index e3f4aa98f..3496074fa 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -5,6 +5,8 @@ #include #include #include +#include "nmos/activation_mode.h" +#include "nmos/activation_utils.h" #include "nmos/capabilities.h" // for constraint_sets #include "nmos/constraints.h" #include "nmos/id.h" @@ -120,7 +122,7 @@ namespace nmos using web::json::value; using web::json::value_of; - auto lock = model.write_lock(); // in order to update the resources + auto lock = model.write_lock(); // in order to update state of Sender/Receiver auto& node_resources = model.node_resources; auto& connection_resources = model.connection_resources; auto& streamcompatibility_resources = model.streamcompatibility_resources; @@ -135,7 +137,7 @@ namespace nmos auto streamcompatibility_senders_ids = get_resources_ids(streamcompatibility_resources, nmos::types::sender); auto streamcompatibility_receivers_ids = get_resources_ids(streamcompatibility_resources, nmos::types::receiver); - // find IS-11 recently updated Senders and Senders with recently updated Flow or Source + // find Senders with recently updated IS-11 properties, associated Flow or Source for (const nmos::id& sender_id : streamcompatibility_senders_ids) { try @@ -144,15 +146,15 @@ namespace nmos const std::pair sender_id_type{ sender_id, nmos::types::sender }; auto sender = find_resource(node_resources, sender_id_type); - if (node_resources.end() == sender) throw std::logic_error("Matching IS-04 sender not found"); + if (node_resources.end() == sender) throw std::logic_error("Matching IS-04 Sender not found"); const std::pair flow_id_type{ nmos::fields::flow_id(sender->data).as_string(), nmos::types::flow }; auto flow = find_resource(node_resources, flow_id_type); - if (node_resources.end() == flow) throw std::logic_error("Matching IS-04 flow not found"); + if (node_resources.end() == flow) throw std::logic_error("Matching IS-04 Flow not found"); const std::pair source_id_type{ nmos::fields::source_id(flow->data), nmos::types::source }; auto source = find_resource(node_resources, source_id_type); - if (node_resources.end() == source) throw std::logic_error("Matching IS-04 source not found"); + if (node_resources.end() == source) throw std::logic_error("Matching IS-04 Source not found"); updated = most_recent_update < sender->updated || most_recent_update < flow->updated || @@ -160,11 +162,13 @@ namespace nmos if (updated) { - slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " or its Flow or Source has been updated recently and Sender State is being updated as well"; + slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " or its Flow or Source has been updated recently and the state of this Sender is being updated as well"; - const std::pair streamcompatibility_sender_id_type{ sender_id, nmos::types::sender }; - auto streamcompatibility_sender = find_resource(streamcompatibility_resources, streamcompatibility_sender_id_type); - if (streamcompatibility_resources.end() == streamcompatibility_sender) throw std::logic_error("Matching IS-11 sender not found"); + auto streamcompatibility_sender = find_resource(streamcompatibility_resources, sender_id_type); + if (streamcompatibility_resources.end() == streamcompatibility_sender) throw std::logic_error("Matching IS-11 Sender not found"); + + auto connection_sender = find_resource(connection_resources, sender_id_type); + if (connection_resources.end() == connection_sender) throw std::logic_error("Matching IS-05 Sender not found"); nmos::signal_state signal_state(nmos::fields::signal_state(nmos::fields::endpoint_status(streamcompatibility_sender->data))); nmos::sender_state sender_state(signal_state.name); @@ -172,11 +176,6 @@ namespace nmos if (signal_state == nmos::signal_states::signal_is_present) { const auto& constraint_sets = nmos::fields::constraint_sets(nmos::fields::active_constraint_sets(nmos::fields::endpoint_active_constraints(streamcompatibility_sender->data))).as_array(); - - const std::pair connection_sender_id_type{ sender_id, nmos::types::sender }; - auto connection_sender = find_resource(connection_resources, connection_sender_id_type); - if (connection_resources.end() == connection_sender) throw std::logic_error("Matching IS-05 sender not found"); - auto& transport_file = nmos::fields::endpoint_transportfile(connection_sender->data); slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " is being validated with its Flow, Source and transport file"; @@ -197,11 +196,29 @@ namespace nmos update_version(node_resources, sender_id, updated_timestamp); } + + if (sender_state == nmos::sender_states::active_constraints_violation) + { + slog::log(gate, SLOG_FLF) << "Stopping Sender " << sender->id; + web::json::value merged; + nmos::modify_resource(connection_resources, sender_id, [&merged](nmos::resource& connection_resource) + { + connection_resource.data[nmos::fields::version] = web::json::value::string(nmos::make_version()); + merged = nmos::fields::endpoint_staged(connection_resource.data); + merged[nmos::fields::master_enable] = value::boolean(false); + auto activation = nmos::make_activation(); + activation[nmos::fields::mode] = value::string(nmos::activation_modes::activate_immediate.name); + details::merge_activation(merged[nmos::fields::activation], activation, nmos::tai_now()); + connection_resource.data[nmos::fields::endpoint_staged] = merged; + }); + + details::handle_immediate_activation_pending(model, lock, sender_id_type, merged[nmos::fields::activation], gate); + } } } catch (const std::exception& e) { - slog::log(gate, SLOG_FLF) << "Updating sender status for " << sender_id << " raised exception: " << e.what(); + slog::log(gate, SLOG_FLF) << "An exception appeared while updating the state of Sender " << sender_id << ": " << e.what(); continue; } } @@ -251,6 +268,24 @@ namespace nmos update_version(node_resources, receiver_id, updated_timestamp); } + + if (receiver_state == nmos::receiver_states::non_compliant_stream) + { + slog::log(gate, SLOG_FLF) << "Stopping Receiver " << receiver->id; + web::json::value merged; + nmos::modify_resource(connection_resources, receiver_id, [&merged](nmos::resource& connection_resource) + { + connection_resource.data[nmos::fields::version] = web::json::value::string(nmos::make_version()); + merged = nmos::fields::endpoint_staged(connection_resource.data); + merged[nmos::fields::master_enable] = value::boolean(false); + auto activation = nmos::make_activation(); + activation[nmos::fields::mode] = value::string(nmos::activation_modes::activate_immediate.name); + details::merge_activation(merged[nmos::fields::activation], activation, nmos::tai_now()); + connection_resource.data[nmos::fields::endpoint_staged] = merged; + }); + + details::handle_immediate_activation_pending(model, lock, receiver_id_type, merged[nmos::fields::activation], gate); + } } } catch (const std::exception& e) From f2b89186a2aed5fcec6d1c53db88105ea41807fb Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Wed, 1 Jun 2022 00:50:36 +0300 Subject: [PATCH 017/109] Fix is_subconstraint(), add unit tests --- Development/cmake/NmosCppTest.cmake | 1 + Development/nmos/constraints.cpp | 22 +++-- Development/nmos/test/constraints_test.cpp | 99 ++++++++++++++++++++++ 3 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 Development/nmos/test/constraints_test.cpp diff --git a/Development/cmake/NmosCppTest.cmake b/Development/cmake/NmosCppTest.cmake index e059a4ad6..93a48579b 100644 --- a/Development/cmake/NmosCppTest.cmake +++ b/Development/cmake/NmosCppTest.cmake @@ -41,6 +41,7 @@ set(NMOS_CPP_TEST_MDNS_TEST_HEADERS set(NMOS_CPP_TEST_NMOS_TEST_SOURCES nmos/test/api_utils_test.cpp nmos/test/channels_test.cpp + nmos/test/constraints_test.cpp nmos/test/did_sdid_test.cpp nmos/test/event_type_test.cpp nmos/test/json_validator_test.cpp diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index e46d77932..c91fc5eba 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -112,15 +112,17 @@ namespace nmos return false; } } - // subconstraint enum values should match constraint - const auto& subconstraint_enum_values = nmos::fields::constraint_enum(subconstraint).as_array(); - if (subconstraint_enum_values.end() == std::find_if(subconstraint_enum_values.begin(), subconstraint_enum_values.end(), [&parse, &constraint](const web::json::value& enum_value) - { - return details::match_constraint(parse(enum_value), constraint, parse); - })) + if (subconstraint.has_field(nmos::fields::constraint_enum)) { - return false; + const auto& subconstraint_enum_values = nmos::fields::constraint_enum(subconstraint).as_array(); + if (subconstraint_enum_values.end() == std::find_if(subconstraint_enum_values.begin(), subconstraint_enum_values.end(), [&parse, &constraint](const web::json::value& enum_value) + { + return details::match_constraint(parse(enum_value), constraint, parse); + })) + { + return false; + } } return true; } @@ -199,6 +201,12 @@ namespace nmos }; #undef CAPS_ARGS + // Constraint Set B is a subset of Constraint Set A if all of Parameter Constraints of Constraint Set B, except for meta, are present in Constraint Set A and each Parameter Constraint of Constraint Set B, except for meta, is a subconstraint of the according Parameter Constraint of Constraint Set A. + // Constraint B is a subconstraint of a Constraint A if: + + // 1. Constraint B has enum keyword when Constraint A has it and enum of Constraint B is a subset of enum of Constraint A + // 2. Constraint B has enum or minimum keyword when Constraint A has minimum keyword and allowed values for Constraint B are less than allowed values for Constraint A + // 3. Constraint B has enum or maximum keyword when Constraint A has maximum keyword and allowed values for Constraint B are greater than allowed values for Constraint A bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset) { using web::json::value; diff --git a/Development/nmos/test/constraints_test.cpp b/Development/nmos/test/constraints_test.cpp new file mode 100644 index 000000000..7dfd95ba9 --- /dev/null +++ b/Development/nmos/test/constraints_test.cpp @@ -0,0 +1,99 @@ +#include "nmos/capabilities.h" +#include "nmos/constraints.h" +#include "nmos/json_fields.h" +#include "nmos/sdp_utils.h" + +#include "bst/test/test.h" + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testLessConstraints) +{ + { + using web::json::value_of; + using nmos::experimental::is_constraint_subset; + + auto constraint_set = value_of({ + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25, nmos::rates::rate29_97 }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, + { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ sdp::samplings::YCbCr_4_2_2.name }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_bff.name, nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, + { nmos::caps::format::colorspace, nmos::make_caps_string_constraint({ sdp::colorimetries::BT2020.name, sdp::colorimetries::BT709.name }) }, + { nmos::caps::format::transfer_characteristic, nmos::make_caps_string_constraint({ sdp::transfer_characteristic_systems::SDR.name }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({}, 8, 12) }, + { nmos::caps::transport::st2110_21_sender_type, nmos::make_caps_string_constraint({ sdp::type_parameters::type_N.name }) } + }); + + auto constraint_subset = value_of({ + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25, nmos::rates::rate29_97 }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) } + }); + + BST_REQUIRE(is_constraint_subset(constraint_set, constraint_subset)); + BST_REQUIRE(!is_constraint_subset(constraint_subset, constraint_set)); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testRoundTrip) +{ + { + using web::json::value_of; + using nmos::experimental::is_constraint_subset; + + auto constraint_set = value_of({ + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25, nmos::rates::rate29_97 }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, + { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ sdp::samplings::YCbCr_4_2_2.name }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_bff.name, nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, + { nmos::caps::format::colorspace, nmos::make_caps_string_constraint({ sdp::colorimetries::BT2020.name, sdp::colorimetries::BT709.name }) }, + { nmos::caps::format::transfer_characteristic, nmos::make_caps_string_constraint({ sdp::transfer_characteristic_systems::SDR.name }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({}, 8, 12) }, + { nmos::caps::transport::st2110_21_sender_type, nmos::make_caps_string_constraint({ sdp::type_parameters::type_N.name }) } + }); + + BST_REQUIRE(is_constraint_subset(constraint_set, constraint_set)); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testSubconstraints) +{ + { + using web::json::value_of; + using nmos::experimental::is_constraint_subset; + + auto constraint_set = value_of({ + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25, nmos::rates::rate29_97 }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, + { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ sdp::samplings::YCbCr_4_2_2.name }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_bff.name, nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, + { nmos::caps::format::colorspace, nmos::make_caps_string_constraint({ sdp::colorimetries::BT2020.name, sdp::colorimetries::BT709.name }) }, + { nmos::caps::format::transfer_characteristic, nmos::make_caps_string_constraint({ sdp::transfer_characteristic_systems::SDR.name }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({}, 8, 12) }, + { nmos::caps::transport::st2110_21_sender_type, nmos::make_caps_string_constraint({ sdp::type_parameters::type_N.name }) } + }); + + auto constraint_subset = value_of({ + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25 }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, + { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ sdp::samplings::YCbCr_4_2_2.name }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, + { nmos::caps::format::colorspace, nmos::make_caps_string_constraint({ sdp::colorimetries::BT2020.name, sdp::colorimetries::BT709.name }) }, + { nmos::caps::format::transfer_characteristic, nmos::make_caps_string_constraint({ sdp::transfer_characteristic_systems::SDR.name }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({ 10 }) }, + { nmos::caps::transport::st2110_21_sender_type, nmos::make_caps_string_constraint({ sdp::type_parameters::type_N.name }) } + }); + + BST_REQUIRE(is_constraint_subset(constraint_set, constraint_subset)); + } +} From 66040270c83d8e42c8d7f25aa9acf0f4877f63e0 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Wed, 1 Jun 2022 01:27:51 +0300 Subject: [PATCH 018/109] Get rid of list of parameter constraints used for checking if a Constraint Set is a subset of another one --- Development/nmos/constraints.cpp | 88 +++----------------------------- 1 file changed, 6 insertions(+), 82 deletions(-) diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index c91fc5eba..dbcac6f73 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -76,8 +76,7 @@ namespace nmos }); } - template - bool is_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint, Parse parse) + bool is_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint) { // subconstraint should have enum if constraint has enum if (constraint.has_field(nmos::fields::constraint_enum) && !subconstraint.has_field(nmos::fields::constraint_enum)) @@ -98,7 +97,7 @@ namespace nmos { const auto& constraint_minimum = nmos::fields::constraint_minimum(constraint); const auto& subconstraint_minimum = nmos::fields::constraint_minimum(subconstraint); - if (parse(constraint_minimum) > parse(subconstraint_minimum)) + if (constraint_minimum > subconstraint_minimum) { return false; } @@ -107,7 +106,7 @@ namespace nmos { const auto& constraint_maximum = nmos::fields::constraint_maximum(constraint); const auto& subconstraint_maximum = nmos::fields::constraint_maximum(subconstraint); - if (parse(constraint_maximum) < parse(subconstraint_maximum)) + if (constraint_maximum < subconstraint_maximum) { return false; } @@ -116,9 +115,9 @@ namespace nmos if (subconstraint.has_field(nmos::fields::constraint_enum)) { const auto& subconstraint_enum_values = nmos::fields::constraint_enum(subconstraint).as_array(); - if (subconstraint_enum_values.end() == std::find_if(subconstraint_enum_values.begin(), subconstraint_enum_values.end(), [&parse, &constraint](const web::json::value& enum_value) + if (subconstraint_enum_values.end() == std::find_if(subconstraint_enum_values.begin(), subconstraint_enum_values.end(), [&constraint](const web::json::value& enum_value) { - return details::match_constraint(parse(enum_value), constraint, parse); + return details::match_constraint(enum_value, constraint, [](const web::json::value& v) { return v; }); })) { return false; @@ -127,80 +126,6 @@ namespace nmos return true; } - bool is_string_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint) - { - return is_subconstraint(constraint, subconstraint, [](const web::json::value& enum_value) - { - return enum_value.as_string(); - }); - } - - bool is_integer_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint) - { - return is_subconstraint(constraint, subconstraint, [](const web::json::value& enum_value) - { - return enum_value.as_integer(); - }); - } - - bool is_number_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint) - { - return is_subconstraint(constraint, subconstraint, [](const web::json::value& enum_value) - { - return enum_value.as_double(); - }); - } - - bool is_boolean_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint) - { - return is_subconstraint(constraint, subconstraint, [](const web::json::value& enum_value) - { - return enum_value.as_bool(); - }); - } - - bool is_rational_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint) - { - return is_subconstraint(constraint, subconstraint, [](const web::json::value& enum_value) - { - return nmos::parse_rational(enum_value); - }); - } - -#define CAPS_ARGS const web::json::value& constraint, const web::json::value& subconstraint - static const std::map> format_constraints - { - // General Constraints - - { nmos::caps::format::media_type, [](CAPS_ARGS) { return is_string_subconstraint(constraint, subconstraint); } }, - // hm, how best to match (rational) nmos::caps::format::grain_rate against (double) framerate e.g. for video/SMPTE2022-6? - // is 23.976 a match for 24000/1001? how about 23.98, or 23.9? or even 23?! - { nmos::caps::format::grain_rate, [](CAPS_ARGS) { return is_rational_subconstraint(constraint, subconstraint); } }, - - // Video Constraints - - { nmos::caps::format::frame_height, [](CAPS_ARGS) { return is_integer_subconstraint(constraint, subconstraint); } }, - { nmos::caps::format::frame_width, [](CAPS_ARGS) { return is_integer_subconstraint(constraint, subconstraint); } }, - { nmos::caps::format::color_sampling, [](CAPS_ARGS) { return is_string_subconstraint(constraint, subconstraint); } }, - { nmos::caps::format::interlace_mode, [](CAPS_ARGS) { return is_string_subconstraint(constraint, subconstraint); } }, - { nmos::caps::format::colorspace, [](CAPS_ARGS) { return is_string_subconstraint(constraint, subconstraint); } }, - { nmos::caps::format::transfer_characteristic, [](CAPS_ARGS) { return is_string_subconstraint(constraint, subconstraint); } }, - { nmos::caps::format::component_depth, [](CAPS_ARGS) { return is_integer_subconstraint(constraint, subconstraint); } }, - - // Audio Constraints - - { nmos::caps::format::channel_count, [](CAPS_ARGS) { return is_integer_subconstraint(constraint, subconstraint); } }, - { nmos::caps::format::sample_rate, [](CAPS_ARGS) { return is_rational_subconstraint(constraint, subconstraint); } }, - { nmos::caps::format::sample_depth, [](CAPS_ARGS) { return is_integer_subconstraint(constraint, subconstraint); } }, - - // Transport Constraints - - { nmos::caps::transport::packet_time, [](CAPS_ARGS) { return is_number_subconstraint(constraint, subconstraint); } }, - { nmos::caps::transport::max_packet_time, [](CAPS_ARGS) { return is_number_subconstraint(constraint, subconstraint); } }, - { nmos::caps::transport::st2110_21_sender_type, [](CAPS_ARGS) { return is_string_subconstraint(constraint, subconstraint); } }, - }; -#undef CAPS_ARGS - // Constraint Set B is a subset of Constraint Set A if all of Parameter Constraints of Constraint Set B, except for meta, are present in Constraint Set A and each Parameter Constraint of Constraint Set B, except for meta, is a subconstraint of the according Parameter Constraint of Constraint Set A. // Constraint B is a subconstraint of a Constraint A if: @@ -220,9 +145,8 @@ namespace nmos { if (subconstraint.first == nmos::caps::meta::label.key || subconstraint.first == nmos::caps::meta::preference.key) return false; - const auto& found = format_constraints.find(subconstraint.first); const auto& constraint = param_constraints_set.find(subconstraint.first); - return param_constraints_set.end() == constraint || (found != format_constraints.end() && !found->second(constraint->second, subconstraint.second)); + return param_constraints_set.end() == constraint || !is_subconstraint(constraint->second, subconstraint.second); }); } } From 583969d90e5f6b1c33be4dcb08c94e952fb58ac6 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Wed, 1 Jun 2022 01:34:12 +0300 Subject: [PATCH 019/109] Align bst::optional usage in IS-11 with the rest of code --- Development/nmos-cpp-node/node_implementation.cpp | 4 ++-- Development/nmos/streamcompatibility_api.cpp | 8 ++++---- Development/nmos/streamcompatibility_resources.cpp | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index eb45966b7..c16d133d2 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1378,9 +1378,9 @@ nmos::experimental::details::streamcompatibility_effective_edid_setter make_node } } - if (base_edid.has_value()) + if (base_edid) { - effective_edid = base_edid.value(); + effective_edid = *base_edid; } else { diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index 5146d59b5..b644794cd 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -99,9 +99,9 @@ namespace nmos { input.data[nmos::fields::endpoint_effective_edid] = boost::apply_visitor(edid_file_visitor(), effective_edid); - if (effective_edid_properties.has_value()) + if (effective_edid_properties) { - input.data[nmos::fields::effective_edid_properties] = effective_edid_properties.value(); + input.data[nmos::fields::effective_edid_properties] = *effective_edid_properties; } updated_timestamp = nmos::make_version(); @@ -640,9 +640,9 @@ namespace nmos // Update Base EDID in streamcompatibility_resources modify_resource(resources, resourceId, [&base_edid, &base_edid_properties, &updated_timestamp](nmos::resource& input) { - if (base_edid_properties.has_value()) + if (base_edid_properties) { - input.data[nmos::fields::base_edid_properties] = base_edid_properties.value(); + input.data[nmos::fields::base_edid_properties] = *base_edid_properties; } input.data[nmos::fields::endpoint_base_edid] = make_streamcompatibility_edid_endpoint(base_edid); diff --git a/Development/nmos/streamcompatibility_resources.cpp b/Development/nmos/streamcompatibility_resources.cpp index 3a9b3e862..7a053b5e8 100644 --- a/Development/nmos/streamcompatibility_resources.cpp +++ b/Development/nmos/streamcompatibility_resources.cpp @@ -166,9 +166,9 @@ namespace nmos data[nmos::fields::endpoint_effective_edid] = boost::apply_visitor(edid_file_visitor(), effective_edid); - if (effective_edid_properties.has_value()) + if (effective_edid_properties) { - data[nmos::fields::effective_edid_properties] = effective_edid_properties.value(); + data[nmos::fields::effective_edid_properties] = *effective_edid_properties; } data[nmos::fields::senders] = value_from_elements(senders); @@ -193,14 +193,14 @@ namespace nmos auto data = make_streamcompatibility_input_output_base(id, connected, true, settings); data[nmos::fields::receivers] = value_from_elements(receivers); - if (edid.has_value()) + if (edid) { - data[nmos::fields::endpoint_edid] = boost::apply_visitor(edid_file_visitor(), edid.value()); + data[nmos::fields::endpoint_edid] = boost::apply_visitor(edid_file_visitor(), *edid); } - if (edid_properties.has_value()) + if (edid_properties) { - data[nmos::fields::edid_properties] = edid_properties.value(); + data[nmos::fields::edid_properties] = *edid_properties; } return{ is11_versions::v1_0, types::output, std::move(data), id, false }; From b3cf776af1d618ae311f12f14b45c9091b952422 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Wed, 1 Jun 2022 02:42:56 +0300 Subject: [PATCH 020/109] Replace string literals with platform-independent version --- Development/nmos/streamcompatibility_api.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index b644794cd..9a7e0b650 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -448,10 +448,10 @@ namespace nmos throw std::logic_error("matching IS-04 resource not found"); } - if ("active" == constraintsType) { + if (U("active") == constraintsType) { set_reply(res, status_codes::OK, nmos::fields::active_constraint_sets(nmos::fields::endpoint_active_constraints(resource->data))); } - else if ("supported" == constraintsType) { + else if (U("supported") == constraintsType) { set_reply(res, status_codes::OK, nmos::fields::supported_param_constraints(resource->data)); } else { @@ -574,13 +574,13 @@ namespace nmos auto resource = find_resource(resources, id_type); if (resources.end() != resource) { - const auto filter = ("base" == edidType) ? + const auto filter = (U("base") == edidType) ? nmos::fields::endpoint_base_edid : nmos::fields::endpoint_effective_edid; auto& edid_endpoint = filter(resource->data); - slog::log(gate, SLOG_FLF) << edidType << " EDID requested for " << id_type; + slog::log(gate, SLOG_FLF) << "EDID requested for " << id_type << ": " << edidType; details::set_edid_endpoint_as_reply(res, id_type, edid_endpoint, gate); } From 3755db2d2fe41aed5c66720aeae7746d6c68e3f9 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Mon, 27 Jun 2022 12:54:51 +0300 Subject: [PATCH 021/109] Convert EDID binary stored as utility::string_t to std::vector --- Development/nmos/streamcompatibility_api.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index 9a7e0b650..59839ae28 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -30,8 +30,13 @@ namespace nmos if (!edid_binary.is_null()) { slog::log(gate, SLOG_FLF) << "Returning EDID binary for " << id_type; - - auto i_stream = concurrency::streams::bytestream::open_istream(edid_binary.as_string()); + // Convert wchar_t to uint8_t if utility::string_t consists of wide chars + auto edid_string = edid_binary.as_string(); + std::vector edid_vector; + std::transform(edid_string.begin(), edid_string.end(), std::back_inserter(edid_vector), [](utility::char_t char_element) { + return static_cast(char_element); + }); + auto i_stream = concurrency::streams::bytestream::open_istream(edid_vector); set_reply(res, web::http::status_codes::OK, i_stream); } else if (!edid_href.is_null()) From 986820e53f76098cf1d3f0ad6bc20b6f881444f0 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Mon, 27 Jun 2022 16:51:26 +0300 Subject: [PATCH 022/109] Sync IS-11 Status with the spec --- Development/nmos/connection_api.cpp | 2 +- Development/nmos/json_fields.h | 3 +- Development/nmos/streamcompatibility_api.cpp | 2 +- .../nmos/streamcompatibility_behaviour.cpp | 14 +++---- .../nmos/streamcompatibility_resources.cpp | 34 +++++------------ Development/nmos/streamcompatibility_state.h | 38 ++++++++++++------- 6 files changed, 45 insertions(+), 48 deletions(-) diff --git a/Development/nmos/connection_api.cpp b/Development/nmos/connection_api.cpp index 4799fbc37..717815871 100644 --- a/Development/nmos/connection_api.cpp +++ b/Development/nmos/connection_api.cpp @@ -465,7 +465,7 @@ namespace nmos auto streamcompatibility_resource = find_resource(model.streamcompatibility_resources, id_type); if (model.streamcompatibility_resources.end() != streamcompatibility_resource) { - auto resource_state = nmos::fields::state(nmos::fields::status(nmos::fields::endpoint_status(streamcompatibility_resource->data))); + auto resource_state = nmos::fields::state(nmos::fields::status(streamcompatibility_resource->data)); if (resource_state == nmos::sender_states::active_constraints_violation.name || resource_state == nmos::receiver_states::non_compliant_stream.name) { diff --git a/Development/nmos/json_fields.h b/Development/nmos/json_fields.h index 700504470..0e02a2ebd 100644 --- a/Development/nmos/json_fields.h +++ b/Development/nmos/json_fields.h @@ -251,10 +251,9 @@ namespace nmos const web::json::field_as_array parameter_constraints{ U("parameter_constraints") }; // for status - const web::json::field_as_value endpoint_status{ U("endpoint_status") }; // object - const web::json::field_as_string signal_state{ U("signal_state") }; const web::json::field_as_value status{ U("status") }; // object const web::json::field_as_string state{ U("state") }; + const web::json::field_as_string debug{ U("debug") }; // for EDID endpoints const web::json::field_as_value_or endpoint_base_edid{ U("endpoint_base_edid"), {} }; // object diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index 59839ae28..01b3c98f7 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -388,7 +388,7 @@ namespace nmos throw std::logic_error("matching IS-04 resource not found"); } - set_reply(res, status_codes::OK, nmos::fields::status(nmos::fields::endpoint_status(resource->data))); + set_reply(res, status_codes::OK, nmos::fields::status(resource->data)); } else if (nmos::details::is_erased_resource(resources, id_type)) { diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index 3496074fa..fa99d7e5e 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -170,10 +170,10 @@ namespace nmos auto connection_sender = find_resource(connection_resources, sender_id_type); if (connection_resources.end() == connection_sender) throw std::logic_error("Matching IS-05 Sender not found"); - nmos::signal_state signal_state(nmos::fields::signal_state(nmos::fields::endpoint_status(streamcompatibility_sender->data))); - nmos::sender_state sender_state(signal_state.name); + nmos::sender_state sender_state(nmos::fields::state(nmos::fields::status(streamcompatibility_sender->data))); - if (signal_state == nmos::signal_states::signal_is_present) + // Setting the State to any value except for "no_essence" or "awaiting_essence" triggers Active Constraints validation + if (sender_state != nmos::sender_states::no_essence && sender_state != nmos::sender_states::awaiting_essence) { const auto& constraint_sets = nmos::fields::constraint_sets(nmos::fields::active_constraint_sets(nmos::fields::endpoint_active_constraints(streamcompatibility_sender->data))).as_array(); auto& transport_file = nmos::fields::endpoint_transportfile(connection_sender->data); @@ -182,13 +182,13 @@ namespace nmos sender_state = validate_sender_resources(transport_file, flow->data, source->data, constraint_sets); } - if (nmos::fields::state(nmos::fields::status(nmos::fields::endpoint_status(streamcompatibility_sender->data))) != sender_state.name) + if (nmos::fields::state(nmos::fields::status(streamcompatibility_sender->data)) != sender_state.name) { utility::string_t updated_timestamp; modify_resource(streamcompatibility_resources, sender_id, [&sender_state, &updated_timestamp, &gate](nmos::resource& sender) { - nmos::fields::status(nmos::fields::endpoint_status(sender.data))[nmos::fields::state] = web::json::value::string(sender_state.name); + nmos::fields::status(sender.data)[nmos::fields::state] = web::json::value::string(sender_state.name); updated_timestamp = nmos::make_version(); sender.data[nmos::fields::version] = web::json::value::string(updated_timestamp); @@ -254,13 +254,13 @@ namespace nmos receiver_state = validate_receiver_resources(transport_file, receiver->data); - if (nmos::fields::state(nmos::fields::status(nmos::fields::endpoint_status(streamcompatibility_receiver->data))) != receiver_state.name) + if (nmos::fields::state(nmos::fields::status(streamcompatibility_receiver->data)) != receiver_state.name) { utility::string_t updated_timestamp; modify_resource(streamcompatibility_resources, receiver_id, [&receiver_state, &updated_timestamp, &gate](nmos::resource& receiver) { - nmos::fields::status(nmos::fields::endpoint_status(receiver.data))[nmos::fields::state] = web::json::value::string(receiver_state.name); + nmos::fields::status(receiver.data)[nmos::fields::state] = web::json::value::string(receiver_state.name); updated_timestamp = nmos::make_version(); receiver.data[nmos::fields::version] = web::json::value::string(updated_timestamp); diff --git a/Development/nmos/streamcompatibility_resources.cpp b/Development/nmos/streamcompatibility_resources.cpp index 7a053b5e8..44bc3b1b1 100644 --- a/Development/nmos/streamcompatibility_resources.cpp +++ b/Development/nmos/streamcompatibility_resources.cpp @@ -24,20 +24,6 @@ namespace nmos }); } - web::json::value make_streamcompatibility_sender_status_endpoint(nmos::sender_state sender_state, nmos::signal_state signal_state) - { - using web::json::value_of; - - auto sender_status = value_of({ - { nmos::fields::state, sender_state.name }, - }); - - return value_of({ - { nmos::fields::status, sender_status }, - { nmos::fields::signal_state, signal_state.name }, - }); - } - nmos::resource make_streamcompatibility_sender(const nmos::id& id, const std::vector& inputs, const std::vector& param_constraints) { using web::json::value; @@ -73,7 +59,7 @@ namespace nmos { nmos::fields::endpoint_active_constraints, make_streamcompatibility_active_constraints_endpoint(value::array()) }, { nmos::fields::inputs, value_from_elements(inputs) }, { nmos::fields::supported_param_constraints, supported_param_constraints }, - { nmos::fields::endpoint_status, make_streamcompatibility_sender_status_endpoint(nmos::sender_states::unconstrained, nmos::signal_states::signal_is_present) }, + { nmos::fields::status, value_of({ { nmos::fields::state, nmos::sender_states::unconstrained.name } }) }, }); return{ is11_versions::v1_0, types::sender, std::move(data), id, false }; @@ -84,19 +70,11 @@ namespace nmos using web::json::value_of; using web::json::value_from_elements; - auto receiver_status = value_of({ - { nmos::fields::state, nmos::receiver_states::unknown.name }, - }); - - auto endpoint_status = value_of({ - { nmos::fields::status, receiver_status }, - }); - auto data = value_of({ { nmos::fields::id, id }, { nmos::fields::device_id, U("these are not the droids you are looking for") }, { nmos::fields::outputs, value_from_elements(outputs) }, - { nmos::fields::endpoint_status, endpoint_status }, + { nmos::fields::status, value_of({ { nmos::fields::state, nmos::receiver_states::unknown.name } }) }, }); return{ is11_versions::v1_0, types::receiver, std::move(data), id, false }; @@ -146,9 +124,11 @@ namespace nmos nmos::resource make_streamcompatibility_input(const nmos::id& id, bool connected, const std::vector& senders, const nmos::settings& settings) { using web::json::value_from_elements; + using web::json::value_of; auto data = make_streamcompatibility_input_output_base(id, connected, false, settings); data[nmos::fields::senders] = value_from_elements(senders); + data[nmos::fields::status] = value_of({ { nmos::fields::state, nmos::input_states::signal_present.name } }); return{ is11_versions::v1_0, types::input, std::move(data), id, false }; } @@ -156,6 +136,7 @@ namespace nmos nmos::resource make_streamcompatibility_input(const nmos::id& id, bool connected, bool base_edid_changeable, const boost::variant& effective_edid, const bst::optional& effective_edid_properties, const std::vector& senders, const nmos::settings& settings) { using web::json::value_from_elements; + using web::json::value_of; auto data = make_streamcompatibility_input_output_base(id, connected, true, settings); @@ -172,6 +153,7 @@ namespace nmos } data[nmos::fields::senders] = value_from_elements(senders); + data[nmos::fields::status] = value_of({ { nmos::fields::state, nmos::input_states::signal_present.name } }); return{ is11_versions::v1_0, types::input, std::move(data), id, false }; } @@ -179,9 +161,11 @@ namespace nmos nmos::resource make_streamcompatibility_output(const nmos::id& id, bool connected, const std::vector& receivers, const nmos::settings& settings) { using web::json::value_from_elements; + using web::json::value_of; auto data = make_streamcompatibility_input_output_base(id, connected, false, settings); data[nmos::fields::receivers] = value_from_elements(receivers); + data[nmos::fields::status] = value_of({ { nmos::fields::state, nmos::output_states::signal_present.name } }); return{ is11_versions::v1_0, types::output, std::move(data), id, false }; } @@ -189,9 +173,11 @@ namespace nmos nmos::resource make_streamcompatibility_output(const nmos::id& id, bool connected, const bst::optional>& edid, const bst::optional& edid_properties, const std::vector& receivers, const nmos::settings& settings) { using web::json::value_from_elements; + using web::json::value_of; auto data = make_streamcompatibility_input_output_base(id, connected, true, settings); data[nmos::fields::receivers] = value_from_elements(receivers); + data[nmos::fields::status] = value_of({ { nmos::fields::state, nmos::output_states::signal_present.name } }); if (edid) { diff --git a/Development/nmos/streamcompatibility_state.h b/Development/nmos/streamcompatibility_state.h index 5637d9886..4fd36a547 100644 --- a/Development/nmos/streamcompatibility_state.h +++ b/Development/nmos/streamcompatibility_state.h @@ -5,30 +5,42 @@ namespace nmos { + // Stream Compatibility Input states + // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#status-of-input + // and https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/input.html + DEFINE_STRING_ENUM(input_state) + namespace input_states + { + const input_state no_signal{ U("no_signal") }; + const input_state awaiting_signal{ U("awaiting_signal") }; + const input_state signal_present{ U("signal_present") }; + } + + // Stream Compatibility Output states + // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#status-of-output + // and https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/output.html + DEFINE_STRING_ENUM(output_state) + namespace output_states + { + const output_state no_signal{ U("no_signal") }; + const output_state signal_present{ U("signal_present") }; + } + // Stream Compatibility Sender states - // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#state-of-sender + // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#status-of-sender // and https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/sender-status.html DEFINE_STRING_ENUM(sender_state) namespace sender_states { - const sender_state no_signal{ U("no_signal") }; - const sender_state awaiting_signal{ U("awaiting_signal") }; + const sender_state no_essence{ U("no_essence") }; + const sender_state awaiting_essence{ U("awaiting_essence") }; const sender_state unconstrained{ U("unconstrained") }; const sender_state constrained{ U("constrained") }; const sender_state active_constraints_violation{ U("active_constraints_violation") }; } - // State of the signal coming into a Sender from its Inputs. It is used to set Sender Status properly. - DEFINE_STRING_ENUM(signal_state) - namespace signal_states - { - const sender_state no_signal{ U("no_signal") }; - const sender_state awaiting_signal{ U("awaiting_signal") }; - const signal_state signal_is_present{ U("signal_is_present") }; - } - // Stream Compatibility Receiver states - // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#state-of-receiver + // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#status-of-receiver // and https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/receiver-status.html DEFINE_STRING_ENUM(receiver_state) namespace receiver_states From c8f111b4eba7a78c807aa013236b551783d97da2 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Wed, 6 Jul 2022 14:15:43 +0300 Subject: [PATCH 023/109] Make Sender validator of streamcompatibility_behaviour_thread customizable --- .../nmos-cpp-node/node_implementation.cpp | 9 +++++++- Development/nmos/node_server.cpp | 3 ++- Development/nmos/node_server.h | 3 +++ .../nmos/streamcompatibility_behaviour.cpp | 7 +++++-- .../nmos/streamcompatibility_behaviour.h | 21 ++++++++++++++++++- 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index c16d133d2..e42980096 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1440,6 +1440,12 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler }; } +nmos::experimental::details::streamcompatibility_sender_validator make_node_implementation_sender_validator() +{ + // this example uses the default sender validator explicitly + return &nmos::experimental::validate_sender_resources; +} + namespace impl { nmos::interlace_mode get_interlace_mode(const nmos::settings& settings) @@ -1561,5 +1567,6 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode .on_base_edid_changed(make_node_implementation_streamcompatibility_base_edid_put_handler(gate)) .on_base_edid_deleted(make_node_implementation_streamcompatibility_base_edid_delete_handler(gate)) .on_set_effective_edid(make_node_implementation_effective_edid_setter(model.streamcompatibility_resources, gate)) - .on_active_constraints_changed(make_node_implementation_active_constraints_handler(gate)); + .on_active_constraints_changed(make_node_implementation_active_constraints_handler(gate)) + .on_validate_sender_against_active_constraints(make_node_implementation_sender_validator()); } diff --git a/Development/nmos/node_server.cpp b/Development/nmos/node_server.cpp index 051ce8d5d..e84f285bd 100644 --- a/Development/nmos/node_server.cpp +++ b/Development/nmos/node_server.cpp @@ -106,13 +106,14 @@ namespace nmos auto set_transportfile = node_implementation.set_transportfile; auto connection_activated = node_implementation.connection_activated; auto channelmapping_activated = node_implementation.channelmapping_activated; + auto validate_sender = node_implementation.validate_sender; node_server.thread_functions.assign({ [&, load_ca_certificates, registration_changed] { nmos::node_behaviour_thread(node_model, load_ca_certificates, registration_changed, gate); }, [&] { nmos::send_events_ws_messages_thread(events_ws_listener, node_model, events_ws_api.second, gate); }, [&] { nmos::erase_expired_events_resources_thread(node_model, gate); }, [&, resolve_auto, set_transportfile, connection_activated] { nmos::connection_activation_thread(node_model, resolve_auto, set_transportfile, connection_activated, gate); }, [&, channelmapping_activated] { nmos::channelmapping_activation_thread(node_model, channelmapping_activated, gate); }, - [&] { nmos::experimental::streamcompatibility_behaviour_thread(node_model, gate); } + [&, validate_sender] { nmos::experimental::streamcompatibility_behaviour_thread(node_model, validate_sender, gate); } }); auto system_changed = node_implementation.system_changed; diff --git a/Development/nmos/node_server.h b/Development/nmos/node_server.h index fd9dbecb6..b58d082e7 100644 --- a/Development/nmos/node_server.h +++ b/Development/nmos/node_server.h @@ -7,6 +7,7 @@ #include "nmos/connection_api.h" #include "nmos/connection_activation.h" #include "nmos/streamcompatibility_api.h" +#include "nmos/streamcompatibility_behaviour.h" #include "nmos/node_behaviour.h" #include "nmos/node_system_behaviour.h" #include "nmos/ocsp_response_handler.h" @@ -62,6 +63,7 @@ namespace nmos node_implementation& on_base_edid_deleted(nmos::experimental::details::streamcompatibility_base_edid_delete_handler base_edid_deleted) { this->base_edid_deleted = std::move(base_edid_deleted); return *this; } node_implementation& on_set_effective_edid(nmos::experimental::details::streamcompatibility_effective_edid_setter set_effective_edid) { this->set_effective_edid = std::move(set_effective_edid); return *this; } node_implementation& on_active_constraints_changed(nmos::experimental::details::streamcompatibility_active_constraints_put_handler active_constraints_changed) { this->active_constraints_changed = std::move(active_constraints_changed); return *this; } + node_implementation& on_validate_sender_against_active_constraints(nmos::experimental::details::streamcompatibility_sender_validator validate_sender) { this->validate_sender = std::move(validate_sender); return *this; } // deprecated, use on_validate_connection_resource_patch node_implementation& on_validate_merged(nmos::details::connection_resource_patch_validator validate_merged) { return on_validate_connection_resource_patch(std::move(validate_merged)); } @@ -96,6 +98,7 @@ namespace nmos nmos::experimental::details::streamcompatibility_base_edid_delete_handler base_edid_deleted; nmos::experimental::details::streamcompatibility_effective_edid_setter set_effective_edid; nmos::experimental::details::streamcompatibility_active_constraints_put_handler active_constraints_changed; + nmos::experimental::details::streamcompatibility_sender_validator validate_sender; }; // Construct a server instance for an NMOS Node, implementing the IS-04 Node API, IS-05 Connection API, IS-07 Events API diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index fa99d7e5e..a2e99ae80 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -117,7 +117,7 @@ namespace nmos return receiver_state; } - void streamcompatibility_behaviour_thread(nmos::node_model& model, slog::base_gate& gate) + void streamcompatibility_behaviour_thread(nmos::node_model& model, details::streamcompatibility_sender_validator validate_sender, slog::base_gate& gate) { using web::json::value; using web::json::value_of; @@ -179,7 +179,10 @@ namespace nmos auto& transport_file = nmos::fields::endpoint_transportfile(connection_sender->data); slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " is being validated with its Flow, Source and transport file"; - sender_state = validate_sender_resources(transport_file, flow->data, source->data, constraint_sets); + if (validate_sender) + { + sender_state = validate_sender(transport_file, flow->data, source->data, constraint_sets); + } } if (nmos::fields::state(nmos::fields::status(streamcompatibility_sender->data)) != sender_state.name) diff --git a/Development/nmos/streamcompatibility_behaviour.h b/Development/nmos/streamcompatibility_behaviour.h index 498a0a569..75c83e4db 100644 --- a/Development/nmos/streamcompatibility_behaviour.h +++ b/Development/nmos/streamcompatibility_behaviour.h @@ -1,18 +1,37 @@ #ifndef NMOS_FLOWCOMPATIBILITY_BEHAVIOUR_H #define NMOS_FLOWCOMPATIBILITY_BEHAVIOUR_H +#include + +#include "nmos/streamcompatibility_state.h" + namespace slog { class base_gate; } +namespace web +{ + namespace json + { + class value; + class array; + } +} + namespace nmos { struct node_model; namespace experimental { - void streamcompatibility_behaviour_thread(nmos::node_model& model, slog::base_gate& gate); + namespace details + { + typedef std::function streamcompatibility_sender_validator; + } + + nmos::sender_state validate_sender_resources(const web::json::value& transport_file, const web::json::value& flow, const web::json::value& source, const web::json::array& constraint_sets); + void streamcompatibility_behaviour_thread(nmos::node_model& model, details::streamcompatibility_sender_validator validate_sender, slog::base_gate& gate); } } From 278a80253b2ff01d7a1bfdf22f02c589cf9bbe65 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Wed, 6 Jul 2022 14:16:38 +0300 Subject: [PATCH 024/109] streamcompatibility_behaviour: make namespaces full --- Development/nmos/node_server.cpp | 2 +- Development/nmos/streamcompatibility_behaviour.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Development/nmos/node_server.cpp b/Development/nmos/node_server.cpp index e84f285bd..431e57796 100644 --- a/Development/nmos/node_server.cpp +++ b/Development/nmos/node_server.cpp @@ -63,7 +63,7 @@ namespace nmos // Configure the Channel Mapping API node_server.api_routers[{ {}, nmos::fields::channelmapping_port(node_model.settings) }].mount({}, nmos::make_channelmapping_api(node_model, node_implementation.validate_map, gate)); - // Configure the Flow Compatibility API + // Configure the Stream Compatibility API node_server.api_routers[{ {}, nmos::fields::streamcompatibility_port(node_model.settings) }].mount({}, nmos::experimental::make_streamcompatibility_api(node_model, node_implementation.base_edid_changed, node_implementation.base_edid_deleted, node_implementation.set_effective_edid, node_implementation.active_constraints_changed, gate)); auto& events_ws_api = node_server.ws_handlers[{ {}, nmos::fields::events_ws_port(node_model.settings) }]; diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index a2e99ae80..848043d4e 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -72,8 +72,8 @@ namespace nmos const auto session_description = sdp::parse_session_description(utility::us2s(sdp_data)); auto sdp_params = nmos::parse_session_description(session_description).first; - const auto format_params = details::get_format_parameters(sdp_params); - const auto sdp_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return nmos::details::match_sdp_parameters_constraint_set(details::format_constraints, sdp_params, format_params, constraint_set); }); + const auto format_params = nmos::details::get_format_parameters(sdp_params); + const auto sdp_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return nmos::details::match_sdp_parameters_constraint_set(nmos::details::format_constraints, sdp_params, format_params, constraint_set); }); constrained = constrained && constraint_sets.end() != sdp_found; } @@ -211,11 +211,11 @@ namespace nmos merged[nmos::fields::master_enable] = value::boolean(false); auto activation = nmos::make_activation(); activation[nmos::fields::mode] = value::string(nmos::activation_modes::activate_immediate.name); - details::merge_activation(merged[nmos::fields::activation], activation, nmos::tai_now()); + nmos::details::merge_activation(merged[nmos::fields::activation], activation, nmos::tai_now()); connection_resource.data[nmos::fields::endpoint_staged] = merged; }); - details::handle_immediate_activation_pending(model, lock, sender_id_type, merged[nmos::fields::activation], gate); + nmos::details::handle_immediate_activation_pending(model, lock, sender_id_type, merged[nmos::fields::activation], gate); } } } @@ -283,11 +283,11 @@ namespace nmos merged[nmos::fields::master_enable] = value::boolean(false); auto activation = nmos::make_activation(); activation[nmos::fields::mode] = value::string(nmos::activation_modes::activate_immediate.name); - details::merge_activation(merged[nmos::fields::activation], activation, nmos::tai_now()); + nmos::details::merge_activation(merged[nmos::fields::activation], activation, nmos::tai_now()); connection_resource.data[nmos::fields::endpoint_staged] = merged; }); - details::handle_immediate_activation_pending(model, lock, receiver_id_type, merged[nmos::fields::activation], gate); + nmos::details::handle_immediate_activation_pending(model, lock, receiver_id_type, merged[nmos::fields::activation], gate); } } } From 48363645421ef07ddc917fb4bcf40c8d4ce5f9a0 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Thu, 21 Jul 2022 01:13:30 +0300 Subject: [PATCH 025/109] is_subconstraint(): fix the rational case --- Development/nmos/constraints.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index dbcac6f73..90a47c931 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -117,6 +117,10 @@ namespace nmos const auto& subconstraint_enum_values = nmos::fields::constraint_enum(subconstraint).as_array(); if (subconstraint_enum_values.end() == std::find_if(subconstraint_enum_values.begin(), subconstraint_enum_values.end(), [&constraint](const web::json::value& enum_value) { + if (enum_value.has_field(nmos::fields::numerator)) + { + return details::match_constraint(nmos::parse_rational(enum_value), constraint, [](const web::json::value& v) { return nmos::parse_rational(v); }); + } return details::match_constraint(enum_value, constraint, [](const web::json::value& v) { return v; }); })) { From b48f40fef8595a20e24df21e064a51a008edec99 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Thu, 17 Nov 2022 01:53:32 +0400 Subject: [PATCH 026/109] Add match_constraint() for arbitrary JSON value --- Development/nmos/capabilities.cpp | 67 +++++++++++++++++++++++++++++++ Development/nmos/capabilities.h | 39 +----------------- Development/nmos/constraints.cpp | 6 +-- 3 files changed, 69 insertions(+), 43 deletions(-) diff --git a/Development/nmos/capabilities.cpp b/Development/nmos/capabilities.cpp index c4d3540da..8fb91a87c 100644 --- a/Development/nmos/capabilities.cpp +++ b/Development/nmos/capabilities.cpp @@ -56,6 +56,43 @@ namespace nmos }); } + namespace details + { + // cf. nmos::details::make_constraints_schema in nmos/connection_api.cpp + template + bool match_constraint(const T& value, const web::json::value& constraint, Parse parse) + { + if (constraint.has_field(nmos::fields::constraint_enum)) + { + const auto& enum_values = nmos::fields::constraint_enum(constraint).as_array(); + if (enum_values.end() == std::find_if(enum_values.begin(), enum_values.end(), [&parse, &value](const web::json::value& enum_value) + { + return parse(enum_value) == value; + })) + { + return false; + } + } + if (constraint.has_field(nmos::fields::constraint_minimum)) + { + const auto& minimum = nmos::fields::constraint_minimum(constraint); + if (parse(minimum) > value) + { + return false; + } + } + if (constraint.has_field(nmos::fields::constraint_maximum)) + { + const auto& maximum = nmos::fields::constraint_maximum(constraint); + if (parse(maximum) < value) + { + return false; + } + } + return true; + } + } + bool match_string_constraint(const utility::string_t& value, const web::json::value& constraint) { return details::match_constraint(value, constraint, [](const web::json::value& enum_value) @@ -95,4 +132,34 @@ namespace nmos return nmos::parse_rational(enum_value); }); } + + bool match_constraint(const web::json::value& value, const web::json::value& constraint) + { + bool result = false; + if (value.is_string()) + { + result = match_string_constraint(value.as_string(), constraint); + } + else if (value.is_integer()) + { + result = match_integer_constraint(value.as_integer(), constraint); + } + else if (value.is_double()) + { + result = match_number_constraint(value.as_double(), constraint); + } + else if (value.is_boolean()) + { + result = match_boolean_constraint(value.as_bool(), constraint); + } + else if (value.has_field(nmos::fields::numerator)) + { + result = match_rational_constraint(nmos::parse_rational(value), constraint); + } + else + { + throw std::logic_error("unreachable code"); + } + return result; + } } diff --git a/Development/nmos/capabilities.h b/Development/nmos/capabilities.h index a296a2ee3..4dd6e7688 100644 --- a/Development/nmos/capabilities.h +++ b/Development/nmos/capabilities.h @@ -2,7 +2,6 @@ #define NMOS_CAPABILITIES_H #include "cpprest/json_utils.h" -#include "nmos/json_fields.h" #include "nmos/rational.h" namespace nmos @@ -35,48 +34,12 @@ namespace nmos // See https://specs.amwa.tv/bcp-004-01/releases/v1.0.0/docs/1.0._Receiver_Capabilities.html#rational-constraint-keywords web::json::value make_caps_rational_constraint(const std::vector& enum_values = {}, const nmos::rational& minimum = no_minimum(), const nmos::rational& maximum = no_maximum()); - namespace details - { - // cf. nmos::details::make_constraints_schema in nmos/connection_api.cpp - template - bool match_constraint(const T& value, const web::json::value& constraint, Parse parse) - { - if (constraint.has_field(nmos::fields::constraint_enum)) - { - const auto& enum_values = nmos::fields::constraint_enum(constraint).as_array(); - if (enum_values.end() == std::find_if(enum_values.begin(), enum_values.end(), [&parse, &value](const web::json::value& enum_value) - { - return parse(enum_value) == value; - })) - { - return false; - } - } - if (constraint.has_field(nmos::fields::constraint_minimum)) - { - const auto& minimum = nmos::fields::constraint_minimum(constraint); - if (parse(minimum) > value) - { - return false; - } - } - if (constraint.has_field(nmos::fields::constraint_maximum)) - { - const auto& maximum = nmos::fields::constraint_maximum(constraint); - if (parse(maximum) < value) - { - return false; - } - } - return true; - } - } - bool match_string_constraint(const utility::string_t& value, const web::json::value& constraint); bool match_integer_constraint(int64_t value, const web::json::value& constraint); bool match_number_constraint(double value, const web::json::value& constraint); bool match_boolean_constraint(bool value, const web::json::value& constraint); bool match_rational_constraint(const nmos::rational& value, const web::json::value& constraint); + bool match_constraint(const web::json::value& value, const web::json::value& constraint); // NMOS Parameter Registers - Capabilities register // See https://specs.amwa.tv/nmos-parameter-registers/branches/main/capabilities/ diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index 90a47c931..cd76c1ef4 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -117,11 +117,7 @@ namespace nmos const auto& subconstraint_enum_values = nmos::fields::constraint_enum(subconstraint).as_array(); if (subconstraint_enum_values.end() == std::find_if(subconstraint_enum_values.begin(), subconstraint_enum_values.end(), [&constraint](const web::json::value& enum_value) { - if (enum_value.has_field(nmos::fields::numerator)) - { - return details::match_constraint(nmos::parse_rational(enum_value), constraint, [](const web::json::value& v) { return nmos::parse_rational(v); }); - } - return details::match_constraint(enum_value, constraint, [](const web::json::value& v) { return v; }); + return match_constraint(enum_value, constraint); })) { return false; From 814128629b445c8dd1dafddedb025c8512666d97 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Thu, 17 Nov 2022 01:55:36 +0400 Subject: [PATCH 027/109] is_subconstraint(): fix comparing of rationals --- Development/nmos/constraints.cpp | 32 ++++++++++++---------- Development/nmos/constraints.h | 1 + Development/nmos/test/constraints_test.cpp | 14 ++++++++++ 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index cd76c1ef4..cde985ea3 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -44,10 +44,7 @@ namespace nmos // General Constraints { nmos::caps::format::media_type, [](const web::json::value& flow, const value& con) { return nmos::match_string_constraint(flow.at(U("media_type")).as_string(), con); } }, - { nmos::caps::format::grain_rate, [](const web::json::value& flow, const value& con) { - auto grain_rate = nmos::rational(nmos::fields::numerator(nmos::fields::grain_rate(flow)), nmos::fields::denominator(nmos::fields::grain_rate(flow))); - return nmos::match_rational_constraint(grain_rate, con); } - }, + { nmos::caps::format::grain_rate, [](const web::json::value& flow, const value& con) { return nmos::match_rational_constraint(nmos::parse_rational(nmos::fields::grain_rate(flow)), con); } }, // Video Constraints @@ -61,10 +58,7 @@ namespace nmos // Audio Constraints - { nmos::caps::format::sample_rate, [](const web::json::value& flow, const value& con) { - auto sample_rate = nmos::rational(nmos::fields::numerator(nmos::fields::sample_rate(flow)), nmos::fields::denominator(nmos::fields::sample_rate(flow))); - return nmos::match_rational_constraint(sample_rate, con); } - }, + { nmos::caps::format::sample_rate, [](const web::json::value& flow, const value& con) { return nmos::match_rational_constraint(nmos::parse_rational(nmos::fields::sample_rate(flow)), con); } }, { nmos::caps::format::sample_depth, [](const web::json::value& flow, const value& con) { return nmos::match_integer_constraint(nmos::fields::bit_depth(flow), con); } }, }; @@ -95,18 +89,28 @@ namespace nmos } if (constraint.has_field(nmos::fields::constraint_minimum) && subconstraint.has_field(nmos::fields::constraint_minimum)) { - const auto& constraint_minimum = nmos::fields::constraint_minimum(constraint); - const auto& subconstraint_minimum = nmos::fields::constraint_minimum(subconstraint); - if (constraint_minimum > subconstraint_minimum) + if (constraint.at(U("minimum")).has_field(nmos::fields::numerator)) + { + if (nmos::parse_rational(constraint.at(U("minimum"))) > nmos::parse_rational(subconstraint.at(U("minimum")))) + { + return false; + } + } + else if (nmos::fields::constraint_minimum(constraint) > nmos::fields::constraint_minimum(subconstraint)) { return false; } } if (constraint.has_field(nmos::fields::constraint_maximum) && subconstraint.has_field(nmos::fields::constraint_maximum)) { - const auto& constraint_maximum = nmos::fields::constraint_maximum(constraint); - const auto& subconstraint_maximum = nmos::fields::constraint_maximum(subconstraint); - if (constraint_maximum < subconstraint_maximum) + if (constraint.at(U("maximum")).has_field(nmos::fields::numerator)) + { + if (nmos::parse_rational(constraint.at(U("maximum"))) < nmos::parse_rational(subconstraint.at(U("maximum")))) + { + return false; + } + } + else if (nmos::fields::constraint_maximum(constraint) < nmos::fields::constraint_maximum(subconstraint)) { return false; } diff --git a/Development/nmos/constraints.h b/Development/nmos/constraints.h index 87e785d1a..7f0fca946 100644 --- a/Development/nmos/constraints.h +++ b/Development/nmos/constraints.h @@ -18,6 +18,7 @@ namespace nmos { bool match_source_parameters_constraint_set(const web::json::value& source, const web::json::value& constraint_set); bool match_flow_parameters_constraint_set(const web::json::value& flow, const web::json::value& constraint_set); + bool is_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint); bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset); } } diff --git a/Development/nmos/test/constraints_test.cpp b/Development/nmos/test/constraints_test.cpp index 7dfd95ba9..7743ecd67 100644 --- a/Development/nmos/test/constraints_test.cpp +++ b/Development/nmos/test/constraints_test.cpp @@ -97,3 +97,17 @@ BST_TEST_CASE(testSubconstraints) BST_REQUIRE(is_constraint_subset(constraint_set, constraint_subset)); } } + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testRationalMinMaxSubconstraints) +{ + { + using web::json::value_of; + using nmos::experimental::is_subconstraint; + + auto wideRange = nmos::make_caps_rational_constraint({}, nmos::rates::rate25, nmos::rates::rate30); + auto narrowRange = nmos::make_caps_rational_constraint({}, nmos::rates::rate25, nmos::rates::rate29_97); + + BST_REQUIRE(is_subconstraint(wideRange, narrowRange)); + } +} From c5d2000f66d72add4373b62f38285486516b326f Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 18 Nov 2022 01:53:30 +0400 Subject: [PATCH 028/109] Add constraints matcher for Senders --- Development/nmos/constraints.cpp | 101 ++++++++++-------- Development/nmos/constraints.h | 1 + .../nmos/streamcompatibility_behaviour.cpp | 7 +- .../nmos/streamcompatibility_behaviour.h | 4 +- 4 files changed, 62 insertions(+), 51 deletions(-) diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index cde985ea3..3142688c6 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -1,75 +1,84 @@ + +#include "nmos/constraints.h" + #include #include "nmos/capabilities.h" -#include "nmos/constraints.h" #include "nmos/json_fields.h" #include "nmos/sdp_utils.h" +#include "nmos/video_jxsv.h" namespace nmos { namespace experimental { - bool match_source_parameters_constraint_set(const web::json::value& source, const web::json::value& constraint_set) - { - using web::json::value; - - if (!nmos::caps::meta::enabled(constraint_set)) return true; + typedef std::map> constraints_matcher; - // NMOS Parameter Registers - Capabilities register - // See https://github.com/AMWA-TV/nmos-parameter-registers/blob/main/capabilities/README.md - static const std::map> match_constraints - { - // Audio Constraints - - { nmos::caps::format::channel_count, [](const web::json::value& source, const value& con) { return nmos::match_integer_constraint((uint32_t)nmos::fields::channels(source).size(), con); } } - }; + // NMOS Parameter Registers - Capabilities register + // See https://github.com/AMWA-TV/nmos-parameter-registers/blob/main/capabilities/README.md + static const std::map> source_constraints_matcher + { + // Audio Constraints - const auto& constraints = constraint_set.as_object(); - return constraints.end() == std::find_if(constraints.begin(), constraints.end(), [&](const std::pair& constraint) - { - const auto& found = match_constraints.find(constraint.first); - return match_constraints.end() != found && !found->second(source, constraint.second); - }); - } + { nmos::caps::format::channel_count, [](const web::json::value& source, const web::json::value& con) { return nmos::match_integer_constraint((uint32_t)nmos::fields::channels(source).size(), con); } }, + }; - bool match_flow_parameters_constraint_set(const web::json::value& flow, const web::json::value& constraint_set) + static const std::map> flow_constraints_matcher { - using web::json::value; + // General Constraints - if (!nmos::caps::meta::enabled(constraint_set)) return true; + { nmos::caps::format::media_type, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(flow.at(U("media_type")).as_string(), con); } }, + { nmos::caps::format::grain_rate, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_rational_constraint(nmos::parse_rational(nmos::fields::grain_rate(flow)), con); } }, - // NMOS Parameter Registers - Capabilities register - // See https://github.com/AMWA-TV/nmos-parameter-registers/blob/main/capabilities/README.md - static const std::map> match_constraints - { - // General Constraints + // Video Constraints - { nmos::caps::format::media_type, [](const web::json::value& flow, const value& con) { return nmos::match_string_constraint(flow.at(U("media_type")).as_string(), con); } }, - { nmos::caps::format::grain_rate, [](const web::json::value& flow, const value& con) { return nmos::match_rational_constraint(nmos::parse_rational(nmos::fields::grain_rate(flow)), con); } }, + { nmos::caps::format::frame_height, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::frame_height(flow), con); } }, + { nmos::caps::format::frame_width, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::frame_width(flow), con); } }, + { nmos::caps::format::color_sampling, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::details::make_sampling(nmos::fields::components(flow)).name, con); } }, + { nmos::caps::format::interlace_mode, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::interlace_mode(flow), con); } }, + { nmos::caps::format::colorspace, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::colorspace(flow), con); } }, + { nmos::caps::format::transfer_characteristic, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::transfer_characteristic(flow), con); } }, + { nmos::caps::format::component_depth, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_depth(nmos::fields::components(flow).at(0)), con); } }, - // Video Constraints + // Audio Constraints - { nmos::caps::format::frame_height, [](const web::json::value& flow, const value& con) { return nmos::match_integer_constraint(nmos::fields::frame_height(flow), con); } }, - { nmos::caps::format::frame_width, [](const web::json::value& flow, const value& con) { return nmos::match_integer_constraint(nmos::fields::frame_width(flow), con); } }, - { nmos::caps::format::color_sampling, [](const web::json::value& flow, const value& con) { return nmos::match_string_constraint(nmos::details::make_sampling(nmos::fields::components(flow)).name, con); } }, - { nmos::caps::format::interlace_mode, [](const web::json::value& flow, const value& con) { return nmos::match_string_constraint(nmos::fields::interlace_mode(flow), con); } }, - { nmos::caps::format::colorspace, [](const web::json::value& flow, const value& con) { return nmos::match_string_constraint(nmos::fields::colorspace(flow), con); } }, - { nmos::caps::format::transfer_characteristic, [](const web::json::value& flow, const value& con) { return nmos::match_string_constraint(nmos::fields::transfer_characteristic(flow), con); } }, - { nmos::caps::format::component_depth, [](const web::json::value& flow, const value& con) { return nmos::match_integer_constraint(nmos::fields::bit_depth(nmos::fields::components(flow).at(0)), con); } }, + { nmos::caps::format::sample_rate, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_rational_constraint(nmos::parse_rational(nmos::fields::sample_rate(flow)), con); } }, + { nmos::caps::format::sample_depth, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_depth(flow), con); } }, + }; - // Audio Constraints + static const std::map> sender_constraints_matcher + { + { nmos::caps::transport::packet_transmission_mode, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::packet_transmission_mode(sender), con); } }, + { nmos::caps::transport::st2110_21_sender_type, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::st2110_21_sender_type(sender), con); } }, + { nmos::caps::transport::bit_rate, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_rate(sender), con); } }, + }; - { nmos::caps::format::sample_rate, [](const web::json::value& flow, const value& con) { return nmos::match_rational_constraint(nmos::parse_rational(nmos::fields::sample_rate(flow)), con); } }, - { nmos::caps::format::sample_depth, [](const web::json::value& flow, const value& con) { return nmos::match_integer_constraint(nmos::fields::bit_depth(flow), con); } }, - }; + bool match_resource_parameters_constraint_set(const web::json::value& resource, const constraints_matcher& matcher, const web::json::value& constraint_set) + { + if (!nmos::caps::meta::enabled(constraint_set)) return true; const auto& constraints = constraint_set.as_object(); - return constraints.end() == std::find_if(constraints.begin(), constraints.end(), [&](const std::pair& constraint) + return constraints.end() == std::find_if(constraints.begin(), constraints.end(), [&](const std::pair& constraint) { - const auto& found = match_constraints.find(constraint.first); - return match_constraints.end() != found && !found->second(flow, constraint.second); + const auto& found = matcher.find(constraint.first); + return matcher.end() != found && !found->second(resource, constraint.second); }); } + bool match_source_parameters_constraint_set(const web::json::value& source, const web::json::value& constraint_set) + { + return match_resource_parameters_constraint_set(source, source_constraints_matcher, constraint_set); + } + + bool match_flow_parameters_constraint_set(const web::json::value& flow, const web::json::value& constraint_set) + { + return match_resource_parameters_constraint_set(flow, flow_constraints_matcher, constraint_set); + } + + bool match_sender_parameters_constraint_set(const web::json::value& sender, const web::json::value& constraint_set) + { + return match_resource_parameters_constraint_set(sender, sender_constraints_matcher, constraint_set); + } + bool is_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint) { // subconstraint should have enum if constraint has enum diff --git a/Development/nmos/constraints.h b/Development/nmos/constraints.h index 7f0fca946..f6ca8e811 100644 --- a/Development/nmos/constraints.h +++ b/Development/nmos/constraints.h @@ -18,6 +18,7 @@ namespace nmos { bool match_source_parameters_constraint_set(const web::json::value& source, const web::json::value& constraint_set); bool match_flow_parameters_constraint_set(const web::json::value& flow, const web::json::value& constraint_set); + bool match_sender_parameters_constraint_set(const web::json::value& sender, const web::json::value& constraint_set); bool is_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint); bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset); } diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index 848043d4e..e5467f7bf 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -53,7 +53,7 @@ namespace nmos })); } - nmos::sender_state validate_sender_resources(const web::json::value& transport_file, const web::json::value& flow, const web::json::value& source, const web::json::array& constraint_sets) + nmos::sender_state validate_sender_resources(const web::json::value& transport_file, const web::json::value& sender, const web::json::value& flow, const web::json::value& source, const web::json::array& constraint_sets) { nmos::sender_state sender_state; @@ -63,8 +63,9 @@ namespace nmos auto source_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return match_source_parameters_constraint_set(source, constraint_set); }); auto flow_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return match_flow_parameters_constraint_set(flow, constraint_set); }); + auto sender_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return match_sender_parameters_constraint_set(sender, constraint_set); }); - constrained = constraint_sets.end() != source_found && constraint_sets.end() != flow_found; + constrained = constraint_sets.end() != source_found && constraint_sets.end() != flow_found && constraint_sets.end() != sender_found; if (!transport_file.is_null() && !transport_file.as_object().empty()) { @@ -181,7 +182,7 @@ namespace nmos slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " is being validated with its Flow, Source and transport file"; if (validate_sender) { - sender_state = validate_sender(transport_file, flow->data, source->data, constraint_sets); + sender_state = validate_sender(transport_file, sender->data, flow->data, source->data, constraint_sets); } } diff --git a/Development/nmos/streamcompatibility_behaviour.h b/Development/nmos/streamcompatibility_behaviour.h index 75c83e4db..724382cee 100644 --- a/Development/nmos/streamcompatibility_behaviour.h +++ b/Development/nmos/streamcompatibility_behaviour.h @@ -27,10 +27,10 @@ namespace nmos { namespace details { - typedef std::function streamcompatibility_sender_validator; + typedef std::function streamcompatibility_sender_validator; } - nmos::sender_state validate_sender_resources(const web::json::value& transport_file, const web::json::value& flow, const web::json::value& source, const web::json::array& constraint_sets); + nmos::sender_state validate_sender_resources(const web::json::value& transport_file, const web::json::value& sender, const web::json::value& flow, const web::json::value& source, const web::json::array& constraint_sets); void streamcompatibility_behaviour_thread(nmos::node_model& model, details::streamcompatibility_sender_validator validate_sender, slog::base_gate& gate); } } From 2bac1b63507af73ffa45958496625826b1c7ac9c Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 18 Nov 2022 02:14:04 +0400 Subject: [PATCH 029/109] Add validate_receiver() callback, expand validate_sender()'s return value with debug message --- .../nmos-cpp-node/node_implementation.cpp | 10 +++++- Development/nmos/node_server.cpp | 3 +- Development/nmos/node_server.h | 2 ++ .../nmos/streamcompatibility_behaviour.cpp | 33 ++++++++++++++----- .../nmos/streamcompatibility_behaviour.h | 8 +++-- 5 files changed, 42 insertions(+), 14 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index e42980096..65bd4b6b7 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1446,6 +1446,13 @@ nmos::experimental::details::streamcompatibility_sender_validator make_node_impl return &nmos::experimental::validate_sender_resources; } +nmos::experimental::details::streamcompatibility_receiver_validator make_node_implementation_receiver_validator() +{ + // this example uses the default receiver validator explicitly + return &nmos::experimental::validate_receiver_resources; +} + + namespace impl { nmos::interlace_mode get_interlace_mode(const nmos::settings& settings) @@ -1568,5 +1575,6 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode .on_base_edid_deleted(make_node_implementation_streamcompatibility_base_edid_delete_handler(gate)) .on_set_effective_edid(make_node_implementation_effective_edid_setter(model.streamcompatibility_resources, gate)) .on_active_constraints_changed(make_node_implementation_active_constraints_handler(gate)) - .on_validate_sender_against_active_constraints(make_node_implementation_sender_validator()); + .on_validate_sender_against_active_constraints(make_node_implementation_sender_validator()) + .on_validate_receiver_against_transport_file(make_node_implementation_receiver_validator()); } diff --git a/Development/nmos/node_server.cpp b/Development/nmos/node_server.cpp index 431e57796..5d3145931 100644 --- a/Development/nmos/node_server.cpp +++ b/Development/nmos/node_server.cpp @@ -107,13 +107,14 @@ namespace nmos auto connection_activated = node_implementation.connection_activated; auto channelmapping_activated = node_implementation.channelmapping_activated; auto validate_sender = node_implementation.validate_sender; + auto validate_receiver = node_implementation.validate_receiver; node_server.thread_functions.assign({ [&, load_ca_certificates, registration_changed] { nmos::node_behaviour_thread(node_model, load_ca_certificates, registration_changed, gate); }, [&] { nmos::send_events_ws_messages_thread(events_ws_listener, node_model, events_ws_api.second, gate); }, [&] { nmos::erase_expired_events_resources_thread(node_model, gate); }, [&, resolve_auto, set_transportfile, connection_activated] { nmos::connection_activation_thread(node_model, resolve_auto, set_transportfile, connection_activated, gate); }, [&, channelmapping_activated] { nmos::channelmapping_activation_thread(node_model, channelmapping_activated, gate); }, - [&, validate_sender] { nmos::experimental::streamcompatibility_behaviour_thread(node_model, validate_sender, gate); } + [&, validate_sender] { nmos::experimental::streamcompatibility_behaviour_thread(node_model, validate_sender, validate_receiver, gate); } }); auto system_changed = node_implementation.system_changed; diff --git a/Development/nmos/node_server.h b/Development/nmos/node_server.h index b58d082e7..5a5db817e 100644 --- a/Development/nmos/node_server.h +++ b/Development/nmos/node_server.h @@ -64,6 +64,7 @@ namespace nmos node_implementation& on_set_effective_edid(nmos::experimental::details::streamcompatibility_effective_edid_setter set_effective_edid) { this->set_effective_edid = std::move(set_effective_edid); return *this; } node_implementation& on_active_constraints_changed(nmos::experimental::details::streamcompatibility_active_constraints_put_handler active_constraints_changed) { this->active_constraints_changed = std::move(active_constraints_changed); return *this; } node_implementation& on_validate_sender_against_active_constraints(nmos::experimental::details::streamcompatibility_sender_validator validate_sender) { this->validate_sender = std::move(validate_sender); return *this; } + node_implementation& on_validate_receiver_against_transport_file(nmos::experimental::details::streamcompatibility_receiver_validator validate_receiver) { this->validate_receiver = std::move(validate_receiver); return *this; } // deprecated, use on_validate_connection_resource_patch node_implementation& on_validate_merged(nmos::details::connection_resource_patch_validator validate_merged) { return on_validate_connection_resource_patch(std::move(validate_merged)); } @@ -99,6 +100,7 @@ namespace nmos nmos::experimental::details::streamcompatibility_effective_edid_setter set_effective_edid; nmos::experimental::details::streamcompatibility_active_constraints_put_handler active_constraints_changed; nmos::experimental::details::streamcompatibility_sender_validator validate_sender; + nmos::experimental::details::streamcompatibility_receiver_validator validate_receiver; }; // Construct a server instance for an NMOS Node, implementing the IS-04 Node API, IS-05 Connection API, IS-07 Events API diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index e5467f7bf..011c89262 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -53,7 +53,7 @@ namespace nmos })); } - nmos::sender_state validate_sender_resources(const web::json::value& transport_file, const web::json::value& sender, const web::json::value& flow, const web::json::value& source, const web::json::array& constraint_sets) + std::pair validate_sender_resources(const web::json::value& transport_file, const web::json::value& sender, const web::json::value& flow, const web::json::value& source, const web::json::array& constraint_sets) { nmos::sender_state sender_state; @@ -86,12 +86,13 @@ namespace nmos sender_state = nmos::sender_states::unconstrained; } - return sender_state; + return { sender_state, {} }; } - nmos::receiver_state validate_receiver_resources(const web::json::value& transport_file, const web::json::value& receiver) + std::pair validate_receiver_resources(const web::json::value& transport_file, const web::json::value& receiver) { nmos::receiver_state receiver_state; + utility::string_t receiver_state_debug; if (!transport_file.is_null() && !transport_file.as_object().empty()) { @@ -108,6 +109,7 @@ namespace nmos catch (const std::runtime_error& e) { receiver_state = nmos::receiver_states::non_compliant_stream; + receiver_state_debug = e.what(); } } else @@ -115,10 +117,10 @@ namespace nmos receiver_state = nmos::receiver_states::unknown; } - return receiver_state; + return { receiver_state, receiver_state_debug }; } - void streamcompatibility_behaviour_thread(nmos::node_model& model, details::streamcompatibility_sender_validator validate_sender, slog::base_gate& gate) + void streamcompatibility_behaviour_thread(nmos::node_model& model, details::streamcompatibility_sender_validator validate_sender, details::streamcompatibility_receiver_validator validate_receiver, slog::base_gate& gate) { using web::json::value; using web::json::value_of; @@ -172,6 +174,7 @@ namespace nmos if (connection_resources.end() == connection_sender) throw std::logic_error("Matching IS-05 Sender not found"); nmos::sender_state sender_state(nmos::fields::state(nmos::fields::status(streamcompatibility_sender->data))); + utility::string_t sender_state_debug; // Setting the State to any value except for "no_essence" or "awaiting_essence" triggers Active Constraints validation if (sender_state != nmos::sender_states::no_essence && sender_state != nmos::sender_states::awaiting_essence) @@ -182,7 +185,7 @@ namespace nmos slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " is being validated with its Flow, Source and transport file"; if (validate_sender) { - sender_state = validate_sender(transport_file, sender->data, flow->data, source->data, constraint_sets); + std::tie(sender_state, sender_state_debug) = validate_sender(transport_file, sender->data, flow->data, source->data, constraint_sets); } } @@ -190,9 +193,13 @@ namespace nmos { utility::string_t updated_timestamp; - modify_resource(streamcompatibility_resources, sender_id, [&sender_state, &updated_timestamp, &gate](nmos::resource& sender) + modify_resource(streamcompatibility_resources, sender_id, [&sender_state, &sender_state_debug, &updated_timestamp, &gate](nmos::resource& sender) { nmos::fields::status(sender.data)[nmos::fields::state] = web::json::value::string(sender_state.name); + if (!sender_state_debug.empty()) + { + nmos::fields::status(sender.data)[nmos::fields::debug] = web::json::value::string(sender_state_debug); + } updated_timestamp = nmos::make_version(); sender.data[nmos::fields::version] = web::json::value::string(updated_timestamp); @@ -249,6 +256,7 @@ namespace nmos if (streamcompatibility_resources.end() == streamcompatibility_receiver) throw std::logic_error("Matching IS-11 receiver not found"); nmos::receiver_state receiver_state(nmos::receiver_states::unknown); + utility::string_t receiver_state_debug; const std::pair connection_receiver_id_type{ receiver_id, nmos::types::receiver }; auto connection_receiver = find_resource(connection_resources, connection_receiver_id_type); @@ -256,15 +264,22 @@ namespace nmos auto& transport_file = nmos::fields::transport_file(nmos::fields::endpoint_staged(connection_receiver->data)); - receiver_state = validate_receiver_resources(transport_file, receiver->data); + if (validate_receiver) + { + std::tie(receiver_state, receiver_state_debug) = validate_receiver(transport_file, receiver->data); + } if (nmos::fields::state(nmos::fields::status(streamcompatibility_receiver->data)) != receiver_state.name) { utility::string_t updated_timestamp; - modify_resource(streamcompatibility_resources, receiver_id, [&receiver_state, &updated_timestamp, &gate](nmos::resource& receiver) + modify_resource(streamcompatibility_resources, receiver_id, [&receiver_state, &receiver_state_debug, &updated_timestamp, &gate](nmos::resource& receiver) { nmos::fields::status(receiver.data)[nmos::fields::state] = web::json::value::string(receiver_state.name); + if (!receiver_state_debug.empty()) + { + nmos::fields::status(receiver.data)[nmos::fields::debug] = web::json::value::string(receiver_state_debug); + } updated_timestamp = nmos::make_version(); receiver.data[nmos::fields::version] = web::json::value::string(updated_timestamp); diff --git a/Development/nmos/streamcompatibility_behaviour.h b/Development/nmos/streamcompatibility_behaviour.h index 724382cee..e1208da62 100644 --- a/Development/nmos/streamcompatibility_behaviour.h +++ b/Development/nmos/streamcompatibility_behaviour.h @@ -27,11 +27,13 @@ namespace nmos { namespace details { - typedef std::function streamcompatibility_sender_validator; + typedef std::function(const web::json::value& transport_file, const web::json::value& sender, const web::json::value& flow, const web::json::value& source, const web::json::array& constraint_sets)> streamcompatibility_sender_validator; + typedef std::function(const web::json::value& transport_file, const web::json::value& receiver)> streamcompatibility_receiver_validator; } - nmos::sender_state validate_sender_resources(const web::json::value& transport_file, const web::json::value& sender, const web::json::value& flow, const web::json::value& source, const web::json::array& constraint_sets); - void streamcompatibility_behaviour_thread(nmos::node_model& model, details::streamcompatibility_sender_validator validate_sender, slog::base_gate& gate); + std::pair validate_sender_resources(const web::json::value& transport_file, const web::json::value& sender, const web::json::value& flow, const web::json::value& source, const web::json::array& constraint_sets); + std::pair validate_receiver_resources(const web::json::value& transport_file, const web::json::value& receiver); + void streamcompatibility_behaviour_thread(nmos::node_model& model, details::streamcompatibility_sender_validator validate_sender, details::streamcompatibility_receiver_validator validate_receiver, slog::base_gate& gate); } } From e94eddf3a218d52b9eaab7a9ac08e6900b04c4d7 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 18 Nov 2022 02:28:33 +0400 Subject: [PATCH 030/109] Remove senderReceiverSubrouteType --- Development/nmos/api_utils.h | 1 - Development/nmos/streamcompatibility_api.cpp | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Development/nmos/api_utils.h b/Development/nmos/api_utils.h index d83a79abe..29283c279 100644 --- a/Development/nmos/api_utils.h +++ b/Development/nmos/api_utils.h @@ -84,7 +84,6 @@ namespace nmos // Stream Compatibility Management API const route_pattern streamCompatibilityResourceType = make_route_pattern(U("resourceType"), U("senders|receivers|inputs|outputs")); - const route_pattern senderReceiverSubrouteType = make_route_pattern(U("senderReceiverSubroute"), U("inputs|outputs")); const route_pattern constraintsType = make_route_pattern(U("constraintsType"), U("active|supported")); const route_pattern edidType = make_route_pattern(U("edidType"), U("base|effective")); diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index 01b3c98f7..a3ca0639b 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -321,14 +321,14 @@ namespace nmos return pplx::task_from_result(true); }); - streamcompatibility_api.support(U("/") + nmos::patterns::connectorType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/") + nmos::patterns::senderReceiverSubrouteType.pattern + U("/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::connectorType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/") + nmos::patterns::inputOutputType.pattern + U("/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) { auto lock = model.read_lock(); auto& resources = model.streamcompatibility_resources; const string_t resourceType = parameters.at(nmos::patterns::connectorType.name); const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); - const string_t associatedResourceType = parameters.at(nmos::patterns::senderReceiverSubrouteType.name); + const string_t associatedResourceType = parameters.at(nmos::patterns::inputOutputType.name); const std::pair id_type{ resourceId, nmos::type_from_resourceType(resourceType) }; auto resource = find_resource(resources, id_type); From 0812d6facff43ae37e6c186d3762af973c42722e Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 18 Nov 2022 02:55:09 +0400 Subject: [PATCH 031/109] Minor fixes --- Development/nmos/constraints.cpp | 2 +- Development/nmos/streamcompatibility_behaviour.cpp | 2 +- Development/nmos/streamcompatibility_behaviour.h | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index 3142688c6..3da3c3af3 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -26,7 +26,7 @@ namespace nmos { // General Constraints - { nmos::caps::format::media_type, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(flow.at(U("media_type")).as_string(), con); } }, + { nmos::caps::format::media_type, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::media_type(flow), con); } }, { nmos::caps::format::grain_rate, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_rational_constraint(nmos::parse_rational(nmos::fields::grain_rate(flow)), con); } }, // Video Constraints diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index 011c89262..9847c5b49 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -109,7 +109,7 @@ namespace nmos catch (const std::runtime_error& e) { receiver_state = nmos::receiver_states::non_compliant_stream; - receiver_state_debug = e.what(); + receiver_state_debug = utility::conversions::to_string_t(e.what()); } } else diff --git a/Development/nmos/streamcompatibility_behaviour.h b/Development/nmos/streamcompatibility_behaviour.h index e1208da62..1820d4d9f 100644 --- a/Development/nmos/streamcompatibility_behaviour.h +++ b/Development/nmos/streamcompatibility_behaviour.h @@ -1,5 +1,5 @@ -#ifndef NMOS_FLOWCOMPATIBILITY_BEHAVIOUR_H -#define NMOS_FLOWCOMPATIBILITY_BEHAVIOUR_H +#ifndef NMOS_STREAMCOMPATIBILITY_BEHAVIOUR_H +#define NMOS_STREAMCOMPATIBILITY_BEHAVIOUR_H #include From 8eabb3f8f8792e47c5bcf16f520cc30b775ec63c Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 18 Nov 2022 03:16:13 +0400 Subject: [PATCH 032/109] Align make_node_implementation_streamcompatibility_...() naming --- .../nmos-cpp-node/node_implementation.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 65bd4b6b7..0c83f3287 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1334,7 +1334,7 @@ nmos::experimental::details::streamcompatibility_base_edid_delete_handler make_n } // Example Stream Compatibility Management API callback to update Effective EDID - captures streamcompatibility_resources by reference! -nmos::experimental::details::streamcompatibility_effective_edid_setter make_node_implementation_effective_edid_setter(const nmos::resources& streamcompatibility_resources, slog::base_gate& gate) +nmos::experimental::details::streamcompatibility_effective_edid_setter make_node_implementation_streamcompatibility_effective_edid_setter(const nmos::resources& streamcompatibility_resources, slog::base_gate& gate) { return [&streamcompatibility_resources, &gate](const nmos::id& input_id, boost::variant& effective_edid, bst::optional& effective_edid_properties) { @@ -1392,7 +1392,7 @@ nmos::experimental::details::streamcompatibility_effective_edid_setter make_node } // Example Stream Compatibility Management API callback to update Active Constraints of a Sender -nmos::experimental::details::streamcompatibility_active_constraints_put_handler make_node_implementation_active_constraints_handler(slog::base_gate& gate) +nmos::experimental::details::streamcompatibility_active_constraints_put_handler make_node_implementation_streamcompatibility_active_constraints_handler(slog::base_gate& gate) { using web::json::value_of; @@ -1440,13 +1440,13 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler }; } -nmos::experimental::details::streamcompatibility_sender_validator make_node_implementation_sender_validator() +nmos::experimental::details::streamcompatibility_sender_validator make_node_implementation_streamcompatibility_sender_validator() { // this example uses the default sender validator explicitly return &nmos::experimental::validate_sender_resources; } -nmos::experimental::details::streamcompatibility_receiver_validator make_node_implementation_receiver_validator() +nmos::experimental::details::streamcompatibility_receiver_validator make_node_implementation_streamcompatibility_receiver_validator() { // this example uses the default receiver validator explicitly return &nmos::experimental::validate_receiver_resources; @@ -1573,8 +1573,8 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode .on_channelmapping_activated(make_node_implementation_channelmapping_activation_handler(gate)) .on_base_edid_changed(make_node_implementation_streamcompatibility_base_edid_put_handler(gate)) .on_base_edid_deleted(make_node_implementation_streamcompatibility_base_edid_delete_handler(gate)) - .on_set_effective_edid(make_node_implementation_effective_edid_setter(model.streamcompatibility_resources, gate)) - .on_active_constraints_changed(make_node_implementation_active_constraints_handler(gate)) - .on_validate_sender_against_active_constraints(make_node_implementation_sender_validator()) - .on_validate_receiver_against_transport_file(make_node_implementation_receiver_validator()); + .on_set_effective_edid(make_node_implementation_streamcompatibility_effective_edid_setter(model.streamcompatibility_resources, gate)) + .on_active_constraints_changed(make_node_implementation_streamcompatibility_active_constraints_handler(gate)) + .on_validate_sender_against_active_constraints(make_node_implementation_streamcompatibility_sender_validator()) + .on_validate_receiver_against_transport_file(make_node_implementation_streamcompatibility_receiver_validator()); } From 7df92e915fc84df3f94e8039b8d3b1f521a4abff Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sun, 20 Nov 2022 21:56:53 +0400 Subject: [PATCH 033/109] Fix is_constraint_subset() --- Development/nmos-cpp-node/node_implementation.cpp | 1 + Development/nmos/constraints.cpp | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 0c83f3287..c7936072b 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1427,6 +1427,7 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler { for (const auto& constraint_set : nmos::fields::constraint_sets(active_constraints).as_array()) { + if (!nmos::caps::meta::enabled(constraint_set)) continue; for (const auto& sender_caps_constraint_set : sender_capabilities.as_array()) { if (nmos::experimental::is_constraint_subset(sender_caps_constraint_set, constraint_set)) diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index 3da3c3af3..85cf64226 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -2,6 +2,7 @@ #include "nmos/constraints.h" #include +#include #include "nmos/capabilities.h" #include "nmos/json_fields.h" #include "nmos/sdp_utils.h" @@ -149,14 +150,12 @@ namespace nmos { using web::json::value; - if (!nmos::caps::meta::enabled(constraint_subset)) return true; - const auto& param_constraints_set = constraint_set.as_object(); const auto& param_constraints_subset = constraint_subset.as_object(); return param_constraints_subset.end() == std::find_if(param_constraints_subset.begin(), param_constraints_subset.end(), [&](const std::pair& subconstraint) { - if (subconstraint.first == nmos::caps::meta::label.key || subconstraint.first == nmos::caps::meta::preference.key) return false; + if (boost::algorithm::istarts_with(subconstraint.first, U("urn:x-nmos:cap:meta:"))) return false; const auto& constraint = param_constraints_set.find(subconstraint.first); return param_constraints_set.end() == constraint || !is_subconstraint(constraint->second, subconstraint.second); From 376d6164b588a57b84b385cfd77b8f2acc43f966 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sun, 20 Nov 2022 22:15:30 +0400 Subject: [PATCH 034/109] Decouple IS-11 and IS-05 --- .../nmos-cpp-node/node_implementation.cpp | 31 ++++++++++++++++--- Development/nmos/connection_api.cpp | 19 ------------ 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index c7936072b..9ad7b466f 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1090,11 +1090,32 @@ nmos::transport_file_parser make_node_implementation_transport_file_parser() } // Example Connection API callback to perform application-specific validation of the merged /staged endpoint during a PATCH /staged request -nmos::details::connection_resource_patch_validator make_node_implementation_patch_validator() +nmos::details::connection_resource_patch_validator make_node_implementation_patch_validator(nmos::node_model& model) { - // this example uses an 'empty' std::function because it does not need to do any validation - // beyond what is expressed by the schemas and /constraints endpoint - return{}; + // "At any time if State of an active Sender becomes active_constraints_violation, the Sender MUST become inactive. + // An inactive Sender in this state MUST NOT allow activations. + // At any time if State of an active Receiver becomes non_compliant_stream, the Receiver SHOULD become inactive. + // An inactive Receiver in this state SHOULD NOT allow activations." + // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#preventing-restrictions-violation + return [&] (const nmos::resource& resource, const nmos::resource& connection_resource, const web::json::value& endpoint_staged, slog::base_gate& gate) + { + if (nmos::fields::master_enable(endpoint_staged)) + { + const auto id = nmos::fields::id(resource.data); + const auto type = resource.type; + + auto streamcompatibility_resource = find_resource(model.streamcompatibility_resources, {id, type}); + if (model.streamcompatibility_resources.end() != streamcompatibility_resource) + { + auto resource_state = nmos::fields::state(nmos::fields::status(streamcompatibility_resource->data)); + + if (resource_state == nmos::sender_states::active_constraints_violation.name || resource_state == nmos::receiver_states::non_compliant_stream.name) + { + throw std::logic_error("activation failed due to Status of the connector"); + } + } + } + }; } // Example Connection API activation callback to resolve "auto" values when /staged is transitioned to /active @@ -1566,7 +1587,7 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode .on_system_changed(make_node_implementation_system_global_handler(model, gate)) // may be omitted if not required .on_registration_changed(make_node_implementation_registration_handler(gate)) // may be omitted if not required .on_parse_transport_file(make_node_implementation_transport_file_parser()) // may be omitted if the default is sufficient - .on_validate_connection_resource_patch(make_node_implementation_patch_validator()) // may be omitted if not required + .on_validate_connection_resource_patch(make_node_implementation_patch_validator(model)) // may be omitted if not required .on_resolve_auto(make_node_implementation_auto_resolver(model.settings)) .on_set_transportfile(make_node_implementation_transportfile_setter(model.node_resources, model.settings)) .on_connection_activated(make_node_implementation_connection_activation_handler(model, gate)) diff --git a/Development/nmos/connection_api.cpp b/Development/nmos/connection_api.cpp index 717815871..385a31c76 100644 --- a/Development/nmos/connection_api.cpp +++ b/Development/nmos/connection_api.cpp @@ -15,7 +15,6 @@ #include "nmos/model.h" #include "nmos/sdp_utils.h" #include "nmos/slog.h" -#include "nmos/streamcompatibility_state.h" #include "nmos/transport.h" #include "nmos/thread_utils.h" #include "nmos/version.h" @@ -456,24 +455,6 @@ namespace nmos // find resource again just in case, since waiting releases and reacquires the lock resource = find_resource(resources, id_type); } - - // "At any time if State of an active Sender becomes active_constraints_violation, the Sender MUST become inactive. - // An inactive Sender in this state MUST NOT allow activations. - // At any time if State of an active Receiver becomes non_compliant_stream, the Receiver SHOULD become inactive. - // An inactive Receiver in this state SHOULD NOT allow activations." - // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#preventing-restrictions-violation - auto streamcompatibility_resource = find_resource(model.streamcompatibility_resources, id_type); - if (model.streamcompatibility_resources.end() != streamcompatibility_resource) - { - auto resource_state = nmos::fields::state(nmos::fields::status(streamcompatibility_resource->data)); - - if (resource_state == nmos::sender_states::active_constraints_violation.name || resource_state == nmos::receiver_states::non_compliant_stream.name) - { - slog::log(gate, SLOG_FLF) << "Rejecting PATCH request for " << id_type << " due to its state: " << resource_state; - - return details::make_connection_resource_patch_error_response(status_codes::InternalError); - } - } } else { From 5c8ab03380bdb72a17fc9599a203d96a84847d07 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Tue, 22 Nov 2022 20:51:03 +0400 Subject: [PATCH 035/109] Apply suggestions from code review Co-authored-by: Gareth Sylvester-Bradley <31761158+garethsb@users.noreply.github.com> --- Development/nmos/constraints.cpp | 1 - Development/nmos/node_resources.cpp | 2 +- Development/nmos/node_server.cpp | 2 +- Development/nmos/streamcompatibility_api.h | 6 +++--- Development/nmos/streamcompatibility_resources.h | 8 ++++---- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index 85cf64226..1e980b4f2 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -1,4 +1,3 @@ - #include "nmos/constraints.h" #include diff --git a/Development/nmos/node_resources.cpp b/Development/nmos/node_resources.cpp index e9764497b..45fe1275f 100644 --- a/Development/nmos/node_resources.cpp +++ b/Development/nmos/node_resources.cpp @@ -115,7 +115,7 @@ namespace nmos .set_scheme(nmos::http_scheme(settings)) .set_port(nmos::fields::streamcompatibility_port(settings)) .set_path(U("/x-nmos/streamcompatibility/") + make_api_version(version)); - auto type = U("urn:x-nmos:control:fc-ctrl/") + make_api_version(version); + auto type = U("urn:x-nmos:control:stream-compat/") + make_api_version(version); for (const auto& host : hosts) { diff --git a/Development/nmos/node_server.cpp b/Development/nmos/node_server.cpp index 5d3145931..9267b5706 100644 --- a/Development/nmos/node_server.cpp +++ b/Development/nmos/node_server.cpp @@ -114,7 +114,7 @@ namespace nmos [&] { nmos::erase_expired_events_resources_thread(node_model, gate); }, [&, resolve_auto, set_transportfile, connection_activated] { nmos::connection_activation_thread(node_model, resolve_auto, set_transportfile, connection_activated, gate); }, [&, channelmapping_activated] { nmos::channelmapping_activation_thread(node_model, channelmapping_activated, gate); }, - [&, validate_sender] { nmos::experimental::streamcompatibility_behaviour_thread(node_model, validate_sender, validate_receiver, gate); } + [&, validate_sender, validate_receiver] { nmos::experimental::streamcompatibility_behaviour_thread(node_model, validate_sender, validate_receiver, gate); } }); auto system_changed = node_implementation.system_changed; diff --git a/Development/nmos/streamcompatibility_api.h b/Development/nmos/streamcompatibility_api.h index 7731ce11f..c23a64cc0 100644 --- a/Development/nmos/streamcompatibility_api.h +++ b/Development/nmos/streamcompatibility_api.h @@ -1,5 +1,5 @@ -#ifndef NMOS_FLOWCOMPATIBILITY_API_H -#define NMOS_FLOWCOMPATIBILITY_API_H +#ifndef NMOS_STREAMCOMPATIBILITY_API_H +#define NMOS_STREAMCOMPATIBILITY_API_H #include #include "bst/optional.h" @@ -7,7 +7,7 @@ #include "nmos/slog.h" // Stream Compatibility Management API implementation -// See https://github.com/AMWA-TV/is-11/blob/v1.0-dev/APIs/FlowCompatibilityManagementAPI.raml +// See https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/StreamCompatibilityManagementAPI.html namespace nmos { struct node_model; diff --git a/Development/nmos/streamcompatibility_resources.h b/Development/nmos/streamcompatibility_resources.h index 8c7c44949..77b4158ba 100644 --- a/Development/nmos/streamcompatibility_resources.h +++ b/Development/nmos/streamcompatibility_resources.h @@ -1,5 +1,5 @@ -#ifndef NMOS_FLOWCOMPATIBILITY_RESOURCES_H -#define NMOS_FLOWCOMPATIBILITY_RESOURCES_H +#ifndef NMOS_STREAMCOMPATIBILITY_RESOURCES_H +#define NMOS_STREAMCOMPATIBILITY_RESOURCES_H #include #include @@ -24,13 +24,13 @@ namespace nmos web::json::value make_streamcompatibility_edid_endpoint(const web::uri& edid_file, bool locked = false); web::json::value make_streamcompatibility_edid_endpoint(const utility::string_t& edid_file, bool locked = false); - // See https://github.com/AMWA-TV/is-11/blob/v1.0-dev/APIs/schemas/input.json + // See https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/input.html // Makes an input without EDID support nmos::resource make_streamcompatibility_input(const nmos::id& id, bool connected, const std::vector& senders, const nmos::settings& settings); // Makes an input with EDID support nmos::resource make_streamcompatibility_input(const nmos::id& id, bool connected, bool base_edid_changeable, const boost::variant& effective_edid, const bst::optional& effective_edid_properties, const std::vector& senders, const nmos::settings& settings); - // See https://github.com/AMWA-TV/is-11/blob/v1.0-dev/APIs/schemas/output.json + // See https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/output.html // Makes an output without EDID support nmos::resource make_streamcompatibility_output(const nmos::id& id, bool connected, const std::vector& receivers, const nmos::settings& settings); // Makes an output with EDID support From 0dcdd314c068d5f7c4fa3e6cc5072753ce7cd489 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Tue, 22 Nov 2022 20:40:40 +0400 Subject: [PATCH 036/109] Remove trailing commas --- Development/nmos/constraints.cpp | 6 +++--- Development/nmos/streamcompatibility_resources.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index 1e980b4f2..fc3b967b4 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -19,7 +19,7 @@ namespace nmos { // Audio Constraints - { nmos::caps::format::channel_count, [](const web::json::value& source, const web::json::value& con) { return nmos::match_integer_constraint((uint32_t)nmos::fields::channels(source).size(), con); } }, + { nmos::caps::format::channel_count, [](const web::json::value& source, const web::json::value& con) { return nmos::match_integer_constraint((uint32_t)nmos::fields::channels(source).size(), con); } } }; static const std::map> flow_constraints_matcher @@ -42,14 +42,14 @@ namespace nmos // Audio Constraints { nmos::caps::format::sample_rate, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_rational_constraint(nmos::parse_rational(nmos::fields::sample_rate(flow)), con); } }, - { nmos::caps::format::sample_depth, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_depth(flow), con); } }, + { nmos::caps::format::sample_depth, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_depth(flow), con); } } }; static const std::map> sender_constraints_matcher { { nmos::caps::transport::packet_transmission_mode, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::packet_transmission_mode(sender), con); } }, { nmos::caps::transport::st2110_21_sender_type, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::st2110_21_sender_type(sender), con); } }, - { nmos::caps::transport::bit_rate, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_rate(sender), con); } }, + { nmos::caps::transport::bit_rate, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_rate(sender), con); } } }; bool match_resource_parameters_constraint_set(const web::json::value& resource, const constraints_matcher& matcher, const web::json::value& constraint_set) diff --git a/Development/nmos/streamcompatibility_resources.cpp b/Development/nmos/streamcompatibility_resources.cpp index 44bc3b1b1..8e3c789ed 100644 --- a/Development/nmos/streamcompatibility_resources.cpp +++ b/Development/nmos/streamcompatibility_resources.cpp @@ -15,12 +15,12 @@ namespace nmos using web::json::value_of; auto active_constraint_sets = value_of({ - { nmos::fields::constraint_sets, constraint_sets }, + { nmos::fields::constraint_sets, constraint_sets } }); return value_of({ { nmos::fields::active_constraint_sets, active_constraint_sets }, - { nmos::fields::temporarily_locked, locked }, + { nmos::fields::temporarily_locked, locked } }); } From a538f15e9b4a1478ec08ce2780bfc6cd5e1ed071 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Tue, 22 Nov 2022 21:14:54 +0400 Subject: [PATCH 037/109] Move default connection_resource_patch_validator to nmos/connection_streamcompatibility_validator.{cpp,h} --- Development/cmake/NmosCppLibraries.cmake | 2 + .../nmos-cpp-node/node_implementation.cpp | 26 +----------- ...nnection_streamcompatibility_validator.cpp | 41 +++++++++++++++++++ ...connection_streamcompatibility_validator.h | 21 ++++++++++ 4 files changed, 66 insertions(+), 24 deletions(-) create mode 100644 Development/nmos/connection_streamcompatibility_validator.cpp create mode 100644 Development/nmos/connection_streamcompatibility_validator.h diff --git a/Development/cmake/NmosCppLibraries.cmake b/Development/cmake/NmosCppLibraries.cmake index 89fb7aa5d..2b92a39fd 100644 --- a/Development/cmake/NmosCppLibraries.cmake +++ b/Development/cmake/NmosCppLibraries.cmake @@ -914,6 +914,7 @@ set(NMOS_CPP_NMOS_SOURCES nmos/connection_api.cpp nmos/connection_events_activation.cpp nmos/connection_resources.cpp + nmos/connection_streamcompatibility_validator.cpp nmos/constraints.cpp nmos/did_sdid.cpp nmos/events_api.cpp @@ -990,6 +991,7 @@ set(NMOS_CPP_NMOS_HEADERS nmos/connection_api.h nmos/connection_events_activation.h nmos/connection_resources.h + nmos/connection_streamcompatibility_validator.h nmos/constraints.h nmos/device_type.h nmos/did_sdid.h diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 9ad7b466f..1b087406c 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -22,6 +22,7 @@ #include "nmos/colorspace.h" #include "nmos/connection_resources.h" #include "nmos/connection_events_activation.h" +#include "nmos/connection_streamcompatibility_validator.h" #include "nmos/events_resources.h" #include "nmos/format.h" #include "nmos/streamcompatibility_resources.h" @@ -1092,30 +1093,7 @@ nmos::transport_file_parser make_node_implementation_transport_file_parser() // Example Connection API callback to perform application-specific validation of the merged /staged endpoint during a PATCH /staged request nmos::details::connection_resource_patch_validator make_node_implementation_patch_validator(nmos::node_model& model) { - // "At any time if State of an active Sender becomes active_constraints_violation, the Sender MUST become inactive. - // An inactive Sender in this state MUST NOT allow activations. - // At any time if State of an active Receiver becomes non_compliant_stream, the Receiver SHOULD become inactive. - // An inactive Receiver in this state SHOULD NOT allow activations." - // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#preventing-restrictions-violation - return [&] (const nmos::resource& resource, const nmos::resource& connection_resource, const web::json::value& endpoint_staged, slog::base_gate& gate) - { - if (nmos::fields::master_enable(endpoint_staged)) - { - const auto id = nmos::fields::id(resource.data); - const auto type = resource.type; - - auto streamcompatibility_resource = find_resource(model.streamcompatibility_resources, {id, type}); - if (model.streamcompatibility_resources.end() != streamcompatibility_resource) - { - auto resource_state = nmos::fields::state(nmos::fields::status(streamcompatibility_resource->data)); - - if (resource_state == nmos::sender_states::active_constraints_violation.name || resource_state == nmos::receiver_states::non_compliant_stream.name) - { - throw std::logic_error("activation failed due to Status of the connector"); - } - } - } - }; + return nmos::experimental::make_connection_streamcompatibility_validator(model); } // Example Connection API activation callback to resolve "auto" values when /staged is transitioned to /active diff --git a/Development/nmos/connection_streamcompatibility_validator.cpp b/Development/nmos/connection_streamcompatibility_validator.cpp new file mode 100644 index 000000000..1312762a5 --- /dev/null +++ b/Development/nmos/connection_streamcompatibility_validator.cpp @@ -0,0 +1,41 @@ +#include "nmos/connection_streamcompatibility_validator.h" + +#include "nmos/json_fields.h" +#include "nmos/model.h" +#include "nmos/resource.h" +#include "nmos/resources.h" +#include "nmos/streamcompatibility_state.h" + +namespace nmos +{ + namespace experimental + { + // "At any time if State of an active Sender becomes active_constraints_violation, the Sender MUST become inactive. + // An inactive Sender in this state MUST NOT allow activations. + // At any time if State of an active Receiver becomes non_compliant_stream, the Receiver SHOULD become inactive. + // An inactive Receiver in this state SHOULD NOT allow activations." + // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#preventing-restrictions-violation + nmos::details::connection_resource_patch_validator make_connection_streamcompatibility_validator(nmos::node_model& model) + { + return [&model] (const nmos::resource& resource, const nmos::resource& connection_resource, const web::json::value& endpoint_staged, slog::base_gate& gate) + { + if (nmos::fields::master_enable(endpoint_staged)) + { + const auto id = nmos::fields::id(resource.data); + const auto type = resource.type; + + auto streamcompatibility_resource = find_resource(model.streamcompatibility_resources, {id, type}); + if (model.streamcompatibility_resources.end() != streamcompatibility_resource) + { + auto resource_state = nmos::fields::state(nmos::fields::status(streamcompatibility_resource->data)); + + if (resource_state == nmos::sender_states::active_constraints_violation.name || resource_state == nmos::receiver_states::non_compliant_stream.name) + { + throw std::logic_error("activation failed due to Status of the connector"); + } + } + } + }; + } + } +} diff --git a/Development/nmos/connection_streamcompatibility_validator.h b/Development/nmos/connection_streamcompatibility_validator.h new file mode 100644 index 000000000..18f1b5e9f --- /dev/null +++ b/Development/nmos/connection_streamcompatibility_validator.h @@ -0,0 +1,21 @@ +#ifndef NMOS_CONNECTION_STREAMCOMPATIBILITY_VALIDATOR_H +#define NMOS_CONNECTION_STREAMCOMPATIBILITY_VALIDATOR_H + +#include "nmos/connection_api.h" + +namespace nmos +{ + struct node_model; + + namespace experimental + { + // "At any time if State of an active Sender becomes active_constraints_violation, the Sender MUST become inactive. + // An inactive Sender in this state MUST NOT allow activations. + // At any time if State of an active Receiver becomes non_compliant_stream, the Receiver SHOULD become inactive. + // An inactive Receiver in this state SHOULD NOT allow activations." + // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#preventing-restrictions-violation + nmos::details::connection_resource_patch_validator make_connection_streamcompatibility_validator(nmos::node_model& model); + } +} + +#endif From 88012989358526a6100ebc8ca2f20f0fda4cdd3d Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Wed, 23 Nov 2022 06:31:10 +0400 Subject: [PATCH 038/109] Replace validate_sender_resources with make_streamcompatibility_sender_resources_validator --- Development/cmake/NmosCppLibraries.cmake | 4 +- .../nmos-cpp-node/node_implementation.cpp | 5 +- Development/nmos/connection_api.h | 1 + ...nnection_streamcompatibility_validator.cpp | 41 ------ ...connection_streamcompatibility_validator.h | 21 --- Development/nmos/constraints.cpp | 73 +---------- Development/nmos/constraints.h | 6 - .../nmos/streamcompatibility_behaviour.cpp | 69 ++-------- .../nmos/streamcompatibility_behaviour.h | 3 +- .../nmos/streamcompatibility_validation.cpp | 124 ++++++++++++++++++ .../nmos/streamcompatibility_validation.h | 91 +++++++++++++ 11 files changed, 232 insertions(+), 206 deletions(-) delete mode 100644 Development/nmos/connection_streamcompatibility_validator.cpp delete mode 100644 Development/nmos/connection_streamcompatibility_validator.h create mode 100644 Development/nmos/streamcompatibility_validation.cpp create mode 100644 Development/nmos/streamcompatibility_validation.h diff --git a/Development/cmake/NmosCppLibraries.cmake b/Development/cmake/NmosCppLibraries.cmake index 2b92a39fd..d381c335c 100644 --- a/Development/cmake/NmosCppLibraries.cmake +++ b/Development/cmake/NmosCppLibraries.cmake @@ -914,7 +914,7 @@ set(NMOS_CPP_NMOS_SOURCES nmos/connection_api.cpp nmos/connection_events_activation.cpp nmos/connection_resources.cpp - nmos/connection_streamcompatibility_validator.cpp + nmos/streamcompatibility_validation.cpp nmos/constraints.cpp nmos/did_sdid.cpp nmos/events_api.cpp @@ -991,7 +991,7 @@ set(NMOS_CPP_NMOS_HEADERS nmos/connection_api.h nmos/connection_events_activation.h nmos/connection_resources.h - nmos/connection_streamcompatibility_validator.h + nmos/streamcompatibility_validation.h nmos/constraints.h nmos/device_type.h nmos/did_sdid.h diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 1b087406c..5eeb1ad1c 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -22,7 +22,7 @@ #include "nmos/colorspace.h" #include "nmos/connection_resources.h" #include "nmos/connection_events_activation.h" -#include "nmos/connection_streamcompatibility_validator.h" +#include "nmos/streamcompatibility_validation.h" #include "nmos/events_resources.h" #include "nmos/format.h" #include "nmos/streamcompatibility_resources.h" @@ -1442,8 +1442,7 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler nmos::experimental::details::streamcompatibility_sender_validator make_node_implementation_streamcompatibility_sender_validator() { - // this example uses the default sender validator explicitly - return &nmos::experimental::validate_sender_resources; + return nmos::experimental::make_streamcompatibility_sender_resources_validator(); } nmos::experimental::details::streamcompatibility_receiver_validator make_node_implementation_streamcompatibility_receiver_validator() diff --git a/Development/nmos/connection_api.h b/Development/nmos/connection_api.h index 6a7cd47be..3e73e96f9 100644 --- a/Development/nmos/connection_api.h +++ b/Development/nmos/connection_api.h @@ -50,6 +50,7 @@ namespace nmos // and experimental Manifest API for Node API "manifest_href" namespace details { + std::pair get_transport_type_data(const web::json::value& transport_file); void handle_connection_resource_patch(web::http::http_response res, nmos::node_model& model, const nmos::api_version& version, const std::pair& id_type, const web::json::value& patch, transport_file_parser parse_transport_file, connection_resource_patch_validator validate_merged, slog::base_gate& gate); void handle_connection_resource_transportfile(web::http::http_response res, const nmos::node_model& model, const nmos::api_version& version, const std::pair& id_type, const utility::string_t& accept, slog::base_gate& gate); } diff --git a/Development/nmos/connection_streamcompatibility_validator.cpp b/Development/nmos/connection_streamcompatibility_validator.cpp deleted file mode 100644 index 1312762a5..000000000 --- a/Development/nmos/connection_streamcompatibility_validator.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "nmos/connection_streamcompatibility_validator.h" - -#include "nmos/json_fields.h" -#include "nmos/model.h" -#include "nmos/resource.h" -#include "nmos/resources.h" -#include "nmos/streamcompatibility_state.h" - -namespace nmos -{ - namespace experimental - { - // "At any time if State of an active Sender becomes active_constraints_violation, the Sender MUST become inactive. - // An inactive Sender in this state MUST NOT allow activations. - // At any time if State of an active Receiver becomes non_compliant_stream, the Receiver SHOULD become inactive. - // An inactive Receiver in this state SHOULD NOT allow activations." - // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#preventing-restrictions-violation - nmos::details::connection_resource_patch_validator make_connection_streamcompatibility_validator(nmos::node_model& model) - { - return [&model] (const nmos::resource& resource, const nmos::resource& connection_resource, const web::json::value& endpoint_staged, slog::base_gate& gate) - { - if (nmos::fields::master_enable(endpoint_staged)) - { - const auto id = nmos::fields::id(resource.data); - const auto type = resource.type; - - auto streamcompatibility_resource = find_resource(model.streamcompatibility_resources, {id, type}); - if (model.streamcompatibility_resources.end() != streamcompatibility_resource) - { - auto resource_state = nmos::fields::state(nmos::fields::status(streamcompatibility_resource->data)); - - if (resource_state == nmos::sender_states::active_constraints_violation.name || resource_state == nmos::receiver_states::non_compliant_stream.name) - { - throw std::logic_error("activation failed due to Status of the connector"); - } - } - } - }; - } - } -} diff --git a/Development/nmos/connection_streamcompatibility_validator.h b/Development/nmos/connection_streamcompatibility_validator.h deleted file mode 100644 index 18f1b5e9f..000000000 --- a/Development/nmos/connection_streamcompatibility_validator.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef NMOS_CONNECTION_STREAMCOMPATIBILITY_VALIDATOR_H -#define NMOS_CONNECTION_STREAMCOMPATIBILITY_VALIDATOR_H - -#include "nmos/connection_api.h" - -namespace nmos -{ - struct node_model; - - namespace experimental - { - // "At any time if State of an active Sender becomes active_constraints_violation, the Sender MUST become inactive. - // An inactive Sender in this state MUST NOT allow activations. - // At any time if State of an active Receiver becomes non_compliant_stream, the Receiver SHOULD become inactive. - // An inactive Receiver in this state SHOULD NOT allow activations." - // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#preventing-restrictions-violation - nmos::details::connection_resource_patch_validator make_connection_streamcompatibility_validator(nmos::node_model& model); - } -} - -#endif diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index fc3b967b4..6e9c11648 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -4,81 +4,12 @@ #include #include "nmos/capabilities.h" #include "nmos/json_fields.h" -#include "nmos/sdp_utils.h" -#include "nmos/video_jxsv.h" +#include "nmos/rational.h" namespace nmos { namespace experimental { - typedef std::map> constraints_matcher; - - // NMOS Parameter Registers - Capabilities register - // See https://github.com/AMWA-TV/nmos-parameter-registers/blob/main/capabilities/README.md - static const std::map> source_constraints_matcher - { - // Audio Constraints - - { nmos::caps::format::channel_count, [](const web::json::value& source, const web::json::value& con) { return nmos::match_integer_constraint((uint32_t)nmos::fields::channels(source).size(), con); } } - }; - - static const std::map> flow_constraints_matcher - { - // General Constraints - - { nmos::caps::format::media_type, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::media_type(flow), con); } }, - { nmos::caps::format::grain_rate, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_rational_constraint(nmos::parse_rational(nmos::fields::grain_rate(flow)), con); } }, - - // Video Constraints - - { nmos::caps::format::frame_height, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::frame_height(flow), con); } }, - { nmos::caps::format::frame_width, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::frame_width(flow), con); } }, - { nmos::caps::format::color_sampling, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::details::make_sampling(nmos::fields::components(flow)).name, con); } }, - { nmos::caps::format::interlace_mode, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::interlace_mode(flow), con); } }, - { nmos::caps::format::colorspace, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::colorspace(flow), con); } }, - { nmos::caps::format::transfer_characteristic, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::transfer_characteristic(flow), con); } }, - { nmos::caps::format::component_depth, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_depth(nmos::fields::components(flow).at(0)), con); } }, - - // Audio Constraints - - { nmos::caps::format::sample_rate, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_rational_constraint(nmos::parse_rational(nmos::fields::sample_rate(flow)), con); } }, - { nmos::caps::format::sample_depth, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_depth(flow), con); } } - }; - - static const std::map> sender_constraints_matcher - { - { nmos::caps::transport::packet_transmission_mode, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::packet_transmission_mode(sender), con); } }, - { nmos::caps::transport::st2110_21_sender_type, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::st2110_21_sender_type(sender), con); } }, - { nmos::caps::transport::bit_rate, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_rate(sender), con); } } - }; - - bool match_resource_parameters_constraint_set(const web::json::value& resource, const constraints_matcher& matcher, const web::json::value& constraint_set) - { - if (!nmos::caps::meta::enabled(constraint_set)) return true; - - const auto& constraints = constraint_set.as_object(); - return constraints.end() == std::find_if(constraints.begin(), constraints.end(), [&](const std::pair& constraint) - { - const auto& found = matcher.find(constraint.first); - return matcher.end() != found && !found->second(resource, constraint.second); - }); - } - - bool match_source_parameters_constraint_set(const web::json::value& source, const web::json::value& constraint_set) - { - return match_resource_parameters_constraint_set(source, source_constraints_matcher, constraint_set); - } - - bool match_flow_parameters_constraint_set(const web::json::value& flow, const web::json::value& constraint_set) - { - return match_resource_parameters_constraint_set(flow, flow_constraints_matcher, constraint_set); - } - - bool match_sender_parameters_constraint_set(const web::json::value& sender, const web::json::value& constraint_set) - { - return match_resource_parameters_constraint_set(sender, sender_constraints_matcher, constraint_set); - } - bool is_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint) { // subconstraint should have enum if constraint has enum @@ -154,7 +85,7 @@ namespace nmos return param_constraints_subset.end() == std::find_if(param_constraints_subset.begin(), param_constraints_subset.end(), [&](const std::pair& subconstraint) { - if (boost::algorithm::istarts_with(subconstraint.first, U("urn:x-nmos:cap:meta:"))) return false; + if (boost::algorithm::starts_with(subconstraint.first, U("urn:x-nmos:cap:meta:"))) return false; const auto& constraint = param_constraints_set.find(subconstraint.first); return param_constraints_set.end() == constraint || !is_subconstraint(constraint->second, subconstraint.second); diff --git a/Development/nmos/constraints.h b/Development/nmos/constraints.h index f6ca8e811..dc33b4ced 100644 --- a/Development/nmos/constraints.h +++ b/Development/nmos/constraints.h @@ -1,9 +1,6 @@ #ifndef NMOS_CONSTRAINTS_H #define NMOS_CONSTRAINTS_H -#include "nmos/capabilities.h" -#include "nmos/json_fields.h" - namespace web { namespace json @@ -16,9 +13,6 @@ namespace nmos { namespace experimental { - bool match_source_parameters_constraint_set(const web::json::value& source, const web::json::value& constraint_set); - bool match_flow_parameters_constraint_set(const web::json::value& flow, const web::json::value& constraint_set); - bool match_sender_parameters_constraint_set(const web::json::value& sender, const web::json::value& constraint_set); bool is_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint); bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset); } diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index 9847c5b49..8b677b15c 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -8,6 +8,7 @@ #include "nmos/activation_mode.h" #include "nmos/activation_utils.h" #include "nmos/capabilities.h" // for constraint_sets +#include "nmos/connection_api.h" // for get_transport_type_data #include "nmos/constraints.h" #include "nmos/id.h" #include "nmos/media_type.h" @@ -21,27 +22,6 @@ namespace nmos { namespace experimental { - utility::string_t get_sdp_data(const web::json::value& transport_file) - { - // "'data' and 'type' must both be strings or both be null" - // See https://specs.amwa.tv/is-05/releases/v1.0.2/APIs/schemas/with-refs/v1.0-receiver-response-schema.html - // and https://specs.amwa.tv/is-05/releases/v1.1.0/APIs/schemas/with-refs/receiver-transport-file.html - - if (!transport_file.has_field(nmos::fields::data)) throw std::logic_error("data is required"); - - auto& transport_data = transport_file.at(nmos::fields::data); - if (transport_data.is_null()) throw std::logic_error("data is required"); - - if (!transport_file.has_field(nmos::fields::type)) throw std::logic_error("type is required"); - - auto& transport_type = transport_file.at(nmos::fields::type); - if (transport_type.is_null() || transport_type.as_string().empty()) throw std::logic_error("type is required"); - - if (nmos::media_types::application_sdp.name != transport_type.as_string()) throw std::logic_error("transport file type is not SDP"); - - return transport_data.as_string(); - } - std::vector get_resources_ids(const nmos::resources& resources, const nmos::type& type) { return boost::copy_range>(resources | boost::adaptors::filtered([&type] (const nmos::resource& resource) @@ -53,42 +33,6 @@ namespace nmos })); } - std::pair validate_sender_resources(const web::json::value& transport_file, const web::json::value& sender, const web::json::value& flow, const web::json::value& source, const web::json::array& constraint_sets) - { - nmos::sender_state sender_state; - - if (!web::json::empty(constraint_sets)) - { - bool constrained = true; - - auto source_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return match_source_parameters_constraint_set(source, constraint_set); }); - auto flow_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return match_flow_parameters_constraint_set(flow, constraint_set); }); - auto sender_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return match_sender_parameters_constraint_set(sender, constraint_set); }); - - constrained = constraint_sets.end() != source_found && constraint_sets.end() != flow_found && constraint_sets.end() != sender_found; - - if (!transport_file.is_null() && !transport_file.as_object().empty()) - { - utility::string_t sdp_data = get_sdp_data(transport_file); - const auto session_description = sdp::parse_session_description(utility::us2s(sdp_data)); - auto sdp_params = nmos::parse_session_description(session_description).first; - - const auto format_params = nmos::details::get_format_parameters(sdp_params); - const auto sdp_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return nmos::details::match_sdp_parameters_constraint_set(nmos::details::format_constraints, sdp_params, format_params, constraint_set); }); - - constrained = constrained && constraint_sets.end() != sdp_found; - } - - sender_state = constrained ? nmos::sender_states::constrained : nmos::sender_states::active_constraints_violation; - } - else - { - sender_state = nmos::sender_states::unconstrained; - } - - return { sender_state, {} }; - } - std::pair validate_receiver_resources(const web::json::value& transport_file, const web::json::value& receiver) { nmos::receiver_state receiver_state; @@ -96,8 +40,13 @@ namespace nmos if (!transport_file.is_null() && !transport_file.as_object().empty()) { - utility::string_t sdp_data = get_sdp_data(transport_file); - const auto session_description = sdp::parse_session_description(utility::us2s(sdp_data)); + const auto [transport_file_type, transport_file_data] = nmos::details::get_transport_type_data(transport_file); + if (nmos::media_types::application_sdp.name != transport_file_type) + { + throw std::runtime_error("unknown transport file type"); + } + + const auto session_description = sdp::parse_session_description(utility::us2s(transport_file_data)); auto sdp_params = nmos::parse_session_description(session_description).first; receiver_state = nmos::receiver_states::compliant_stream; @@ -185,7 +134,7 @@ namespace nmos slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " is being validated with its Flow, Source and transport file"; if (validate_sender) { - std::tie(sender_state, sender_state_debug) = validate_sender(transport_file, sender->data, flow->data, source->data, constraint_sets); + std::tie(sender_state, sender_state_debug) = validate_sender(transport_file, *sender, *flow, *source, constraint_sets); } } diff --git a/Development/nmos/streamcompatibility_behaviour.h b/Development/nmos/streamcompatibility_behaviour.h index 1820d4d9f..868f7b8db 100644 --- a/Development/nmos/streamcompatibility_behaviour.h +++ b/Development/nmos/streamcompatibility_behaviour.h @@ -4,6 +4,7 @@ #include #include "nmos/streamcompatibility_state.h" +#include "nmos/streamcompatibility_validation.h" namespace slog { @@ -27,11 +28,9 @@ namespace nmos { namespace details { - typedef std::function(const web::json::value& transport_file, const web::json::value& sender, const web::json::value& flow, const web::json::value& source, const web::json::array& constraint_sets)> streamcompatibility_sender_validator; typedef std::function(const web::json::value& transport_file, const web::json::value& receiver)> streamcompatibility_receiver_validator; } - std::pair validate_sender_resources(const web::json::value& transport_file, const web::json::value& sender, const web::json::value& flow, const web::json::value& source, const web::json::array& constraint_sets); std::pair validate_receiver_resources(const web::json::value& transport_file, const web::json::value& receiver); void streamcompatibility_behaviour_thread(nmos::node_model& model, details::streamcompatibility_sender_validator validate_sender, details::streamcompatibility_receiver_validator validate_receiver, slog::base_gate& gate); } diff --git a/Development/nmos/streamcompatibility_validation.cpp b/Development/nmos/streamcompatibility_validation.cpp new file mode 100644 index 000000000..67bc12549 --- /dev/null +++ b/Development/nmos/streamcompatibility_validation.cpp @@ -0,0 +1,124 @@ +#include "nmos/streamcompatibility_validation.h" + +#include "nmos/json_fields.h" +#include "nmos/model.h" +#include "nmos/resource.h" +#include "nmos/resources.h" +#include "nmos/streamcompatibility_state.h" +#include "sdp/sdp.h" + +namespace nmos +{ + namespace experimental + { + bool match_resource_parameters_constraint_set(const parameter_constraints& constraints, const web::json::value& resource, const web::json::value& constraint_set_) + { + if (!nmos::caps::meta::enabled(constraint_set_)) return true; + + const auto& constraint_set = constraint_set_.as_object(); + return constraint_set.end() == std::find_if(constraint_set.begin(), constraint_set.end(), [&](const std::pair& constraint) + { + const auto found = constraints.find(constraint.first); + return constraints.end() != found && !found->second(resource, constraint.second); + }); + } + + // "At any time if State of an active Sender becomes active_constraints_violation, the Sender MUST become inactive. + // An inactive Sender in this state MUST NOT allow activations. + // At any time if State of an active Receiver becomes non_compliant_stream, the Receiver SHOULD become inactive. + // An inactive Receiver in this state SHOULD NOT allow activations." + // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#preventing-restrictions-violation + nmos::details::connection_resource_patch_validator make_connection_streamcompatibility_validator(nmos::node_model& model) + { + return [&model] (const nmos::resource& resource, const nmos::resource& connection_resource, const web::json::value& endpoint_staged, slog::base_gate& gate) + { + if (nmos::fields::master_enable(endpoint_staged)) + { + const auto id = nmos::fields::id(resource.data); + const auto type = resource.type; + + auto streamcompatibility_resource = find_resource(model.streamcompatibility_resources, {id, type}); + if (model.streamcompatibility_resources.end() != streamcompatibility_resource) + { + auto resource_state = nmos::fields::state(nmos::fields::status(streamcompatibility_resource->data)); + + if (resource_state == nmos::sender_states::active_constraints_violation.name || resource_state == nmos::receiver_states::non_compliant_stream.name) + { + throw std::logic_error("activation failed due to Status of the connector"); + } + } + } + }; + } + + details::streamcompatibility_sender_validator make_streamcompatibility_sender_resources_validator() + { + const details::resource_constraints_matcher match_resource_constraint_set = [](const nmos::resource& resource, const web::json::value& constraint_set) -> bool + { + const std::map resource_parameter_constraints + { + { nmos::types::source, source_parameter_constraints }, + { nmos::types::flow, flow_parameter_constraints }, + { nmos::types::sender, sender_parameter_constraints } + }; + + if (0 == resource_parameter_constraints.count(resource.type)) + { + throw std::logic_error("wrong resource type"); + } + + return match_resource_parameters_constraint_set(resource_parameter_constraints.at(resource.type), resource.data, constraint_set); + }; + + const details::transport_file_constraint_sets_matcher match_transport_file_constraint_sets = [](const std::pair& transport_file, const web::json::array& constraint_sets) -> bool + { + const auto& [transport_file_type, transport_file_data] = transport_file; + if (nmos::media_types::application_sdp.name != transport_file_type) + { + throw std::runtime_error("unknown transport file type"); + } + const auto session_description = sdp::parse_session_description(utility::us2s(transport_file_data)); + auto sdp_params = nmos::parse_session_description(session_description).first; + + const auto format_params = nmos::details::get_format_parameters(sdp_params); + const auto sdp_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return nmos::details::match_sdp_parameters_constraint_set(nmos::details::format_constraints, sdp_params, format_params, constraint_set); }); + + return constraint_sets.end() != sdp_found; + }; + + return make_streamcompatibility_sender_resources_validator(match_resource_constraint_set, match_transport_file_constraint_sets); + } + + details::streamcompatibility_sender_validator make_streamcompatibility_sender_resources_validator(const details::resource_constraints_matcher& match_resource_constraint_set, const details::transport_file_constraint_sets_matcher& match_transport_file_constraint_sets) + { + return [match_resource_constraint_set, match_transport_file_constraint_sets](const web::json::value& transport_file, const nmos::resource& sender, const nmos::resource& flow, const nmos::resource& source, const web::json::array& constraint_sets) -> std::pair + { + nmos::sender_state sender_state; + + if (!web::json::empty(constraint_sets)) + { + bool constrained = true; + + auto source_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return match_resource_constraint_set(source, constraint_set); }); + auto flow_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return match_resource_constraint_set(flow, constraint_set); }); + auto sender_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return match_resource_constraint_set(sender, constraint_set); }); + + constrained = constraint_sets.end() != source_found && constraint_sets.end() != flow_found && constraint_sets.end() != sender_found; + + if (!transport_file.is_null() && !transport_file.as_object().empty()) + { + constrained = constrained && match_transport_file_constraint_sets(nmos::details::get_transport_type_data(transport_file), constraint_sets); + } + + sender_state = constrained ? nmos::sender_states::constrained : nmos::sender_states::active_constraints_violation; + } + else + { + sender_state = nmos::sender_states::unconstrained; + } + + return { sender_state, {} }; + }; + } + } +} diff --git a/Development/nmos/streamcompatibility_validation.h b/Development/nmos/streamcompatibility_validation.h new file mode 100644 index 000000000..1356bca91 --- /dev/null +++ b/Development/nmos/streamcompatibility_validation.h @@ -0,0 +1,91 @@ +#ifndef NMOS_STREAMCOMPATIBILITY_VALIDATION_H +#define NMOS_STREAMCOMPATIBILITY_VALIDATION_H + +#include +#include +#include "nmos/capabilities.h" +#include "nmos/connection_api.h" +#include "nmos/resource.h" +#include "nmos/sdp_utils.h" +#include "nmos/streamcompatibility_state.h" +#include "nmos/video_jxsv.h" + +namespace web +{ + namespace json + { + class array; + class value; + } +} + +namespace nmos +{ + struct node_model; + + namespace experimental + { + namespace details + { + typedef std::function(const web::json::value& transport_file, const nmos::resource& sender, const nmos::resource& flow, const nmos::resource& source, const web::json::array& constraint_sets)> streamcompatibility_sender_validator; + typedef std::function resource_constraints_matcher; + typedef std::function& transport_file, const web::json::array& constraint_sets)> transport_file_constraint_sets_matcher; + } + + typedef std::map> parameter_constraints; + + // NMOS Parameter Registers - Capabilities register + // See https://github.com/AMWA-TV/nmos-parameter-registers/blob/main/capabilities/README.md + const std::map> source_parameter_constraints + { + // Audio Constraints + + { nmos::caps::format::channel_count, [](const web::json::value& source, const web::json::value& con) { return nmos::match_integer_constraint((uint32_t)nmos::fields::channels(source).size(), con); } } + }; + + const std::map> flow_parameter_constraints + { + // General Constraints + + { nmos::caps::format::media_type, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::media_type(flow), con); } }, + { nmos::caps::format::grain_rate, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_rational_constraint(nmos::parse_rational(nmos::fields::grain_rate(flow)), con); } }, + + // Video Constraints + + { nmos::caps::format::frame_height, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::frame_height(flow), con); } }, + { nmos::caps::format::frame_width, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::frame_width(flow), con); } }, + { nmos::caps::format::color_sampling, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::details::make_sampling(nmos::fields::components(flow)).name, con); } }, + { nmos::caps::format::interlace_mode, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::interlace_mode(flow), con); } }, + { nmos::caps::format::colorspace, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::colorspace(flow), con); } }, + { nmos::caps::format::transfer_characteristic, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::transfer_characteristic(flow), con); } }, + { nmos::caps::format::component_depth, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_depth(nmos::fields::components(flow).at(0)), con); } }, + + // Audio Constraints + + { nmos::caps::format::sample_rate, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_rational_constraint(nmos::parse_rational(nmos::fields::sample_rate(flow)), con); } }, + { nmos::caps::format::sample_depth, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_depth(flow), con); } } + }; + + const std::map> sender_parameter_constraints + { + { nmos::caps::transport::packet_transmission_mode, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::packet_transmission_mode(sender), con); } }, + { nmos::caps::transport::st2110_21_sender_type, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::st2110_21_sender_type(sender), con); } }, + { nmos::caps::transport::bit_rate, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_rate(sender), con); } } + }; + + bool match_resource_parameters_constraint_set(const parameter_constraints& constraints, const web::json::value& resource, const web::json::value& constraint_set); + + // "At any time if State of an active Sender becomes active_constraints_violation, the Sender MUST become inactive. + // An inactive Sender in this state MUST NOT allow activations. + // At any time if State of an active Receiver becomes non_compliant_stream, the Receiver SHOULD become inactive. + // An inactive Receiver in this state SHOULD NOT allow activations." + // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#preventing-restrictions-violation + nmos::details::connection_resource_patch_validator make_connection_streamcompatibility_validator(nmos::node_model& model); + + details::streamcompatibility_sender_validator make_streamcompatibility_sender_resources_validator(const details::resource_constraints_matcher& resource_matcher, const details::transport_file_constraint_sets_matcher& transport_file_matcher); + + details::streamcompatibility_sender_validator make_streamcompatibility_sender_resources_validator(); + } +} + +#endif From b62883e07d3c95f30e6f8ccb780c5716a5ea2b5b Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Wed, 23 Nov 2022 07:40:07 +0400 Subject: [PATCH 039/109] Fix inverted logic in subconstraint and constraint subset definitions --- Development/nmos/constraints.cpp | 27 ++++++-------- .../nmos/streamcompatibility_validation.cpp | 2 +- Development/nmos/test/constraints_test.cpp | 37 +++++++++++++++++-- 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index 6e9c11648..8724526be 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -10,6 +10,10 @@ namespace nmos { namespace experimental { + // Constraint B is a subconstraint of Constraint A if: + // 1. Constraint B has enum keyword when Constraint A has it and enumerated values of Constraint B are a subset of enumerated values of Constraint A + // 2. Constraint B has enum or minimum keyword when Constraint A has minimum keyword and allowed values of Constraint B are greater than minimum value of Constraint A + // 3. Constraint B has enum or maximum keyword when Constraint A has maximum keyword and allowed values of Constraint B are less than maximum value of Constraint A bool is_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint) { // subconstraint should have enum if constraint has enum @@ -59,23 +63,16 @@ namespace nmos if (subconstraint.has_field(nmos::fields::constraint_enum)) { const auto& subconstraint_enum_values = nmos::fields::constraint_enum(subconstraint).as_array(); - if (subconstraint_enum_values.end() == std::find_if(subconstraint_enum_values.begin(), subconstraint_enum_values.end(), [&constraint](const web::json::value& enum_value) + return subconstraint_enum_values.end() == std::find_if_not(subconstraint_enum_values.begin(), subconstraint_enum_values.end(), [&constraint](const web::json::value& enum_value) { return match_constraint(enum_value, constraint); - })) - { - return false; - } + }); } return true; } - // Constraint Set B is a subset of Constraint Set A if all of Parameter Constraints of Constraint Set B, except for meta, are present in Constraint Set A and each Parameter Constraint of Constraint Set B, except for meta, is a subconstraint of the according Parameter Constraint of Constraint Set A. - // Constraint B is a subconstraint of a Constraint A if: - - // 1. Constraint B has enum keyword when Constraint A has it and enum of Constraint B is a subset of enum of Constraint A - // 2. Constraint B has enum or minimum keyword when Constraint A has minimum keyword and allowed values for Constraint B are less than allowed values for Constraint A - // 3. Constraint B has enum or maximum keyword when Constraint A has maximum keyword and allowed values for Constraint B are greater than allowed values for Constraint A + // Constraint Set B is a subset of Constraint Set A if all Parameter Constraints of Constraint Set A are present in Constraint Set B, and for each Parameter Constraint + // that is present in both, the Parameter Constraint of Constraint Set B is a subconstraint of the Parameter Constraint of Constraint Set A. bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset) { using web::json::value; @@ -83,12 +80,12 @@ namespace nmos const auto& param_constraints_set = constraint_set.as_object(); const auto& param_constraints_subset = constraint_subset.as_object(); - return param_constraints_subset.end() == std::find_if(param_constraints_subset.begin(), param_constraints_subset.end(), [&](const std::pair& subconstraint) + return param_constraints_set.end() == std::find_if_not(param_constraints_set.begin(), param_constraints_set.end(), [¶m_constraints_subset](const std::pair& constraint) { - if (boost::algorithm::starts_with(subconstraint.first, U("urn:x-nmos:cap:meta:"))) return false; + if (boost::algorithm::starts_with(constraint.first, U("urn:x-nmos:cap:meta:"))) return true; - const auto& constraint = param_constraints_set.find(subconstraint.first); - return param_constraints_set.end() == constraint || !is_subconstraint(constraint->second, subconstraint.second); + const auto& subconstraint = param_constraints_subset.find(constraint.first); + return param_constraints_subset.end() != subconstraint && is_subconstraint(constraint.second, subconstraint->second); }); } } diff --git a/Development/nmos/streamcompatibility_validation.cpp b/Development/nmos/streamcompatibility_validation.cpp index 67bc12549..4fc865496 100644 --- a/Development/nmos/streamcompatibility_validation.cpp +++ b/Development/nmos/streamcompatibility_validation.cpp @@ -13,7 +13,7 @@ namespace nmos { bool match_resource_parameters_constraint_set(const parameter_constraints& constraints, const web::json::value& resource, const web::json::value& constraint_set_) { - if (!nmos::caps::meta::enabled(constraint_set_)) return true; + if (!nmos::caps::meta::enabled(constraint_set_)) return false; const auto& constraint_set = constraint_set_.as_object(); return constraint_set.end() == std::find_if(constraint_set.begin(), constraint_set.end(), [&](const std::pair& constraint) diff --git a/Development/nmos/test/constraints_test.cpp b/Development/nmos/test/constraints_test.cpp index 7743ecd67..6387cb7f7 100644 --- a/Development/nmos/test/constraints_test.cpp +++ b/Development/nmos/test/constraints_test.cpp @@ -5,6 +5,35 @@ #include "bst/test/test.h" +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testSimpleCase) +{ + { + using web::json::value; + using web::json::value_of; + using nmos::experimental::is_constraint_subset; + + auto a = value_of({ + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({}, 1920) } + }); + + auto b1 = value_of({ + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({}, 2000) }, + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, + }); + + auto b2 = value_of({ + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({}, 1900) } + }); + + auto b3 = value::object(); + + BST_REQUIRE(is_constraint_subset(a, b1)); + BST_REQUIRE(!is_constraint_subset(a, b2)); + BST_REQUIRE(!is_constraint_subset(a, b3)); + } +} + //////////////////////////////////////////////////////////////////////////////////////////// BST_TEST_CASE(testLessConstraints) { @@ -12,7 +41,7 @@ BST_TEST_CASE(testLessConstraints) using web::json::value_of; using nmos::experimental::is_constraint_subset; - auto constraint_set = value_of({ + auto a = value_of({ { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25, nmos::rates::rate29_97 }) }, { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, @@ -25,15 +54,15 @@ BST_TEST_CASE(testLessConstraints) { nmos::caps::transport::st2110_21_sender_type, nmos::make_caps_string_constraint({ sdp::type_parameters::type_N.name }) } }); - auto constraint_subset = value_of({ + auto b = value_of({ { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25, nmos::rates::rate29_97 }) }, { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) } }); - BST_REQUIRE(is_constraint_subset(constraint_set, constraint_subset)); - BST_REQUIRE(!is_constraint_subset(constraint_subset, constraint_set)); + BST_REQUIRE(!is_constraint_subset(a, b)); + BST_REQUIRE(is_constraint_subset(b, a)); } } From e83e7d99d2e8f29ad3e7b81c7784320496181209 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Wed, 23 Nov 2022 07:57:52 +0400 Subject: [PATCH 040/109] Get rid of structured bindings --- Development/nmos/streamcompatibility_behaviour.cpp | 10 +++++----- Development/nmos/streamcompatibility_validation.cpp | 5 ++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index 8b677b15c..d9d2c8ebb 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -33,20 +33,20 @@ namespace nmos })); } - std::pair validate_receiver_resources(const web::json::value& transport_file, const web::json::value& receiver) + std::pair validate_receiver_resources(const web::json::value& transport_file_, const web::json::value& receiver) { nmos::receiver_state receiver_state; utility::string_t receiver_state_debug; - if (!transport_file.is_null() && !transport_file.as_object().empty()) + if (!transport_file_.is_null() && !transport_file_.as_object().empty()) { - const auto [transport_file_type, transport_file_data] = nmos::details::get_transport_type_data(transport_file); - if (nmos::media_types::application_sdp.name != transport_file_type) + const auto transport_file = nmos::details::get_transport_type_data(transport_file_); + if (nmos::media_types::application_sdp.name != transport_file.first) { throw std::runtime_error("unknown transport file type"); } - const auto session_description = sdp::parse_session_description(utility::us2s(transport_file_data)); + const auto session_description = sdp::parse_session_description(utility::us2s(transport_file.second)); auto sdp_params = nmos::parse_session_description(session_description).first; receiver_state = nmos::receiver_states::compliant_stream; diff --git a/Development/nmos/streamcompatibility_validation.cpp b/Development/nmos/streamcompatibility_validation.cpp index 4fc865496..4414f63a1 100644 --- a/Development/nmos/streamcompatibility_validation.cpp +++ b/Development/nmos/streamcompatibility_validation.cpp @@ -72,12 +72,11 @@ namespace nmos const details::transport_file_constraint_sets_matcher match_transport_file_constraint_sets = [](const std::pair& transport_file, const web::json::array& constraint_sets) -> bool { - const auto& [transport_file_type, transport_file_data] = transport_file; - if (nmos::media_types::application_sdp.name != transport_file_type) + if (nmos::media_types::application_sdp.name != transport_file.first) { throw std::runtime_error("unknown transport file type"); } - const auto session_description = sdp::parse_session_description(utility::us2s(transport_file_data)); + const auto session_description = sdp::parse_session_description(utility::us2s(transport_file.second)); auto sdp_params = nmos::parse_session_description(session_description).first; const auto format_params = nmos::details::get_format_parameters(sdp_params); From 8084118aa04a92c8da0369e865263c1ec5dbaae5 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Wed, 30 Nov 2022 06:02:04 +0400 Subject: [PATCH 041/109] constraint_{min,max}imum: field -> field_as_value --- Development/nmos/constraints.cpp | 18 ++++++++++++------ Development/nmos/json_fields.h | 4 ++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index 8724526be..7f5a87d89 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -33,28 +33,34 @@ namespace nmos } if (constraint.has_field(nmos::fields::constraint_minimum) && subconstraint.has_field(nmos::fields::constraint_minimum)) { - if (constraint.at(U("minimum")).has_field(nmos::fields::numerator)) + const auto& constraint_min = nmos::fields::constraint_minimum(constraint); + const auto& subconstraint_min = nmos::fields::constraint_minimum(subconstraint); + + if (constraint_min.has_field(nmos::fields::numerator)) { - if (nmos::parse_rational(constraint.at(U("minimum"))) > nmos::parse_rational(subconstraint.at(U("minimum")))) + if (nmos::parse_rational(constraint_min) > nmos::parse_rational(subconstraint_min)) { return false; } } - else if (nmos::fields::constraint_minimum(constraint) > nmos::fields::constraint_minimum(subconstraint)) + else if (constraint_min.as_double() > subconstraint_min.as_double()) { return false; } } if (constraint.has_field(nmos::fields::constraint_maximum) && subconstraint.has_field(nmos::fields::constraint_maximum)) { - if (constraint.at(U("maximum")).has_field(nmos::fields::numerator)) + const auto& constraint_max = nmos::fields::constraint_maximum(constraint); + const auto& subconstraint_max = nmos::fields::constraint_maximum(subconstraint); + + if (constraint_max.has_field(nmos::fields::numerator)) { - if (nmos::parse_rational(constraint.at(U("maximum"))) < nmos::parse_rational(subconstraint.at(U("maximum")))) + if (nmos::parse_rational(constraint_max) < nmos::parse_rational(subconstraint_max)) { return false; } } - else if (nmos::fields::constraint_maximum(constraint) < nmos::fields::constraint_maximum(subconstraint)) + else if (constraint_max.as_double() < subconstraint_max.as_double()) { return false; } diff --git a/Development/nmos/json_fields.h b/Development/nmos/json_fields.h index 0e02a2ebd..f6b6a0fb7 100644 --- a/Development/nmos/json_fields.h +++ b/Development/nmos/json_fields.h @@ -124,8 +124,8 @@ namespace nmos // for connection_api const web::json::field_as_value endpoint_constraints{ U("constraints") }; // array - const web::json::field constraint_maximum{ U("maximum") }; // integer, number - const web::json::field constraint_minimum{ U("minimum") }; // integer, number + const web::json::field_as_value constraint_maximum{ U("maximum") }; // integer, number or object + const web::json::field_as_value constraint_minimum{ U("minimum") }; // integer, number or object const web::json::field_as_value constraint_enum{ U("enum") }; // array const web::json::field_as_string constraint_pattern{ U("pattern") }; // regex const web::json::field_as_string_or constraint_description{ U("description"), {} }; // string From e0756a240e57d1b37bb68ec4d4ac9e14be4296e6 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Wed, 30 Nov 2022 06:03:19 +0400 Subject: [PATCH 042/109] Rename validate_sender to validate_sender_resources --- Development/nmos-cpp-node/node_implementation.cpp | 2 +- Development/nmos/node_server.cpp | 4 ++-- Development/nmos/node_server.h | 4 ++-- Development/nmos/streamcompatibility_behaviour.cpp | 6 +++--- Development/nmos/streamcompatibility_behaviour.h | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 5eeb1ad1c..6ac1c5f30 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1574,6 +1574,6 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode .on_base_edid_deleted(make_node_implementation_streamcompatibility_base_edid_delete_handler(gate)) .on_set_effective_edid(make_node_implementation_streamcompatibility_effective_edid_setter(model.streamcompatibility_resources, gate)) .on_active_constraints_changed(make_node_implementation_streamcompatibility_active_constraints_handler(gate)) - .on_validate_sender_against_active_constraints(make_node_implementation_streamcompatibility_sender_validator()) + .on_validate_sender_resources_against_active_constraints(make_node_implementation_streamcompatibility_sender_validator()) .on_validate_receiver_against_transport_file(make_node_implementation_streamcompatibility_receiver_validator()); } diff --git a/Development/nmos/node_server.cpp b/Development/nmos/node_server.cpp index 9267b5706..fea2ca501 100644 --- a/Development/nmos/node_server.cpp +++ b/Development/nmos/node_server.cpp @@ -106,7 +106,7 @@ namespace nmos auto set_transportfile = node_implementation.set_transportfile; auto connection_activated = node_implementation.connection_activated; auto channelmapping_activated = node_implementation.channelmapping_activated; - auto validate_sender = node_implementation.validate_sender; + auto validate_sender_resources = node_implementation.validate_sender_resources; auto validate_receiver = node_implementation.validate_receiver; node_server.thread_functions.assign({ [&, load_ca_certificates, registration_changed] { nmos::node_behaviour_thread(node_model, load_ca_certificates, registration_changed, gate); }, @@ -114,7 +114,7 @@ namespace nmos [&] { nmos::erase_expired_events_resources_thread(node_model, gate); }, [&, resolve_auto, set_transportfile, connection_activated] { nmos::connection_activation_thread(node_model, resolve_auto, set_transportfile, connection_activated, gate); }, [&, channelmapping_activated] { nmos::channelmapping_activation_thread(node_model, channelmapping_activated, gate); }, - [&, validate_sender, validate_receiver] { nmos::experimental::streamcompatibility_behaviour_thread(node_model, validate_sender, validate_receiver, gate); } + [&, validate_sender_resources, validate_receiver] { nmos::experimental::streamcompatibility_behaviour_thread(node_model, validate_sender_resources, validate_receiver, gate); } }); auto system_changed = node_implementation.system_changed; diff --git a/Development/nmos/node_server.h b/Development/nmos/node_server.h index 5a5db817e..0937d770c 100644 --- a/Development/nmos/node_server.h +++ b/Development/nmos/node_server.h @@ -63,7 +63,7 @@ namespace nmos node_implementation& on_base_edid_deleted(nmos::experimental::details::streamcompatibility_base_edid_delete_handler base_edid_deleted) { this->base_edid_deleted = std::move(base_edid_deleted); return *this; } node_implementation& on_set_effective_edid(nmos::experimental::details::streamcompatibility_effective_edid_setter set_effective_edid) { this->set_effective_edid = std::move(set_effective_edid); return *this; } node_implementation& on_active_constraints_changed(nmos::experimental::details::streamcompatibility_active_constraints_put_handler active_constraints_changed) { this->active_constraints_changed = std::move(active_constraints_changed); return *this; } - node_implementation& on_validate_sender_against_active_constraints(nmos::experimental::details::streamcompatibility_sender_validator validate_sender) { this->validate_sender = std::move(validate_sender); return *this; } + node_implementation& on_validate_sender_resources_against_active_constraints(nmos::experimental::details::streamcompatibility_sender_validator validate_sender_resources) { this->validate_sender_resources = std::move(validate_sender_resources); return *this; } node_implementation& on_validate_receiver_against_transport_file(nmos::experimental::details::streamcompatibility_receiver_validator validate_receiver) { this->validate_receiver = std::move(validate_receiver); return *this; } // deprecated, use on_validate_connection_resource_patch @@ -99,7 +99,7 @@ namespace nmos nmos::experimental::details::streamcompatibility_base_edid_delete_handler base_edid_deleted; nmos::experimental::details::streamcompatibility_effective_edid_setter set_effective_edid; nmos::experimental::details::streamcompatibility_active_constraints_put_handler active_constraints_changed; - nmos::experimental::details::streamcompatibility_sender_validator validate_sender; + nmos::experimental::details::streamcompatibility_sender_validator validate_sender_resources; nmos::experimental::details::streamcompatibility_receiver_validator validate_receiver; }; diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index d9d2c8ebb..f23070bfa 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -69,7 +69,7 @@ namespace nmos return { receiver_state, receiver_state_debug }; } - void streamcompatibility_behaviour_thread(nmos::node_model& model, details::streamcompatibility_sender_validator validate_sender, details::streamcompatibility_receiver_validator validate_receiver, slog::base_gate& gate) + void streamcompatibility_behaviour_thread(nmos::node_model& model, details::streamcompatibility_sender_validator validate_sender_resources, details::streamcompatibility_receiver_validator validate_receiver, slog::base_gate& gate) { using web::json::value; using web::json::value_of; @@ -132,9 +132,9 @@ namespace nmos auto& transport_file = nmos::fields::endpoint_transportfile(connection_sender->data); slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " is being validated with its Flow, Source and transport file"; - if (validate_sender) + if (validate_sender_resources) { - std::tie(sender_state, sender_state_debug) = validate_sender(transport_file, *sender, *flow, *source, constraint_sets); + std::tie(sender_state, sender_state_debug) = validate_sender_resources(transport_file, *sender, *flow, *source, constraint_sets); } } diff --git a/Development/nmos/streamcompatibility_behaviour.h b/Development/nmos/streamcompatibility_behaviour.h index 868f7b8db..0d70efec0 100644 --- a/Development/nmos/streamcompatibility_behaviour.h +++ b/Development/nmos/streamcompatibility_behaviour.h @@ -32,7 +32,7 @@ namespace nmos } std::pair validate_receiver_resources(const web::json::value& transport_file, const web::json::value& receiver); - void streamcompatibility_behaviour_thread(nmos::node_model& model, details::streamcompatibility_sender_validator validate_sender, details::streamcompatibility_receiver_validator validate_receiver, slog::base_gate& gate); + void streamcompatibility_behaviour_thread(nmos::node_model& model, details::streamcompatibility_sender_validator validate_sender_resources, details::streamcompatibility_receiver_validator validate_receiver, slog::base_gate& gate); } } From 7d799c592a3ecb7e884422e0152a71b28d4bd204 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Wed, 30 Nov 2022 06:03:47 +0400 Subject: [PATCH 043/109] Add a Stream Compatibility validation test --- Development/cmake/NmosCppTest.cmake | 1 + .../streamcompatibility_validation_test.cpp | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 Development/nmos/test/streamcompatibility_validation_test.cpp diff --git a/Development/cmake/NmosCppTest.cmake b/Development/cmake/NmosCppTest.cmake index 93a48579b..4905452ff 100644 --- a/Development/cmake/NmosCppTest.cmake +++ b/Development/cmake/NmosCppTest.cmake @@ -48,6 +48,7 @@ set(NMOS_CPP_TEST_NMOS_TEST_SOURCES nmos/test/paging_utils_test.cpp nmos/test/query_api_test.cpp nmos/test/sdp_utils_test.cpp + nmos/test/streamcompatibility_validation_test.cpp nmos/test/system_resources_test.cpp nmos/test/video_jxsv_test.cpp ) diff --git a/Development/nmos/test/streamcompatibility_validation_test.cpp b/Development/nmos/test/streamcompatibility_validation_test.cpp new file mode 100644 index 000000000..047bd344f --- /dev/null +++ b/Development/nmos/test/streamcompatibility_validation_test.cpp @@ -0,0 +1,35 @@ +#include "nmos/st2110_21_sender_type.h" +#include "nmos/streamcompatibility_validation.h" + +#include "bst/test/test.h" + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testResourceValidator) +{ + { + using web::json::value_of; + using nmos::experimental::flow_parameter_constraints; + using nmos::experimental::sender_parameter_constraints; + using nmos::experimental::match_resource_parameters_constraint_set; + + auto constraint_set = value_of({ + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, + { nmos::caps::transport::st2110_21_sender_type, nmos::make_caps_string_constraint({ sdp::type_parameters::type_N.name, sdp::type_parameters::type_NL.name }) } + }); + + auto flow = value_of({ + { nmos::fields::media_type, nmos::media_types::video_raw.name }, + { nmos::fields::frame_width, 1920 }, + { nmos::fields::frame_height, 1080 } + }); + + auto sender = value_of({ + { nmos::fields::st2110_21_sender_type, nmos::st2110_21_sender_types::type_N.name } + }); + + BST_REQUIRE(match_resource_parameters_constraint_set(flow_parameter_constraints, flow, constraint_set)); + BST_REQUIRE(match_resource_parameters_constraint_set(sender_parameter_constraints, sender, constraint_set)); + } +} From 0444c02b72e05ffb56a5d297d93950eac90edd8f Mon Sep 17 00:00:00 2001 From: Gareth Sylvester-Bradley Date: Fri, 2 Dec 2022 12:09:10 +0000 Subject: [PATCH 044/109] Compare integer min/max as int64_t --- Development/nmos/constraints.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index 7f5a87d89..c76ef983c 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -36,13 +36,20 @@ namespace nmos const auto& constraint_min = nmos::fields::constraint_minimum(constraint); const auto& subconstraint_min = nmos::fields::constraint_minimum(subconstraint); - if (constraint_min.has_field(nmos::fields::numerator)) + if (nmos::is_rational(constraint_min)) { if (nmos::parse_rational(constraint_min) > nmos::parse_rational(subconstraint_min)) { return false; } } + else if (constraint_min.is_integer() && subconstraint_min.is_integer()) + { + if (constraint_min.as_number().to_int64() > subconstraint_min.as_number().to_int64()) + { + return false; + } + } else if (constraint_min.as_double() > subconstraint_min.as_double()) { return false; @@ -53,13 +60,20 @@ namespace nmos const auto& constraint_max = nmos::fields::constraint_maximum(constraint); const auto& subconstraint_max = nmos::fields::constraint_maximum(subconstraint); - if (constraint_max.has_field(nmos::fields::numerator)) + if (nmos::is_rational(constraint_max)) { if (nmos::parse_rational(constraint_max) < nmos::parse_rational(subconstraint_max)) { return false; } } + else if (constraint_max.is_integer() && subconstraint_max.is_integer()) + { + if (constraint_max.as_number().to_int64() < subconstraint_max.as_number().to_int64()) + { + return false; + } + } else if (constraint_max.as_double() < subconstraint_max.as_double()) { return false; From 402fa7d6af6a2f506ad657b40346741ca82c632e Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sun, 4 Dec 2022 08:25:17 +0400 Subject: [PATCH 045/109] Revert changes in sdp_utils.{cpp,h} --- Development/nmos/sdp_utils.cpp | 36 +++++++++++++ Development/nmos/sdp_utils.h | 51 +------------------ .../nmos/streamcompatibility_validation.h | 1 + Development/nmos/test/constraints_test.cpp | 2 + 4 files changed, 40 insertions(+), 50 deletions(-) diff --git a/Development/nmos/sdp_utils.cpp b/Development/nmos/sdp_utils.cpp index 5c4411360..2bc863982 100644 --- a/Development/nmos/sdp_utils.cpp +++ b/Development/nmos/sdp_utils.cpp @@ -1530,6 +1530,42 @@ namespace nmos else return{}; } + // NMOS Parameter Registers - Capabilities register + // See https://specs.amwa.tv/nmos-parameter-registers/branches/main/capabilities/ +#define CAPS_ARGS const sdp_parameters& sdp, const format_parameters& format, const web::json::value& con + static const std::map> format_constraints + { + // General Constraints + + { nmos::caps::format::media_type, [](CAPS_ARGS) { return nmos::match_string_constraint(get_media_type(sdp).name, con); } }, + // hm, how best to match (rational) nmos::caps::format::grain_rate against (double) framerate e.g. for video/SMPTE2022-6? + // is 23.976 a match for 24000/1001? how about 23.98, or 23.9? or even 23?! + { nmos::caps::format::grain_rate, [](CAPS_ARGS) { auto exactframerate = get_exactframerate(&format); return nmos::rational{} == exactframerate || nmos::match_rational_constraint(exactframerate, con); } }, + + // Video Constraints + + { nmos::caps::format::frame_height, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_integer_constraint(video->height, con); } }, + { nmos::caps::format::frame_width, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_integer_constraint(video->width, con); } }, + { nmos::caps::format::color_sampling, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_string_constraint(video->sampling.name, con); } }, + { nmos::caps::format::interlace_mode, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::details::match_interlace_mode_constraint(video->interlace, video->segmented, con); } }, + { nmos::caps::format::colorspace, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_string_constraint(video->colorimetry.name, con); } }, + { nmos::caps::format::transfer_characteristic, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_string_constraint(!video->tcs.empty() ? video->tcs.name : sdp::transfer_characteristic_systems::SDR.name, con); } }, + { nmos::caps::format::component_depth, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_integer_constraint(video->depth, con); } }, + + // Audio Constraints + + { nmos::caps::format::channel_count, [](CAPS_ARGS) { auto audio = get_audio(&format); return audio && nmos::match_integer_constraint(audio->channel_count, con); } }, + { nmos::caps::format::sample_rate, [](CAPS_ARGS) { auto audio = get_audio(&format); return audio && nmos::match_rational_constraint(audio->sample_rate, con); } }, + { nmos::caps::format::sample_depth, [](CAPS_ARGS) { auto audio = get_audio(&format); return audio && nmos::match_integer_constraint(audio->bit_depth, con); } }, + + // Transport Constraints + + { nmos::caps::transport::packet_time, [](CAPS_ARGS) { return 0 == sdp.packet_time || nmos::match_number_constraint(sdp.packet_time, con); } }, + { nmos::caps::transport::max_packet_time, [](CAPS_ARGS) { return 0 == sdp.max_packet_time || nmos::match_number_constraint(sdp.max_packet_time, con); } }, + { nmos::caps::transport::st2110_21_sender_type, [](CAPS_ARGS) { if (auto video = get_video(&format)) return nmos::match_string_constraint(video->tp.name, con); else if (auto mux = get_mux(&format)) return nmos::match_string_constraint(mux->tp.name, con); else return false; } } + }; +#undef CAPS_ARGS + // Check the specified SDP parameters and format-specific parameters against the specified constraint set // using the specified parameter constraint functions bool match_sdp_parameters_constraint_set(const sdp_parameter_constraints& constraints, const sdp_parameters& sdp_params, const format_parameters& format_params, const web::json::value& constraint_set_) diff --git a/Development/nmos/sdp_utils.h b/Development/nmos/sdp_utils.h index 9e192111b..c0202becc 100644 --- a/Development/nmos/sdp_utils.h +++ b/Development/nmos/sdp_utils.h @@ -9,16 +9,14 @@ #include "bst/optional.h" #include "sdp/json.h" #include "sdp/ntp.h" -#include "nmos/capabilities.h" #include "nmos/did_sdid.h" -#include "nmos/interlace_mode.h" -#include "nmos/media_type.h" #include "nmos/rational.h" #include "nmos/vpid_code.h" namespace nmos { struct format; + struct media_type; struct sdp_parameters; // defined below @@ -571,8 +569,6 @@ namespace nmos : sdp_parameters(make_sdp_parameters(session_name, mux, payload_type, media_stream_ids, ts_refclk)) {} - media_type get_media_type(const sdp_parameters& sdp_params); - // Helper functions for implementing format-specific functions namespace details { @@ -605,21 +601,12 @@ namespace nmos // e.g. can hold a video_raw_parameters, an audio_L_parameters, etc. typedef bst::any format_parameters; - format_parameters get_format_parameters(const sdp_parameters& sdp_params); - template inline const FormatParameters* get(const format_parameters* any) { return bst::any_cast(any); } - // for a little brevity, cf. sdp_parameters member type names - const video_raw_parameters* get_video(const format_parameters* format); - const audio_L_parameters* get_audio(const format_parameters* format); - const video_smpte291_parameters* get_data(const format_parameters* format); - const video_SMPTE2022_6_parameters* get_mux(const format_parameters* format); - nmos::rational get_exactframerate(const format_parameters* format); - // a function to check the specified SDP parameters and format-specific parameters // against the specified parameter constraint value, see nmos/capabilities.h typedef std::function sdp_parameter_constraint; @@ -673,42 +660,6 @@ namespace nmos // "Alternatively, payload types may be set by other means in accordance with RFC 3550." // See SMPTE ST 2022-6:2012 Section 6.3 RTP/UDP/IP Header const uint64_t payload_type_mux_default = 98; - - // NMOS Parameter Registers - Capabilities register - // See https://specs.amwa.tv/nmos-parameter-registers/branches/main/capabilities/ -#define CAPS_ARGS const sdp_parameters& sdp, const format_parameters& format, const web::json::value& con - const std::map> format_constraints - { - // General Constraints - - { nmos::caps::format::media_type, [](CAPS_ARGS) { return nmos::match_string_constraint(get_media_type(sdp).name, con); } }, - // hm, how best to match (rational) nmos::caps::format::grain_rate against (double) framerate e.g. for video/SMPTE2022-6? - // is 23.976 a match for 24000/1001? how about 23.98, or 23.9? or even 23?! - { nmos::caps::format::grain_rate, [](CAPS_ARGS) { auto exactframerate = get_exactframerate(&format); return nmos::rational{} == exactframerate || nmos::match_rational_constraint(exactframerate, con); } }, - - // Video Constraints - - { nmos::caps::format::frame_height, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_integer_constraint(video->height, con); } }, - { nmos::caps::format::frame_width, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_integer_constraint(video->width, con); } }, - { nmos::caps::format::color_sampling, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_string_constraint(video->sampling.name, con); } }, - { nmos::caps::format::interlace_mode, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::details::match_interlace_mode_constraint(video->interlace, video->segmented, con); } }, - { nmos::caps::format::colorspace, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_string_constraint(video->colorimetry.name, con); } }, - { nmos::caps::format::transfer_characteristic, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_string_constraint(!video->tcs.empty() ? video->tcs.name : sdp::transfer_characteristic_systems::SDR.name, con); } }, - { nmos::caps::format::component_depth, [](CAPS_ARGS) { auto video = get_video(&format); return video && nmos::match_integer_constraint(video->depth, con); } }, - - // Audio Constraints - - { nmos::caps::format::channel_count, [](CAPS_ARGS) { auto audio = get_audio(&format); return audio && nmos::match_integer_constraint(audio->channel_count, con); } }, - { nmos::caps::format::sample_rate, [](CAPS_ARGS) { auto audio = get_audio(&format); return audio && nmos::match_rational_constraint(audio->sample_rate, con); } }, - { nmos::caps::format::sample_depth, [](CAPS_ARGS) { auto audio = get_audio(&format); return audio && nmos::match_integer_constraint(audio->bit_depth, con); } }, - - // Transport Constraints - - { nmos::caps::transport::packet_time, [](CAPS_ARGS) { return 0 == sdp.packet_time || nmos::match_number_constraint(sdp.packet_time, con); } }, - { nmos::caps::transport::max_packet_time, [](CAPS_ARGS) { return 0 == sdp.max_packet_time || nmos::match_number_constraint(sdp.max_packet_time, con); } }, - { nmos::caps::transport::st2110_21_sender_type, [](CAPS_ARGS) { if (auto video = get_video(&format)) return nmos::match_string_constraint(video->tp.name, con); else if (auto mux = get_mux(&format)) return nmos::match_string_constraint(mux->tp.name, con); else return false; } } - }; -#undef CAPS_ARGS } } diff --git a/Development/nmos/streamcompatibility_validation.h b/Development/nmos/streamcompatibility_validation.h index 1356bca91..846d36a3b 100644 --- a/Development/nmos/streamcompatibility_validation.h +++ b/Development/nmos/streamcompatibility_validation.h @@ -5,6 +5,7 @@ #include #include "nmos/capabilities.h" #include "nmos/connection_api.h" +#include "nmos/media_type.h" #include "nmos/resource.h" #include "nmos/sdp_utils.h" #include "nmos/streamcompatibility_state.h" diff --git a/Development/nmos/test/constraints_test.cpp b/Development/nmos/test/constraints_test.cpp index 6387cb7f7..b9a215139 100644 --- a/Development/nmos/test/constraints_test.cpp +++ b/Development/nmos/test/constraints_test.cpp @@ -1,6 +1,8 @@ #include "nmos/capabilities.h" #include "nmos/constraints.h" +#include "nmos/interlace_mode.h" #include "nmos/json_fields.h" +#include "nmos/media_type.h" #include "nmos/sdp_utils.h" #include "bst/test/test.h" From f5216f3d36ddc4f38903d6b185802a45fe5855d7 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sun, 4 Dec 2022 08:29:36 +0400 Subject: [PATCH 046/109] Add make_streamcompatibility_sdp_constraint_sets_matcher --- .../nmos-cpp-node/node_implementation.cpp | 24 +++++++++++- Development/nmos/sdp_utils.cpp | 9 +++++ Development/nmos/sdp_utils.h | 4 ++ .../nmos/streamcompatibility_validation.cpp | 37 +++++++++---------- .../nmos/streamcompatibility_validation.h | 10 ++--- Development/nmos/video_jxsv.cpp | 9 +++++ Development/nmos/video_jxsv.h | 31 ++++++++++++++++ 7 files changed, 98 insertions(+), 26 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 376371350..bdeef52c0 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1474,7 +1474,29 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler nmos::experimental::details::streamcompatibility_sender_validator make_node_implementation_streamcompatibility_sender_validator() { - return nmos::experimental::make_streamcompatibility_sender_resources_validator(); + using nmos::experimental::make_streamcompatibility_sender_resources_validator; + using nmos::experimental::make_streamcompatibility_resource_constraint_set_matcher; + using nmos::experimental::make_streamcompatibility_sdp_constraint_sets_matcher; + using nmos::experimental::match_resource_parameters_constraint_set; + + return [](const web::json::value& transport_file, const nmos::resource& sender, const nmos::resource& flow, const nmos::resource& source, const web::json::array& constraint_sets) -> std::pair + { + const auto video_jxsv_sender_resources_matcher = [](const nmos::resource& resource, const web::json::value& constraint_set) -> bool + { + return match_resource_parameters_constraint_set(nmos::video_jxsv_parameter_constraints, resource.data, constraint_set); + }; + const auto validate_video_jxsv_sender_resources = make_streamcompatibility_sender_resources_validator(video_jxsv_sender_resources_matcher, make_streamcompatibility_sdp_constraint_sets_matcher(&nmos::match_video_jxsv_sdp_parameters_constraint_sets)); + const auto validate_sender_resources = make_streamcompatibility_sender_resources_validator(make_streamcompatibility_resource_constraint_set_matcher(), make_streamcompatibility_sdp_constraint_sets_matcher(&nmos::match_sdp_parameters_constraint_sets)); + if (nmos::media_types::video_jxsv.name == nmos::fields::media_type(flow.data)) + { + return validate_video_jxsv_sender_resources(transport_file, sender, flow, source, constraint_sets); + } + else + { + // validate core media types, i.e., "video/raw", "audio/L", "video/smpte291" and "video/SMPTE2022-6" + return validate_sender_resources(transport_file, sender, flow, source, constraint_sets); + } + }; } nmos::experimental::details::streamcompatibility_receiver_validator make_node_implementation_streamcompatibility_receiver_validator() diff --git a/Development/nmos/sdp_utils.cpp b/Development/nmos/sdp_utils.cpp index 2bc863982..07bae2012 100644 --- a/Development/nmos/sdp_utils.cpp +++ b/Development/nmos/sdp_utils.cpp @@ -1613,4 +1613,13 @@ namespace nmos { details::validate_sdp_parameters(details::format_constraints, sdp_params, details::get_format(sdp_params), details::get_format_parameters(sdp_params), receiver); } + + // Check the specified SDP parameters against the specified constraint sets + // for "video/raw", "audio/L", "video/smpte291" or "video/SMPTE2022-6" + bool match_sdp_parameters_constraint_sets(const web::json::array& constraint_sets, const sdp_parameters& sdp_params) + { + const auto format_params = nmos::details::get_format_parameters(sdp_params); + const auto found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return details::match_sdp_parameters_constraint_set(details::format_constraints, sdp_params, format_params, constraint_set); }); + return constraint_sets.end() != found; + } } diff --git a/Development/nmos/sdp_utils.h b/Development/nmos/sdp_utils.h index c0202becc..866bb1c20 100644 --- a/Development/nmos/sdp_utils.h +++ b/Development/nmos/sdp_utils.h @@ -62,6 +62,10 @@ namespace nmos // Validate the SDP parameters against a receiver for "video/raw", "audio/L", "video/smpte291" or "video/SMPTE2022-6" void validate_sdp_parameters(const web::json::value& receiver, const sdp_parameters& sdp_params); + // Check the specified SDP parameters against the specified constraint sets + // for "video/raw", "audio/L", "video/smpte291" or "video/SMPTE2022-6" + bool match_sdp_parameters_constraint_sets(const web::json::array& constraint_sets, const sdp_parameters& sdp_params); + // Format-specific types struct video_raw_parameters; diff --git a/Development/nmos/streamcompatibility_validation.cpp b/Development/nmos/streamcompatibility_validation.cpp index 4414f63a1..dcee26d21 100644 --- a/Development/nmos/streamcompatibility_validation.cpp +++ b/Development/nmos/streamcompatibility_validation.cpp @@ -51,9 +51,25 @@ namespace nmos }; } - details::streamcompatibility_sender_validator make_streamcompatibility_sender_resources_validator() + details::transport_file_constraint_sets_matcher make_streamcompatibility_sdp_constraint_sets_matcher(const details::sdp_constraint_sets_matcher& match_sdp_parameters_constraint_sets) { - const details::resource_constraints_matcher match_resource_constraint_set = [](const nmos::resource& resource, const web::json::value& constraint_set) -> bool + return [match_sdp_parameters_constraint_sets](const std::pair& transport_file, const web::json::array& constraint_sets) -> bool + { + if (nmos::media_types::application_sdp.name != transport_file.first) + { + throw std::runtime_error("unknown transport file type"); + } + const auto session_description = sdp::parse_session_description(utility::us2s(transport_file.second)); + auto sdp_params = nmos::parse_session_description(session_description).first; + + return match_sdp_parameters_constraint_sets(constraint_sets, sdp_params); + }; + + } + + details::resource_constraints_matcher make_streamcompatibility_resource_constraint_set_matcher() + { + return [](const nmos::resource& resource, const web::json::value& constraint_set) -> bool { const std::map resource_parameter_constraints { @@ -69,23 +85,6 @@ namespace nmos return match_resource_parameters_constraint_set(resource_parameter_constraints.at(resource.type), resource.data, constraint_set); }; - - const details::transport_file_constraint_sets_matcher match_transport_file_constraint_sets = [](const std::pair& transport_file, const web::json::array& constraint_sets) -> bool - { - if (nmos::media_types::application_sdp.name != transport_file.first) - { - throw std::runtime_error("unknown transport file type"); - } - const auto session_description = sdp::parse_session_description(utility::us2s(transport_file.second)); - auto sdp_params = nmos::parse_session_description(session_description).first; - - const auto format_params = nmos::details::get_format_parameters(sdp_params); - const auto sdp_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return nmos::details::match_sdp_parameters_constraint_set(nmos::details::format_constraints, sdp_params, format_params, constraint_set); }); - - return constraint_sets.end() != sdp_found; - }; - - return make_streamcompatibility_sender_resources_validator(match_resource_constraint_set, match_transport_file_constraint_sets); } details::streamcompatibility_sender_validator make_streamcompatibility_sender_resources_validator(const details::resource_constraints_matcher& match_resource_constraint_set, const details::transport_file_constraint_sets_matcher& match_transport_file_constraint_sets) diff --git a/Development/nmos/streamcompatibility_validation.h b/Development/nmos/streamcompatibility_validation.h index 846d36a3b..55d80fb43 100644 --- a/Development/nmos/streamcompatibility_validation.h +++ b/Development/nmos/streamcompatibility_validation.h @@ -9,7 +9,6 @@ #include "nmos/resource.h" #include "nmos/sdp_utils.h" #include "nmos/streamcompatibility_state.h" -#include "nmos/video_jxsv.h" namespace web { @@ -31,6 +30,7 @@ namespace nmos typedef std::function(const web::json::value& transport_file, const nmos::resource& sender, const nmos::resource& flow, const nmos::resource& source, const web::json::array& constraint_sets)> streamcompatibility_sender_validator; typedef std::function resource_constraints_matcher; typedef std::function& transport_file, const web::json::array& constraint_sets)> transport_file_constraint_sets_matcher; + typedef std::function sdp_constraint_sets_matcher; } typedef std::map> parameter_constraints; @@ -69,9 +69,7 @@ namespace nmos const std::map> sender_parameter_constraints { - { nmos::caps::transport::packet_transmission_mode, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::packet_transmission_mode(sender), con); } }, - { nmos::caps::transport::st2110_21_sender_type, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::st2110_21_sender_type(sender), con); } }, - { nmos::caps::transport::bit_rate, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_rate(sender), con); } } + { nmos::caps::transport::st2110_21_sender_type, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::st2110_21_sender_type(sender), con); } } }; bool match_resource_parameters_constraint_set(const parameter_constraints& constraints, const web::json::value& resource, const web::json::value& constraint_set); @@ -84,8 +82,8 @@ namespace nmos nmos::details::connection_resource_patch_validator make_connection_streamcompatibility_validator(nmos::node_model& model); details::streamcompatibility_sender_validator make_streamcompatibility_sender_resources_validator(const details::resource_constraints_matcher& resource_matcher, const details::transport_file_constraint_sets_matcher& transport_file_matcher); - - details::streamcompatibility_sender_validator make_streamcompatibility_sender_resources_validator(); + details::resource_constraints_matcher make_streamcompatibility_resource_constraint_set_matcher(); + details::transport_file_constraint_sets_matcher make_streamcompatibility_sdp_constraint_sets_matcher(const details::sdp_constraint_sets_matcher& match_sdp_parameters_constraint_sets); } } diff --git a/Development/nmos/video_jxsv.cpp b/Development/nmos/video_jxsv.cpp index 05440fecc..516821554 100644 --- a/Development/nmos/video_jxsv.cpp +++ b/Development/nmos/video_jxsv.cpp @@ -334,6 +334,15 @@ namespace nmos nmos::details::validate_sdp_parameters(details::jxsv_constraints, sdp_params, nmos::formats::video, get_video_jxsv_parameters(sdp_params), receiver); } + // Check the specified SDP parameters against the specified constraint sets + // for "video/jxsv" + bool match_video_jxsv_sdp_parameters_constraint_sets(const web::json::array& constraint_sets, const sdp_parameters& sdp_params) + { + const auto format_params = get_video_jxsv_parameters(sdp_params); + const auto found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return details::match_sdp_parameters_constraint_set(details::jxsv_constraints, sdp_params, format_params, constraint_set); }); + return constraint_sets.end() != found; + } + // See https://specs.amwa.tv/bcp-006-01/branches/v1.0-dev/docs/NMOS_With_JPEG_XS.html#flows // cf. nmos::make_coded_video_flow nmos::resource make_video_jxsv_flow( diff --git a/Development/nmos/video_jxsv.h b/Development/nmos/video_jxsv.h index c954267d0..3400cf60d 100644 --- a/Development/nmos/video_jxsv.h +++ b/Development/nmos/video_jxsv.h @@ -1,6 +1,8 @@ #ifndef NMOS_VIDEO_JXSV_H #define NMOS_VIDEO_JXSV_H +#include "nmos/capabilities.h" +#include "nmos/json_fields.h" #include "nmos/media_type.h" #include "nmos/node_resources.h" #include "nmos/sdp_utils.h" @@ -363,6 +365,10 @@ namespace nmos // Validate SDP parameters for "video/jxsv" against IS-04 receiver capabilities void validate_video_jxsv_sdp_parameters(const web::json::value& receiver, const nmos::sdp_parameters& sdp_params); + // Check the specified SDP parameters against the specified constraint sets + // for "video/jxsv" + bool match_video_jxsv_sdp_parameters_constraint_sets(const web::json::array& constraint_sets, const sdp_parameters& sdp_params); + // Calculate the format bit rate (kilobits/second) from the specified frame rate, dimensions and bits per pixel uint64_t get_video_jxsv_bit_rate(const nmos::rational& grain_rate, uint32_t frame_width, uint32_t frame_height, double bits_per_pixel); @@ -385,6 +391,31 @@ namespace nmos const nmos::sublevel& sublevel, double bits_per_pixel, const nmos::settings& settings); + + const std::map> video_jxsv_parameter_constraints + { + // Flow Constraints + + { nmos::caps::format::media_type, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::media_type(flow), con); } }, + { nmos::caps::format::grain_rate, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_rational_constraint(nmos::parse_rational(nmos::fields::grain_rate(flow)), con); } }, + { nmos::caps::format::profile, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::profile(flow), con); } }, + { nmos::caps::format::level, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::level(flow), con); } }, + { nmos::caps::format::sublevel, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::sublevel(flow), con); } }, + { nmos::caps::format::frame_height, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::frame_height(flow), con); } }, + { nmos::caps::format::frame_width, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::frame_width(flow), con); } }, + { nmos::caps::format::color_sampling, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::details::make_sampling(nmos::fields::components(flow)).name, con); } }, + { nmos::caps::format::interlace_mode, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::interlace_mode(flow), con); } }, + { nmos::caps::format::colorspace, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::colorspace(flow), con); } }, + { nmos::caps::format::transfer_characteristic, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::transfer_characteristic(flow), con); } }, + { nmos::caps::format::component_depth, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_depth(nmos::fields::components(flow).at(0)), con); } }, + { nmos::caps::format::bit_rate, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_rate(flow), con); } }, + + // Sender Constraints + + { nmos::caps::transport::packet_transmission_mode, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::packet_transmission_mode(sender), con); } }, + { nmos::caps::transport::st2110_21_sender_type, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::st2110_21_sender_type(sender), con); } }, + { nmos::caps::transport::bit_rate, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_rate(sender), con); } } + }; } #endif From cd890ad9d5eb8d3b8df91a3d0ad2d3d5e6ba109e Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Tue, 6 Dec 2022 05:58:19 +0400 Subject: [PATCH 047/109] Fix /{senders,receivers}/{resourceId}/{inputs,outputs} --- Development/nmos/api_utils.h | 1 + Development/nmos/streamcompatibility_api.cpp | 32 ++++++++------------ 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/Development/nmos/api_utils.h b/Development/nmos/api_utils.h index 29283c279..b6359800b 100644 --- a/Development/nmos/api_utils.h +++ b/Development/nmos/api_utils.h @@ -84,6 +84,7 @@ namespace nmos // Stream Compatibility Management API const route_pattern streamCompatibilityResourceType = make_route_pattern(U("resourceType"), U("senders|receivers|inputs|outputs")); + const route_pattern streamCompatibilityInputOutputType = make_route_pattern(U("inputOutputType"), U("inputs|outputs")); const route_pattern constraintsType = make_route_pattern(U("constraintsType"), U("active|supported")); const route_pattern edidType = make_route_pattern(U("edidType"), U("base|effective")); diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index a3ca0639b..9e64fb3f3 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -321,18 +321,24 @@ namespace nmos return pplx::task_from_result(true); }); - streamcompatibility_api.support(U("/") + nmos::patterns::connectorType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/") + nmos::patterns::inputOutputType.pattern + U("/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::connectorType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/") + nmos::patterns::streamCompatibilityInputOutputType.pattern + U("/?"), methods::GET, [&model](http_request req, http_response res, const string_t&, const route_parameters& parameters) { auto lock = model.read_lock(); auto& resources = model.streamcompatibility_resources; - const string_t resourceType = parameters.at(nmos::patterns::connectorType.name); + const auto resourceType = nmos::type_from_resourceType(parameters.at(nmos::patterns::connectorType.name)); const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); - const string_t associatedResourceType = parameters.at(nmos::patterns::inputOutputType.name); + const auto associatedResourceType = nmos::type_from_resourceType(parameters.at(nmos::patterns::streamCompatibilityInputOutputType.name)); - const std::pair id_type{ resourceId, nmos::type_from_resourceType(resourceType) }; + const bool consistentTypes + { + (nmos::types::sender == resourceType && nmos::types::input == associatedResourceType) || + (nmos::types::receiver == resourceType && nmos::types::output == associatedResourceType) + }; + + const std::pair id_type{ resourceId, resourceType }; auto resource = find_resource(resources, id_type); - if (resources.end() != resource) + if (resources.end() != resource && consistentTypes) { auto matching_resource = find_resource(model.node_resources, id_type); if (model.node_resources.end() == matching_resource) @@ -340,23 +346,11 @@ namespace nmos throw std::logic_error("matching IS-04 resource not found"); } - const auto match = [&](const nmos::resources::value_type& resource) - { - return resource.type == nmos::type_from_resourceType(resourceType) && nmos::fields::id(resource.data) == resourceId; - }; - - const auto filter = (nmos::types::input == nmos::type_from_resourceType(associatedResourceType)) ? + const auto filter = (nmos::types::input == associatedResourceType) ? nmos::fields::inputs : nmos::fields::outputs; - set_reply(res, status_codes::OK, - web::json::serialize_array(resources - | boost::adaptors::filtered(match) - | boost::adaptors::transformed( - [&filter](const nmos::resource& resource) { return value_from_elements(filter(resource.data)); } - ) - ), - web::http::details::mime_types::application_json); + set_reply(res, status_codes::OK, web::json::value_from_elements(filter(resource->data))); } else if (nmos::details::is_erased_resource(resources, id_type)) { From 6f46f5325b8c442830f4c958b76be51d69e63ce7 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Thu, 2 Feb 2023 17:03:19 +0400 Subject: [PATCH 048/109] Fix PUTting an empty array to Active Constraints --- Development/nmos-cpp-node/node_implementation.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index bdeef52c0..48d5e35ab 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1456,7 +1456,14 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler return [&gate, sender_capabilities](const nmos::id& sender_id, const web::json::value& active_constraints) -> bool { - for (const auto& constraint_set : nmos::fields::constraint_sets(active_constraints).as_array()) + using web::json::empty; + + const auto& constraint_sets = nmos::fields::constraint_sets(active_constraints).as_array(); + if (empty(constraint_sets)) + { + return true; + } + for (const auto& constraint_set : constraint_sets) { if (!nmos::caps::meta::enabled(constraint_set)) continue; for (const auto& sender_caps_constraint_set : sender_capabilities.as_array()) From 8fc7591a936d2f1bf8e65afcc6411e1da63e4216 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Thu, 2 Feb 2023 18:20:19 +0400 Subject: [PATCH 049/109] Add default streamcompatibility_sender_validator to node_implementation --- .../nmos-cpp-node/node_implementation.cpp | 10 ++--- Development/nmos/node_server.h | 2 + .../nmos/streamcompatibility_validation.cpp | 45 ++++++++++--------- .../nmos/streamcompatibility_validation.h | 7 ++- .../streamcompatibility_validation_test.cpp | 2 +- 5 files changed, 36 insertions(+), 30 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 48d5e35ab..68e09b270 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1482,18 +1482,18 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler nmos::experimental::details::streamcompatibility_sender_validator make_node_implementation_streamcompatibility_sender_validator() { using nmos::experimental::make_streamcompatibility_sender_resources_validator; - using nmos::experimental::make_streamcompatibility_resource_constraint_set_matcher; using nmos::experimental::make_streamcompatibility_sdp_constraint_sets_matcher; - using nmos::experimental::match_resource_parameters_constraint_set; + // this example uses a custom sender resources validator to handle video/jxsv in addition to the core media types + // (if this callback is specified, an 'empty' std::function is not allowed) return [](const web::json::value& transport_file, const nmos::resource& sender, const nmos::resource& flow, const nmos::resource& source, const web::json::array& constraint_sets) -> std::pair { const auto video_jxsv_sender_resources_matcher = [](const nmos::resource& resource, const web::json::value& constraint_set) -> bool { - return match_resource_parameters_constraint_set(nmos::video_jxsv_parameter_constraints, resource.data, constraint_set); + return nmos::experimental::detail::match_resource_parameters_constraint_set(nmos::video_jxsv_parameter_constraints, resource.data, constraint_set); }; const auto validate_video_jxsv_sender_resources = make_streamcompatibility_sender_resources_validator(video_jxsv_sender_resources_matcher, make_streamcompatibility_sdp_constraint_sets_matcher(&nmos::match_video_jxsv_sdp_parameters_constraint_sets)); - const auto validate_sender_resources = make_streamcompatibility_sender_resources_validator(make_streamcompatibility_resource_constraint_set_matcher(), make_streamcompatibility_sdp_constraint_sets_matcher(&nmos::match_sdp_parameters_constraint_sets)); + const auto validate_sender_resources = make_streamcompatibility_sender_resources_validator(&nmos::experimental::match_resource_parameters_constraint_set, make_streamcompatibility_sdp_constraint_sets_matcher(&nmos::match_sdp_parameters_constraint_sets)); if (nmos::media_types::video_jxsv.name == nmos::fields::media_type(flow.data)) { return validate_video_jxsv_sender_resources(transport_file, sender, flow, source, constraint_sets); @@ -1671,6 +1671,6 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode .on_base_edid_deleted(make_node_implementation_streamcompatibility_base_edid_delete_handler(gate)) .on_set_effective_edid(make_node_implementation_streamcompatibility_effective_edid_setter(model.streamcompatibility_resources, gate)) .on_active_constraints_changed(make_node_implementation_streamcompatibility_active_constraints_handler(gate)) - .on_validate_sender_resources_against_active_constraints(make_node_implementation_streamcompatibility_sender_validator()) + .on_validate_sender_resources_against_active_constraints(make_node_implementation_streamcompatibility_sender_validator()) // may be omitted if the default is sufficient .on_validate_receiver_against_transport_file(make_node_implementation_streamcompatibility_receiver_validator()); } diff --git a/Development/nmos/node_server.h b/Development/nmos/node_server.h index 0937d770c..1663f6ee5 100644 --- a/Development/nmos/node_server.h +++ b/Development/nmos/node_server.h @@ -8,6 +8,7 @@ #include "nmos/connection_activation.h" #include "nmos/streamcompatibility_api.h" #include "nmos/streamcompatibility_behaviour.h" +#include "nmos/streamcompatibility_validation.h" #include "nmos/node_behaviour.h" #include "nmos/node_system_behaviour.h" #include "nmos/ocsp_response_handler.h" @@ -44,6 +45,7 @@ namespace nmos // (by itself, the default constructor does not construct a valid instance) node_implementation() : parse_transport_file(&nmos::parse_rtp_transport_file) + , validate_sender_resources(make_streamcompatibility_sender_resources_validator(&nmos::experimental::match_resource_parameters_constraint_set, make_streamcompatibility_sdp_constraint_sets_matcher(&nmos::match_sdp_parameters_constraint_sets))) {} node_implementation& on_load_server_certificates(nmos::load_server_certificates_handler load_server_certificates) { this->load_server_certificates = std::move(load_server_certificates); return *this; } diff --git a/Development/nmos/streamcompatibility_validation.cpp b/Development/nmos/streamcompatibility_validation.cpp index dcee26d21..3e29dfb14 100644 --- a/Development/nmos/streamcompatibility_validation.cpp +++ b/Development/nmos/streamcompatibility_validation.cpp @@ -11,16 +11,20 @@ namespace nmos { namespace experimental { - bool match_resource_parameters_constraint_set(const parameter_constraints& constraints, const web::json::value& resource, const web::json::value& constraint_set_) + namespace detail { - if (!nmos::caps::meta::enabled(constraint_set_)) return false; - - const auto& constraint_set = constraint_set_.as_object(); - return constraint_set.end() == std::find_if(constraint_set.begin(), constraint_set.end(), [&](const std::pair& constraint) + bool match_resource_parameters_constraint_set(const parameter_constraints& constraints, const web::json::value& resource, const web::json::value& constraint_set_) { - const auto found = constraints.find(constraint.first); - return constraints.end() != found && !found->second(resource, constraint.second); - }); + if (!nmos::caps::meta::enabled(constraint_set_)) return false; + + const auto& constraint_set = constraint_set_.as_object(); + return constraint_set.end() == std::find_if(constraint_set.begin(), constraint_set.end(), [&](const std::pair& constraint) + { + const auto found = constraints.find(constraint.first); + return constraints.end() != found && !found->second(resource, constraint.second); + }); + } + } // "At any time if State of an active Sender becomes active_constraints_violation, the Sender MUST become inactive. @@ -67,24 +71,21 @@ namespace nmos } - details::resource_constraints_matcher make_streamcompatibility_resource_constraint_set_matcher() + bool match_resource_parameters_constraint_set(const nmos::resource& resource, const web::json::value& constraint_set) { - return [](const nmos::resource& resource, const web::json::value& constraint_set) -> bool + const std::map resource_parameter_constraints { - const std::map resource_parameter_constraints - { - { nmos::types::source, source_parameter_constraints }, - { nmos::types::flow, flow_parameter_constraints }, - { nmos::types::sender, sender_parameter_constraints } - }; + { nmos::types::source, source_parameter_constraints }, + { nmos::types::flow, flow_parameter_constraints }, + { nmos::types::sender, sender_parameter_constraints } + }; - if (0 == resource_parameter_constraints.count(resource.type)) - { - throw std::logic_error("wrong resource type"); - } + if (0 == resource_parameter_constraints.count(resource.type)) + { + throw std::logic_error("wrong resource type"); + } - return match_resource_parameters_constraint_set(resource_parameter_constraints.at(resource.type), resource.data, constraint_set); - }; + return detail::match_resource_parameters_constraint_set(resource_parameter_constraints.at(resource.type), resource.data, constraint_set); } details::streamcompatibility_sender_validator make_streamcompatibility_sender_resources_validator(const details::resource_constraints_matcher& match_resource_constraint_set, const details::transport_file_constraint_sets_matcher& match_transport_file_constraint_sets) diff --git a/Development/nmos/streamcompatibility_validation.h b/Development/nmos/streamcompatibility_validation.h index 55d80fb43..246ebc5aa 100644 --- a/Development/nmos/streamcompatibility_validation.h +++ b/Development/nmos/streamcompatibility_validation.h @@ -72,7 +72,10 @@ namespace nmos { nmos::caps::transport::st2110_21_sender_type, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::st2110_21_sender_type(sender), con); } } }; - bool match_resource_parameters_constraint_set(const parameter_constraints& constraints, const web::json::value& resource, const web::json::value& constraint_set); + namespace detail + { + bool match_resource_parameters_constraint_set(const parameter_constraints& constraints, const web::json::value& resource, const web::json::value& constraint_set); + } // "At any time if State of an active Sender becomes active_constraints_violation, the Sender MUST become inactive. // An inactive Sender in this state MUST NOT allow activations. @@ -82,7 +85,7 @@ namespace nmos nmos::details::connection_resource_patch_validator make_connection_streamcompatibility_validator(nmos::node_model& model); details::streamcompatibility_sender_validator make_streamcompatibility_sender_resources_validator(const details::resource_constraints_matcher& resource_matcher, const details::transport_file_constraint_sets_matcher& transport_file_matcher); - details::resource_constraints_matcher make_streamcompatibility_resource_constraint_set_matcher(); + bool match_resource_parameters_constraint_set(const nmos::resource& resource, const web::json::value& constraint_set); details::transport_file_constraint_sets_matcher make_streamcompatibility_sdp_constraint_sets_matcher(const details::sdp_constraint_sets_matcher& match_sdp_parameters_constraint_sets); } } diff --git a/Development/nmos/test/streamcompatibility_validation_test.cpp b/Development/nmos/test/streamcompatibility_validation_test.cpp index 047bd344f..d0767655d 100644 --- a/Development/nmos/test/streamcompatibility_validation_test.cpp +++ b/Development/nmos/test/streamcompatibility_validation_test.cpp @@ -10,7 +10,7 @@ BST_TEST_CASE(testResourceValidator) using web::json::value_of; using nmos::experimental::flow_parameter_constraints; using nmos::experimental::sender_parameter_constraints; - using nmos::experimental::match_resource_parameters_constraint_set; + using nmos::experimental::detail::match_resource_parameters_constraint_set; auto constraint_set = value_of({ { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, From 8680a8fc9bfe7f61c0bfaaf6339690371693f9c8 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 3 Feb 2023 20:08:42 +0400 Subject: [PATCH 050/109] Move empty Active Constraints fix --- .../nmos-cpp-node/node_implementation.cpp | 6 ----- Development/nmos/streamcompatibility_api.cpp | 27 ++++++++++--------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 68e09b270..224781b81 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1456,13 +1456,7 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler return [&gate, sender_capabilities](const nmos::id& sender_id, const web::json::value& active_constraints) -> bool { - using web::json::empty; - const auto& constraint_sets = nmos::fields::constraint_sets(active_constraints).as_array(); - if (empty(constraint_sets)) - { - return true; - } for (const auto& constraint_set : constraint_sets) { if (!nmos::caps::meta::enabled(constraint_set)) continue; diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index 9e64fb3f3..fc3b67c4e 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -786,24 +786,27 @@ namespace nmos auto resource = find_resource(resources, id_type); if (resources.end() != resource) { - const auto supported_param_constraints = boost::copy_range>(nmos::fields::parameter_constraints(nmos::fields::supported_param_constraints(resource->data)) | boost::adaptors::transformed([](const web::json::value& param_constraint) - { - return std::unordered_set::value_type{ param_constraint.as_string() }; - })); + auto& endpoint_active_constraints = nmos::fields::endpoint_active_constraints(resource->data); - if (details::validate_constraint_sets(nmos::fields::constraint_sets(data).as_array(), supported_param_constraints)) + if (!nmos::fields::temporarily_locked(endpoint_active_constraints)) { - auto& endpoint_active_constraints = nmos::fields::endpoint_active_constraints(resource->data); + const auto supported_param_constraints = boost::copy_range>(nmos::fields::parameter_constraints(nmos::fields::supported_param_constraints(resource->data)) | boost::adaptors::transformed([](const web::json::value& param_constraint) + { + return std::unordered_set::value_type{ param_constraint.as_string() }; + })); - if (!nmos::fields::temporarily_locked(endpoint_active_constraints)) + if (details::validate_constraint_sets(nmos::fields::constraint_sets(data).as_array(), supported_param_constraints)) { slog::log(gate, SLOG_FLF) << "Active Constraints update is requested for " << id_type; bool can_adhere = true; - if (active_constraints_handler) + if (!web::json::empty(nmos::fields::constraint_sets(data).as_array())) { - can_adhere = active_constraints_handler(resourceId, data); + if (active_constraints_handler) + { + can_adhere = active_constraints_handler(resourceId, data); + } } if (can_adhere) @@ -819,13 +822,13 @@ namespace nmos } else { - slog::log(gate, SLOG_FLF) << "Active Constraints update is requested for " << id_type << " but this operation is locked"; - set_error_reply(res, status_codes::Locked); + set_error_reply(res, status_codes::BadRequest, U("The requested Constraint Set uses Parameter Constraints unsupported by this Sender.")); } } else { - set_error_reply(res, status_codes::BadRequest, U("The requested Constraint Set uses Parameter Constraints unsupported by this Sender.")); + slog::log(gate, SLOG_FLF) << "Active Constraints update is requested for " << id_type << " but this operation is locked"; + set_error_reply(res, status_codes::Locked); } } else if (nmos::details::is_erased_resource(resources, id_type)) From d24eb37678b302401ea9013dd9457cc2595af3d5 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Tue, 21 Mar 2023 16:52:19 +0400 Subject: [PATCH 051/109] Add "edid_support" to Node config --- Development/nmos-cpp-node/config.json | 3 +++ .../nmos-cpp-node/node_implementation.cpp | 23 +++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Development/nmos-cpp-node/config.json b/Development/nmos-cpp-node/config.json index bd65a52e4..53c86ac47 100644 --- a/Development/nmos-cpp-node/config.json +++ b/Development/nmos-cpp-node/config.json @@ -50,6 +50,9 @@ // smpte2022_7: controls whether senders and receivers have one leg (false) or two legs (true, default) //"smpte2022_7": false, + // edid_support: controls whether inputs and output have EDID support + //"edid_support": false, + // Configuration settings and defaults for logging // error_log [registry, node]: filename for the error log or an empty string to write to stderr diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 224781b81..70984f21d 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -109,6 +109,9 @@ namespace impl // smpte2022_7: controls whether senders and receivers have one leg (false) or two legs (true, default) const web::json::field_as_bool_or smpte2022_7{ U("smpte2022_7"), true }; + + // edid_support: controls whether inputs and output have EDID support + const web::json::field_as_bool_or edid_support{ U("edid_support"), false }; } nmos::interlace_mode get_interlace_mode(const nmos::settings& settings); @@ -255,6 +258,7 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) const auto video_type = nmos::media_type{ impl::fields::video_type(model.settings) }; const auto channel_count = impl::fields::channel_count(model.settings); const auto smpte2022_7 = impl::fields::smpte2022_7(model.settings); + const auto edid_support = impl::fields::edid_support(model.settings); // for now, some typical values for video/jxsv, based on VSF TR-08:2022 // see https://vsf.tv/download/technical_recommendations/VSF_TR-08_2022-04-20.pdf @@ -913,7 +917,9 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) sender_ids.push_back(impl::make_id(seed_id, nmos::types::sender, port, index)); } - auto input = nmos::experimental::make_streamcompatibility_input(input_id, true, true, edid, bst::nullopt, sender_ids, model.settings); + auto input = edid_support + ? nmos::experimental::make_streamcompatibility_input(input_id, true, true, edid, bst::nullopt, sender_ids, model.settings) + : nmos::experimental::make_streamcompatibility_input(input_id, true, sender_ids, model.settings); impl::set_label_description(input, impl::ports::mux, 0); // The single Input consumes both video and audio signals if (!insert_resource_after(delay_millis, model.streamcompatibility_resources, std::move(input), gate)) return; @@ -960,7 +966,9 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) receiver_ids.push_back(impl::make_id(seed_id, nmos::types::receiver, port, index)); } - auto output = nmos::experimental::make_streamcompatibility_output(output_id, false, boost::variant(edid), bst::nullopt, receiver_ids, model.settings); + auto output = edid_support + ? nmos::experimental::make_streamcompatibility_output(output_id, true, boost::variant(edid), bst::nullopt, receiver_ids, model.settings) + : nmos::experimental::make_streamcompatibility_output(output_id, true, receiver_ids, model.settings); impl::set_label_description(output, impl::ports::mux, 0); // The single Output produces both video and audio signals if (!insert_resource_after(delay_millis, model.streamcompatibility_resources, std::move(output), gate)) return; @@ -1648,7 +1656,7 @@ namespace impl // into the server instance for the NMOS Node. nmos::experimental::node_implementation make_node_implementation(nmos::node_model& model, slog::base_gate& gate) { - return nmos::experimental::node_implementation() + auto node_implementation = nmos::experimental::node_implementation() .on_load_server_certificates(nmos::make_load_server_certificates_handler(model.settings, gate)) .on_load_dh_param(nmos::make_load_dh_param_handler(model.settings, gate)) .on_load_ca_certificates(nmos::make_load_ca_certificates_handler(model.settings, gate)) @@ -1663,8 +1671,15 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode .on_channelmapping_activated(make_node_implementation_channelmapping_activation_handler(gate)) .on_base_edid_changed(make_node_implementation_streamcompatibility_base_edid_put_handler(gate)) .on_base_edid_deleted(make_node_implementation_streamcompatibility_base_edid_delete_handler(gate)) - .on_set_effective_edid(make_node_implementation_streamcompatibility_effective_edid_setter(model.streamcompatibility_resources, gate)) .on_active_constraints_changed(make_node_implementation_streamcompatibility_active_constraints_handler(gate)) .on_validate_sender_resources_against_active_constraints(make_node_implementation_streamcompatibility_sender_validator()) // may be omitted if the default is sufficient .on_validate_receiver_against_transport_file(make_node_implementation_streamcompatibility_receiver_validator()); + + if (impl::fields::edid_support(model.settings)) + { + node_implementation + .on_set_effective_edid(make_node_implementation_streamcompatibility_effective_edid_setter(model.streamcompatibility_resources, gate)); // may be omitted if not required + } + + return node_implementation; } From 2e08ff55139bb7e386d45547a93be45eb08a34d4 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Tue, 21 Mar 2023 17:09:13 +0400 Subject: [PATCH 052/109] Different supported parameter constraints and Sedner Caps for video and audio Senders --- .../nmos-cpp-node/node_implementation.cpp | 61 ++++++++++++++++--- .../nmos/streamcompatibility_resources.cpp | 21 +------ 2 files changed, 53 insertions(+), 29 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 70984f21d..58f31c444 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -923,12 +923,34 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) impl::set_label_description(input, impl::ports::mux, 0); // The single Input consumes both video and audio signals if (!insert_resource_after(delay_millis, model.streamcompatibility_resources, std::move(input), gate)) return; - for (const auto& sender_id : sender_ids) + const std::vector video_parameter_constraints{ + nmos::caps::meta::label.key, + nmos::caps::meta::preference.key, + nmos::caps::meta::enabled.key, + nmos::caps::format::media_type.key, + nmos::caps::format::grain_rate.key, + nmos::caps::format::frame_width.key, + nmos::caps::format::frame_height.key, + nmos::caps::format::interlace_mode.key, + nmos::caps::format::colorspace.key, + nmos::caps::format::color_sampling.key, + nmos::caps::format::component_depth.key + }; + + const std::vector audio_parameter_constraints{ + nmos::caps::meta::label.key, + nmos::caps::meta::preference.key, + nmos::caps::meta::enabled.key, + nmos::caps::format::media_type.key, + nmos::caps::format::channel_count.key, + nmos::caps::format::sample_rate.key, + nmos::caps::format::sample_depth.key + }; + + for (const auto& port : { impl::ports::video, impl::ports::audio }) { - // Add "packet_time" to the list of Parameter Constraints supported by these Senders - const std::vector supported_param_constraints{ - nmos::caps::transport::packet_time.key - }; + const auto sender_id = impl::make_id(seed_id, nmos::types::sender, port, index); + const auto& supported_param_constraints = port == impl::ports::video ? video_parameter_constraints : audio_parameter_constraints; auto streamcompatibility_sender = nmos::experimental::make_streamcompatibility_sender(sender_id, { input_id }, supported_param_constraints); if (!insert_resource_after(delay_millis, model.streamcompatibility_resources, std::move(streamcompatibility_sender), gate)) return; } @@ -1431,11 +1453,18 @@ nmos::experimental::details::streamcompatibility_effective_edid_setter make_node } // Example Stream Compatibility Management API callback to update Active Constraints of a Sender -nmos::experimental::details::streamcompatibility_active_constraints_put_handler make_node_implementation_streamcompatibility_active_constraints_handler(slog::base_gate& gate) +nmos::experimental::details::streamcompatibility_active_constraints_put_handler make_node_implementation_streamcompatibility_active_constraints_handler(const nmos::node_model& model, slog::base_gate& gate) { using web::json::value_of; - auto sender_capabilities = value_of({ + const auto seed_id = nmos::experimental::fields::seed_id(model.settings); + const auto how_many = impl::fields::how_many(model.settings); + const auto video_sender_ids = impl::make_ids(seed_id, nmos::types::sender, { impl::ports::video }, how_many); + const auto audio_sender_ids = impl::make_ids(seed_id, nmos::types::sender, { impl::ports::audio }, how_many); + + // Each Constraint Set in Sender Caps should contain all parameter constraints from /constraints/supported except for "meta" + // and parameter constraints that are not applicable to this Sender + auto video_sender_capabilities = value_of({ value_of({ { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25, nmos::rates::rate29_97 }) }, @@ -1462,8 +1491,22 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler }) }); - return [&gate, sender_capabilities](const nmos::id& sender_id, const web::json::value& active_constraints) -> bool + auto audio_sender_capabilities = value_of({ + value_of({ + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::audio_L(8).name, nmos::media_types::audio_L(16).name, nmos::media_types::audio_L(20).name, nmos::media_types::audio_L(24).name }) }, + { nmos::caps::format::channel_count, nmos::make_caps_integer_constraint({ (int)impl::channels_repeat.size() }) }, + { nmos::caps::format::sample_rate, nmos::make_caps_rational_constraint({ nmos::rational{48000, 1} }) }, + { nmos::caps::format::sample_depth, nmos::make_caps_integer_constraint({ 8, 16, 20, 24 }) } + }) + }); + + return [&gate, video_sender_capabilities, audio_sender_capabilities, video_sender_ids, audio_sender_ids](const nmos::id& sender_id, const web::json::value& active_constraints) -> bool { + const bool video_found = video_sender_ids.end() != boost::range::find(video_sender_ids, sender_id); + const bool audio_found = audio_sender_ids.end() != boost::range::find(audio_sender_ids, sender_id); + + const auto& sender_capabilities = video_found ? video_sender_capabilities : audio_found ? audio_sender_capabilities : throw std::logic_error("No Sender Capabilities found for " + sender_id); + const auto& constraint_sets = nmos::fields::constraint_sets(active_constraints).as_array(); for (const auto& constraint_set : constraint_sets) { @@ -1671,7 +1714,7 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode .on_channelmapping_activated(make_node_implementation_channelmapping_activation_handler(gate)) .on_base_edid_changed(make_node_implementation_streamcompatibility_base_edid_put_handler(gate)) .on_base_edid_deleted(make_node_implementation_streamcompatibility_base_edid_delete_handler(gate)) - .on_active_constraints_changed(make_node_implementation_streamcompatibility_active_constraints_handler(gate)) + .on_active_constraints_changed(make_node_implementation_streamcompatibility_active_constraints_handler(model, gate)) .on_validate_sender_resources_against_active_constraints(make_node_implementation_streamcompatibility_sender_validator()) // may be omitted if the default is sufficient .on_validate_receiver_against_transport_file(make_node_implementation_streamcompatibility_receiver_validator()); diff --git a/Development/nmos/streamcompatibility_resources.cpp b/Development/nmos/streamcompatibility_resources.cpp index 8e3c789ed..22cf00998 100644 --- a/Development/nmos/streamcompatibility_resources.cpp +++ b/Development/nmos/streamcompatibility_resources.cpp @@ -30,27 +30,8 @@ namespace nmos using web::json::value_of; using web::json::value_from_elements; - std::unordered_set parameter_constraints{ - nmos::caps::meta::label.key, - nmos::caps::meta::preference.key, - nmos::caps::meta::enabled.key, - nmos::caps::format::media_type.key, - nmos::caps::format::grain_rate.key, - nmos::caps::format::frame_width.key, - nmos::caps::format::frame_height.key, - nmos::caps::format::interlace_mode.key, - nmos::caps::format::colorspace.key, - nmos::caps::format::color_sampling.key, - nmos::caps::format::component_depth.key, - nmos::caps::format::channel_count.key, - nmos::caps::format::sample_rate.key, - nmos::caps::format::sample_depth.key - }; - - parameter_constraints.insert(param_constraints.begin(), param_constraints.end()); - auto supported_param_constraints = value_of({ - { nmos::fields::parameter_constraints, value_from_elements(parameter_constraints) }, + { nmos::fields::parameter_constraints, value_from_elements(param_constraints) }, }); auto data = value_of({ From f5106cad4c90f5dcf10fd80802981398c7a60e28 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Tue, 21 Mar 2023 17:16:06 +0400 Subject: [PATCH 053/109] Add handling of Active Constraints which Constraint Sets contain less parameter constraints than Constraint Sets of Sender Caps --- Development/nmos-cpp-node/node_implementation.cpp | 4 +++- Development/nmos/constraints.cpp | 13 +++++++++++-- Development/nmos/constraints.h | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 58f31c444..2643d1652 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1513,7 +1513,8 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler if (!nmos::caps::meta::enabled(constraint_set)) continue; for (const auto& sender_caps_constraint_set : sender_capabilities.as_array()) { - if (nmos::experimental::is_constraint_subset(sender_caps_constraint_set, constraint_set)) + if (nmos::experimental::is_constraint_subset(sender_caps_constraint_set, constraint_set, true) || // the Constraint Set makes Sender Constraint Set's constraints narrower + nmos::experimental::is_constraint_subset(constraint_set, sender_caps_constraint_set)) // the Constraint Set is wider than the Sender Constraint Set so the latter is always compliant { return true; } @@ -1722,6 +1723,7 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode { node_implementation .on_set_effective_edid(make_node_implementation_streamcompatibility_effective_edid_setter(model.streamcompatibility_resources, gate)); // may be omitted if not required + } return node_implementation; diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index c76ef983c..b90bc769e 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -93,12 +93,21 @@ namespace nmos // Constraint Set B is a subset of Constraint Set A if all Parameter Constraints of Constraint Set A are present in Constraint Set B, and for each Parameter Constraint // that is present in both, the Parameter Constraint of Constraint Set B is a subconstraint of the Parameter Constraint of Constraint Set A. - bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset) + bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset, bool merge) { using web::json::value; const auto& param_constraints_set = constraint_set.as_object(); - const auto& param_constraints_subset = constraint_subset.as_object(); + auto param_constraints_subset = constraint_subset.as_object(); + + if (merge) + { + param_constraints_subset = param_constraints_set; + std::for_each(constraint_subset.as_object().begin(), constraint_subset.as_object().end(), [¶m_constraints_subset](const std::pair& subconstraint) + { + param_constraints_subset[subconstraint.first] = subconstraint.second; + }); + } return param_constraints_set.end() == std::find_if_not(param_constraints_set.begin(), param_constraints_set.end(), [¶m_constraints_subset](const std::pair& constraint) { diff --git a/Development/nmos/constraints.h b/Development/nmos/constraints.h index dc33b4ced..79667c032 100644 --- a/Development/nmos/constraints.h +++ b/Development/nmos/constraints.h @@ -14,7 +14,7 @@ namespace nmos namespace experimental { bool is_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint); - bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset); + bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset, bool merge = false); } } From fb0e20efac192ce02ae9007ad93f11eada78a9d7 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Tue, 21 Mar 2023 22:46:24 +0400 Subject: [PATCH 054/109] Fix build --- Development/nmos-cpp-node/node_implementation.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 2643d1652..ea8b35cd0 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1459,8 +1459,8 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler const auto seed_id = nmos::experimental::fields::seed_id(model.settings); const auto how_many = impl::fields::how_many(model.settings); - const auto video_sender_ids = impl::make_ids(seed_id, nmos::types::sender, { impl::ports::video }, how_many); - const auto audio_sender_ids = impl::make_ids(seed_id, nmos::types::sender, { impl::ports::audio }, how_many); + const auto video_sender_ids = impl::make_ids(seed_id, nmos::types::sender, impl::ports::video, how_many); + const auto audio_sender_ids = impl::make_ids(seed_id, nmos::types::sender, impl::ports::audio, how_many); // Each Constraint Set in Sender Caps should contain all parameter constraints from /constraints/supported except for "meta" // and parameter constraints that are not applicable to this Sender @@ -1505,7 +1505,7 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler const bool video_found = video_sender_ids.end() != boost::range::find(video_sender_ids, sender_id); const bool audio_found = audio_sender_ids.end() != boost::range::find(audio_sender_ids, sender_id); - const auto& sender_capabilities = video_found ? video_sender_capabilities : audio_found ? audio_sender_capabilities : throw std::logic_error("No Sender Capabilities found for " + sender_id); + const auto& sender_capabilities = video_found ? video_sender_capabilities : audio_found ? audio_sender_capabilities : throw std::logic_error("No Sender Capabilities found"); const auto& constraint_sets = nmos::fields::constraint_sets(active_constraints).as_array(); for (const auto& constraint_set : constraint_sets) From 15a27a9d66415df31604cef22d9d8bf40ec753d3 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Mon, 10 Apr 2023 01:18:41 +0400 Subject: [PATCH 055/109] Add room for custom "debug" in 422 response --- .../nmos-cpp-node/node_implementation.cpp | 6 +++--- Development/nmos/streamcompatibility_api.cpp | 17 +++++++++++------ Development/nmos/streamcompatibility_api.h | 3 ++- .../nmos/streamcompatibility_behaviour.h | 1 + .../nmos/streamcompatibility_validation.h | 2 ++ 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index eb51c699d..230a3d17e 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1505,7 +1505,7 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler }) }); - return [&gate, video_sender_capabilities, audio_sender_capabilities, video_sender_ids, audio_sender_ids](const nmos::id& sender_id, const web::json::value& active_constraints) -> bool + return [&gate, video_sender_capabilities, audio_sender_capabilities, video_sender_ids, audio_sender_ids](const nmos::id& sender_id, const web::json::value& active_constraints) { const bool video_found = video_sender_ids.end() != boost::range::find(video_sender_ids, sender_id); const bool audio_found = audio_sender_ids.end() != boost::range::find(audio_sender_ids, sender_id); @@ -1521,12 +1521,12 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler if (nmos::experimental::is_constraint_subset(sender_caps_constraint_set, constraint_set, true) || // the Constraint Set makes Sender Constraint Set's constraints narrower nmos::experimental::is_constraint_subset(constraint_set, sender_caps_constraint_set)) // the Constraint Set is wider than the Sender Constraint Set so the latter is always compliant { - return true; + return; } } } slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " doesn't support proposed Active Constraints"; - return false; + throw std::logic_error("sender capabilities are " + sender_capabilities.serialize()); }; } diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index fc3b67c4e..058699f33 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -805,7 +805,17 @@ namespace nmos { if (active_constraints_handler) { - can_adhere = active_constraints_handler(resourceId, data); + try + { + active_constraints_handler(resourceId, data); + } + catch(const std::logic_error& e) + { + can_adhere = false; + + slog::log(gate, SLOG_FLF) << "Active Constraints update is requested for " << id_type << " but this sender can't adhere to these Constraints"; + set_error_reply(res, status_codes::UnprocessableEntity, e); + } } } @@ -814,11 +824,6 @@ namespace nmos details::set_active_constraints(model, resourceId, nmos::fields::constraint_sets(data), effective_edid_setter); set_reply(res, status_codes::OK, data); } - else - { - slog::log(gate, SLOG_FLF) << "Active Constraints update is requested for " << id_type << " but this sender can't adhere to these Constraints"; - set_error_reply(res, status_codes::UnprocessableEntity); - } } else { diff --git a/Development/nmos/streamcompatibility_api.h b/Development/nmos/streamcompatibility_api.h index c23a64cc0..f49e24865 100644 --- a/Development/nmos/streamcompatibility_api.h +++ b/Development/nmos/streamcompatibility_api.h @@ -29,8 +29,9 @@ namespace nmos // a streamcompatibility_active_constraints_put_handler is a notification that the Active Constraints for the specified IS-11 sender has changed // it can be used to perform any final validation of the specified Active Constraints // it may throw web::json::json_exception, which will be mapped to a 400 Bad Request status code with NMOS error "debug" information including the exception message + // or std::logic_error, which will be mapped to a 422 Unprocessible Entity status code with NMOS error "debug" information including the exception message // or std::runtime_error, which will be mapped to a 500 Internal Error status code with NMOS error "debug" information including the exception message - typedef std::function streamcompatibility_active_constraints_put_handler; + typedef std::function streamcompatibility_active_constraints_put_handler; // a streamcompatibility_effective_edid_setter updates the specified Effective EDID for the specified IS-11 input // effective EDID of the input is updated when either Active Constraints of a Sender associated with this input are updated diff --git a/Development/nmos/streamcompatibility_behaviour.h b/Development/nmos/streamcompatibility_behaviour.h index 0d70efec0..2b0c12621 100644 --- a/Development/nmos/streamcompatibility_behaviour.h +++ b/Development/nmos/streamcompatibility_behaviour.h @@ -28,6 +28,7 @@ namespace nmos { namespace details { + // returns Receiver's "state" and "debug" values typedef std::function(const web::json::value& transport_file, const web::json::value& receiver)> streamcompatibility_receiver_validator; } diff --git a/Development/nmos/streamcompatibility_validation.h b/Development/nmos/streamcompatibility_validation.h index 246ebc5aa..9bbd073f2 100644 --- a/Development/nmos/streamcompatibility_validation.h +++ b/Development/nmos/streamcompatibility_validation.h @@ -27,7 +27,9 @@ namespace nmos { namespace details { + // returns Sender's "state" and "debug" values typedef std::function(const web::json::value& transport_file, const nmos::resource& sender, const nmos::resource& flow, const nmos::resource& source, const web::json::array& constraint_sets)> streamcompatibility_sender_validator; + typedef std::function resource_constraints_matcher; typedef std::function& transport_file, const web::json::array& constraint_sets)> transport_file_constraint_sets_matcher; typedef std::function sdp_constraint_sets_matcher; From c07645bfbc91d97d2970c9d6612e08d30d679470 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Mon, 17 Apr 2023 17:04:55 +0400 Subject: [PATCH 056/109] Fix Windows build --- Development/nmos-cpp-node/node_implementation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 230a3d17e..8e8313531 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1526,7 +1526,7 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler } } slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " doesn't support proposed Active Constraints"; - throw std::logic_error("sender capabilities are " + sender_capabilities.serialize()); + throw std::logic_error("sender capabilities are " + utility::us2s(sender_capabilities.serialize())); }; } From e31e434b21b917fd6ff265653addbf62728d10a4 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Thu, 20 Apr 2023 14:00:42 +0400 Subject: [PATCH 057/109] Add make_streamcompatibility_receiver_validator --- .../nmos-cpp-node/node_implementation.cpp | 8 ++-- .../nmos/streamcompatibility_behaviour.cpp | 38 +------------------ .../nmos/streamcompatibility_behaviour.h | 6 --- .../nmos/streamcompatibility_validation.cpp | 33 +++++++++++++++- .../nmos/streamcompatibility_validation.h | 3 ++ 5 files changed, 39 insertions(+), 49 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 93156f98d..29bc94c97 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1570,13 +1570,11 @@ nmos::experimental::details::streamcompatibility_sender_validator make_node_impl }; } -nmos::experimental::details::streamcompatibility_receiver_validator make_node_implementation_streamcompatibility_receiver_validator() +nmos::experimental::details::streamcompatibility_receiver_validator make_node_implementation_streamcompatibility_receiver_validator(slog::base_gate& gate) { - // this example uses the default receiver validator explicitly - return &nmos::experimental::validate_receiver_resources; + return nmos::experimental::make_streamcompatibility_receiver_validator(make_node_implementation_transport_file_parser(), gate); } - namespace impl { nmos::interlace_mode get_interlace_mode(const nmos::settings& settings) @@ -1735,7 +1733,7 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode .on_base_edid_deleted(make_node_implementation_streamcompatibility_base_edid_delete_handler(gate)) .on_active_constraints_changed(make_node_implementation_streamcompatibility_active_constraints_handler(model, gate)) .on_validate_sender_resources_against_active_constraints(make_node_implementation_streamcompatibility_sender_validator()) // may be omitted if the default is sufficient - .on_validate_receiver_against_transport_file(make_node_implementation_streamcompatibility_receiver_validator()); + .on_validate_receiver_against_transport_file(make_node_implementation_streamcompatibility_receiver_validator(gate)); if (impl::fields::edid_support(model.settings)) { diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index f23070bfa..a4f6ac178 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -33,42 +33,6 @@ namespace nmos })); } - std::pair validate_receiver_resources(const web::json::value& transport_file_, const web::json::value& receiver) - { - nmos::receiver_state receiver_state; - utility::string_t receiver_state_debug; - - if (!transport_file_.is_null() && !transport_file_.as_object().empty()) - { - const auto transport_file = nmos::details::get_transport_type_data(transport_file_); - if (nmos::media_types::application_sdp.name != transport_file.first) - { - throw std::runtime_error("unknown transport file type"); - } - - const auto session_description = sdp::parse_session_description(utility::us2s(transport_file.second)); - auto sdp_params = nmos::parse_session_description(session_description).first; - - receiver_state = nmos::receiver_states::compliant_stream; - - try - { - validate_sdp_parameters(receiver, sdp_params); - } - catch (const std::runtime_error& e) - { - receiver_state = nmos::receiver_states::non_compliant_stream; - receiver_state_debug = utility::conversions::to_string_t(e.what()); - } - } - else - { - receiver_state = nmos::receiver_states::unknown; - } - - return { receiver_state, receiver_state_debug }; - } - void streamcompatibility_behaviour_thread(nmos::node_model& model, details::streamcompatibility_sender_validator validate_sender_resources, details::streamcompatibility_receiver_validator validate_receiver, slog::base_gate& gate) { using web::json::value; @@ -215,7 +179,7 @@ namespace nmos if (validate_receiver) { - std::tie(receiver_state, receiver_state_debug) = validate_receiver(transport_file, receiver->data); + std::tie(receiver_state, receiver_state_debug) = validate_receiver(transport_file, *receiver, *connection_receiver); } if (nmos::fields::state(nmos::fields::status(streamcompatibility_receiver->data)) != receiver_state.name) diff --git a/Development/nmos/streamcompatibility_behaviour.h b/Development/nmos/streamcompatibility_behaviour.h index 2b0c12621..41da79d3d 100644 --- a/Development/nmos/streamcompatibility_behaviour.h +++ b/Development/nmos/streamcompatibility_behaviour.h @@ -26,12 +26,6 @@ namespace nmos namespace experimental { - namespace details - { - // returns Receiver's "state" and "debug" values - typedef std::function(const web::json::value& transport_file, const web::json::value& receiver)> streamcompatibility_receiver_validator; - } - std::pair validate_receiver_resources(const web::json::value& transport_file, const web::json::value& receiver); void streamcompatibility_behaviour_thread(nmos::node_model& model, details::streamcompatibility_sender_validator validate_sender_resources, details::streamcompatibility_receiver_validator validate_receiver, slog::base_gate& gate); } diff --git a/Development/nmos/streamcompatibility_validation.cpp b/Development/nmos/streamcompatibility_validation.cpp index 3e29dfb14..b6f4cd871 100644 --- a/Development/nmos/streamcompatibility_validation.cpp +++ b/Development/nmos/streamcompatibility_validation.cpp @@ -97,7 +97,6 @@ namespace nmos if (!web::json::empty(constraint_sets)) { bool constrained = true; - auto source_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return match_resource_constraint_set(source, constraint_set); }); auto flow_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return match_resource_constraint_set(flow, constraint_set); }); auto sender_found = std::find_if(constraint_sets.begin(), constraint_sets.end(), [&](const web::json::value& constraint_set) { return match_resource_constraint_set(sender, constraint_set); }); @@ -119,5 +118,37 @@ namespace nmos return { sender_state, {} }; }; } + + details::streamcompatibility_receiver_validator make_streamcompatibility_receiver_validator(const nmos::transport_file_parser& parse_and_validate_transport_file, slog::base_gate& gate) + { + return [parse_and_validate_transport_file, &gate](const web::json::value& transport_file_, const nmos::resource& receiver, const nmos::resource& connection_receiver) -> std::pair + { + nmos::receiver_state receiver_state; + utility::string_t receiver_state_debug; + + if (!transport_file_.is_null() && !transport_file_.as_object().empty()) + { + const auto transport_file = nmos::details::get_transport_type_data(transport_file_); + + receiver_state = nmos::receiver_states::compliant_stream; + + try + { + parse_and_validate_transport_file(receiver, connection_receiver, transport_file.first, transport_file.second, gate); + } + catch (const std::runtime_error& e) + { + receiver_state = nmos::receiver_states::non_compliant_stream; + receiver_state_debug = utility::conversions::to_string_t(e.what()); + } + } + else + { + receiver_state = nmos::receiver_states::unknown; + } + + return { receiver_state, receiver_state_debug }; + }; + } } } diff --git a/Development/nmos/streamcompatibility_validation.h b/Development/nmos/streamcompatibility_validation.h index 9bbd073f2..a3437e748 100644 --- a/Development/nmos/streamcompatibility_validation.h +++ b/Development/nmos/streamcompatibility_validation.h @@ -29,6 +29,8 @@ namespace nmos { // returns Sender's "state" and "debug" values typedef std::function(const web::json::value& transport_file, const nmos::resource& sender, const nmos::resource& flow, const nmos::resource& source, const web::json::array& constraint_sets)> streamcompatibility_sender_validator; + // returns Receiver's "state" and "debug" values + typedef std::function(const web::json::value& transport_file, const nmos::resource& receiver, const nmos::resource& connection_receiver)> streamcompatibility_receiver_validator; typedef std::function resource_constraints_matcher; typedef std::function& transport_file, const web::json::array& constraint_sets)> transport_file_constraint_sets_matcher; @@ -87,6 +89,7 @@ namespace nmos nmos::details::connection_resource_patch_validator make_connection_streamcompatibility_validator(nmos::node_model& model); details::streamcompatibility_sender_validator make_streamcompatibility_sender_resources_validator(const details::resource_constraints_matcher& resource_matcher, const details::transport_file_constraint_sets_matcher& transport_file_matcher); + details::streamcompatibility_receiver_validator make_streamcompatibility_receiver_validator(const nmos::transport_file_parser& parse_and_validate_transport_file, slog::base_gate& gate); bool match_resource_parameters_constraint_set(const nmos::resource& resource, const web::json::value& constraint_set); details::transport_file_constraint_sets_matcher make_streamcompatibility_sdp_constraint_sets_matcher(const details::sdp_constraint_sets_matcher& match_sdp_parameters_constraint_sets); } From 48b3c2cf62e8d070400f31665ba9b92e3eb49368 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Thu, 20 Apr 2023 14:29:33 +0400 Subject: [PATCH 058/109] Fix Sender validator for video/jxsv --- .../nmos-cpp-node/node_implementation.cpp | 31 ++++++++---- Development/nmos/video_jxsv.h | 48 ++++++++++--------- 2 files changed, 48 insertions(+), 31 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 29bc94c97..edff04381 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1548,19 +1548,34 @@ nmos::experimental::details::streamcompatibility_sender_validator make_node_impl using nmos::experimental::make_streamcompatibility_sender_resources_validator; using nmos::experimental::make_streamcompatibility_sdp_constraint_sets_matcher; - // this example uses a custom sender resources validator to handle video/jxsv in addition to the core media types - // (if this callback is specified, an 'empty' std::function is not allowed) - return [](const web::json::value& transport_file, const nmos::resource& sender, const nmos::resource& flow, const nmos::resource& source, const web::json::array& constraint_sets) -> std::pair + const auto video_jxsv_sender_resources_matcher = [](const nmos::resource& resource, const web::json::value& constraint_set) -> bool { - const auto video_jxsv_sender_resources_matcher = [](const nmos::resource& resource, const web::json::value& constraint_set) -> bool + const std::map resource_parameter_constraints { - return nmos::experimental::detail::match_resource_parameters_constraint_set(nmos::video_jxsv_parameter_constraints, resource.data, constraint_set); + { nmos::types::source, nmos::experimental::source_parameter_constraints }, + { nmos::types::flow, nmos::experimental::video_jxsv_flow_parameter_constraints }, + { nmos::types::sender, nmos::experimental::video_jxsv_sender_parameter_constraints } }; - const auto validate_video_jxsv_sender_resources = make_streamcompatibility_sender_resources_validator(video_jxsv_sender_resources_matcher, make_streamcompatibility_sdp_constraint_sets_matcher(&nmos::match_video_jxsv_sdp_parameters_constraint_sets)); - const auto validate_sender_resources = make_streamcompatibility_sender_resources_validator(&nmos::experimental::match_resource_parameters_constraint_set, make_streamcompatibility_sdp_constraint_sets_matcher(&nmos::match_sdp_parameters_constraint_sets)); + + if (0 == resource_parameter_constraints.count(resource.type)) + { + throw std::logic_error("wrong resource type"); + } + + return nmos::experimental::detail::match_resource_parameters_constraint_set(resource_parameter_constraints.at(resource.type), resource.data, constraint_set); + }; + + const auto validate_video_jxsv_sender_resources = make_streamcompatibility_sender_resources_validator(video_jxsv_sender_resources_matcher, make_streamcompatibility_sdp_constraint_sets_matcher(&nmos::match_video_jxsv_sdp_parameters_constraint_sets)); + const auto validate_sender_resources = make_streamcompatibility_sender_resources_validator(&nmos::experimental::match_resource_parameters_constraint_set, make_streamcompatibility_sdp_constraint_sets_matcher(&nmos::match_sdp_parameters_constraint_sets)); + + // this example uses a custom sender resources validator to handle video/jxsv in addition to the core media types + // (if this callback is specified, an 'empty' std::function is not allowed) + return [validate_video_jxsv_sender_resources, validate_sender_resources](const web::json::value& transport_file, const nmos::resource& sender, const nmos::resource& flow, const nmos::resource& source, const web::json::array& constraint_sets) -> std::pair + { if (nmos::media_types::video_jxsv.name == nmos::fields::media_type(flow.data)) { - return validate_video_jxsv_sender_resources(transport_file, sender, flow, source, constraint_sets); + std::pair res = validate_video_jxsv_sender_resources(transport_file, sender, flow, source, constraint_sets); + return res; } else { diff --git a/Development/nmos/video_jxsv.h b/Development/nmos/video_jxsv.h index 3400cf60d..d4aac24d2 100644 --- a/Development/nmos/video_jxsv.h +++ b/Development/nmos/video_jxsv.h @@ -392,30 +392,32 @@ namespace nmos double bits_per_pixel, const nmos::settings& settings); - const std::map> video_jxsv_parameter_constraints + namespace experimental { - // Flow Constraints - - { nmos::caps::format::media_type, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::media_type(flow), con); } }, - { nmos::caps::format::grain_rate, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_rational_constraint(nmos::parse_rational(nmos::fields::grain_rate(flow)), con); } }, - { nmos::caps::format::profile, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::profile(flow), con); } }, - { nmos::caps::format::level, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::level(flow), con); } }, - { nmos::caps::format::sublevel, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::sublevel(flow), con); } }, - { nmos::caps::format::frame_height, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::frame_height(flow), con); } }, - { nmos::caps::format::frame_width, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::frame_width(flow), con); } }, - { nmos::caps::format::color_sampling, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::details::make_sampling(nmos::fields::components(flow)).name, con); } }, - { nmos::caps::format::interlace_mode, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::interlace_mode(flow), con); } }, - { nmos::caps::format::colorspace, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::colorspace(flow), con); } }, - { nmos::caps::format::transfer_characteristic, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::transfer_characteristic(flow), con); } }, - { nmos::caps::format::component_depth, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_depth(nmos::fields::components(flow).at(0)), con); } }, - { nmos::caps::format::bit_rate, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_rate(flow), con); } }, - - // Sender Constraints - - { nmos::caps::transport::packet_transmission_mode, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::packet_transmission_mode(sender), con); } }, - { nmos::caps::transport::st2110_21_sender_type, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::st2110_21_sender_type(sender), con); } }, - { nmos::caps::transport::bit_rate, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_rate(sender), con); } } - }; + const std::map> video_jxsv_flow_parameter_constraints + { + { nmos::caps::format::media_type, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::media_type(flow), con); } }, + { nmos::caps::format::grain_rate, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_rational_constraint(nmos::parse_rational(nmos::fields::grain_rate(flow)), con); } }, + { nmos::caps::format::profile, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::profile(flow), con); } }, + { nmos::caps::format::level, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::level(flow), con); } }, + { nmos::caps::format::sublevel, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::sublevel(flow), con); } }, + { nmos::caps::format::frame_height, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::frame_height(flow), con); } }, + { nmos::caps::format::frame_width, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::frame_width(flow), con); } }, + { nmos::caps::format::color_sampling, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::details::make_sampling(nmos::fields::components(flow)).name, con); } }, + { nmos::caps::format::interlace_mode, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::interlace_mode(flow), con); } }, + { nmos::caps::format::colorspace, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::colorspace(flow), con); } }, + { nmos::caps::format::transfer_characteristic, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::transfer_characteristic(flow), con); } }, + { nmos::caps::format::component_depth, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_depth(nmos::fields::components(flow).at(0)), con); } }, + { nmos::caps::format::bit_rate, [](const web::json::value& flow, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_rate(flow), con); } }, + }; + + const std::map> video_jxsv_sender_parameter_constraints + { + { nmos::caps::transport::packet_transmission_mode, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::packet_transmission_mode(sender), con); } }, + { nmos::caps::transport::st2110_21_sender_type, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::st2110_21_sender_type(sender), con); } }, + { nmos::caps::transport::bit_rate, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_rate(sender), con); } } + }; + } } #endif From 29bc35b84ad3843e0ed7ab0d64c10c23a1ea7665 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Thu, 20 Apr 2023 16:49:01 +0400 Subject: [PATCH 059/109] Fix receivers validation --- .../nmos/streamcompatibility_behaviour.cpp | 2 +- .../nmos/streamcompatibility_validation.cpp | 30 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index a4f6ac178..58164047e 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -175,7 +175,7 @@ namespace nmos auto connection_receiver = find_resource(connection_resources, connection_receiver_id_type); if (connection_resources.end() == connection_receiver) throw std::logic_error("Matching IS-05 receiver not found"); - auto& transport_file = nmos::fields::transport_file(nmos::fields::endpoint_staged(connection_receiver->data)); + const auto& transport_file = nmos::fields::transport_file(nmos::fields::endpoint_active(connection_receiver->data)); if (validate_receiver) { diff --git a/Development/nmos/streamcompatibility_validation.cpp b/Development/nmos/streamcompatibility_validation.cpp index b6f4cd871..0c98edbbf 100644 --- a/Development/nmos/streamcompatibility_validation.cpp +++ b/Development/nmos/streamcompatibility_validation.cpp @@ -4,6 +4,7 @@ #include "nmos/model.h" #include "nmos/resource.h" #include "nmos/resources.h" +#include "nmos/slog.h" #include "nmos/streamcompatibility_state.h" #include "sdp/sdp.h" @@ -123,29 +124,28 @@ namespace nmos { return [parse_and_validate_transport_file, &gate](const web::json::value& transport_file_, const nmos::resource& receiver, const nmos::resource& connection_receiver) -> std::pair { - nmos::receiver_state receiver_state; + nmos::receiver_state receiver_state = nmos::receiver_states::unknown; utility::string_t receiver_state_debug; if (!transport_file_.is_null() && !transport_file_.as_object().empty()) { const auto transport_file = nmos::details::get_transport_type_data(transport_file_); - - receiver_state = nmos::receiver_states::compliant_stream; - - try - { - parse_and_validate_transport_file(receiver, connection_receiver, transport_file.first, transport_file.second, gate); - } - catch (const std::runtime_error& e) + if (std::pair{} != transport_file) { - receiver_state = nmos::receiver_states::non_compliant_stream; - receiver_state_debug = utility::conversions::to_string_t(e.what()); + receiver_state = nmos::receiver_states::compliant_stream; + + try + { + parse_and_validate_transport_file(receiver, connection_receiver, transport_file.first, transport_file.second, gate); + } + catch (const std::runtime_error& e) + { + slog::log(gate, SLOG_FLF) << "Validation of receiver " << receiver.id << " failed with " << e.what(); + receiver_state = nmos::receiver_states::non_compliant_stream; + receiver_state_debug = utility::conversions::to_string_t(e.what()); + } } } - else - { - receiver_state = nmos::receiver_states::unknown; - } return { receiver_state, receiver_state_debug }; }; From 16af3ee967af88df9ed64779f07336c047b8f855 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Thu, 20 Apr 2023 21:20:11 +0400 Subject: [PATCH 060/109] Fill Sender Caps from impl::fields --- .../nmos-cpp-node/node_implementation.cpp | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index edff04381..ee79d2b2a 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1480,41 +1480,39 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler const auto video_sender_ids = impl::make_ids(seed_id, nmos::types::sender, impl::ports::video, how_many); const auto audio_sender_ids = impl::make_ids(seed_id, nmos::types::sender, impl::ports::audio, how_many); - // Each Constraint Set in Sender Caps should contain all parameter constraints from /constraints/supported except for "meta" - // and parameter constraints that are not applicable to this Sender + const auto frame_rate = nmos::parse_rational(impl::fields::frame_rate(model.settings)); + const auto frame_width = impl::fields::frame_width(model.settings); + const auto frame_height = impl::fields::frame_height(model.settings); + const auto color_sampling = impl::fields::color_sampling(model.settings); + const auto interlace_mode = impl::get_interlace_mode(model.settings); + const auto colorspace = impl::fields::colorspace(model.settings); + const auto transfer_characteristic = impl::fields::transfer_characteristic(model.settings); + const auto component_depth = impl::fields::component_depth(model.settings); + const auto video_type = impl::fields::video_type(model.settings); + const auto channel_count = impl::fields::channel_count(model.settings); + + // Each Constraint Set in Sender Caps should contain all parameter constraints from Sender's /constraints/supported auto video_sender_capabilities = value_of({ value_of({ - { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, - { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25, nmos::rates::rate29_97 }) }, - { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, - { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, - { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ sdp::samplings::YCbCr_4_2_2.name }) }, - { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_bff.name, nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, - { nmos::caps::format::colorspace, nmos::make_caps_string_constraint({ sdp::colorimetries::BT2020.name, sdp::colorimetries::BT709.name }) }, - { nmos::caps::format::transfer_characteristic, nmos::make_caps_string_constraint({ sdp::transfer_characteristic_systems::SDR.name }) }, - { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({}, 8, 12) }, - { nmos::caps::transport::st2110_21_sender_type, nmos::make_caps_string_constraint({ sdp::type_parameters::type_N.name }) } - }), - value_of({ - { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, - { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25, nmos::rates::rate29_97 }) }, - { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 3840 }) }, - { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 2160 }) }, - { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ sdp::samplings::YCbCr_4_2_2.name }) }, - { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_bff.name, nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, - { nmos::caps::format::colorspace, nmos::make_caps_string_constraint({ sdp::colorimetries::BT2020.name, sdp::colorimetries::BT709.name }) }, - { nmos::caps::format::transfer_characteristic, nmos::make_caps_string_constraint({ sdp::transfer_characteristic_systems::SDR.name }) }, - { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({}, 8, 12) }, + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ video_type }) }, + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ frame_rate }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ frame_width }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ frame_height }) }, + { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ color_sampling }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ interlace_mode.name }) }, + { nmos::caps::format::colorspace, nmos::make_caps_string_constraint({ colorspace }) }, + { nmos::caps::format::transfer_characteristic, nmos::make_caps_string_constraint({ transfer_characteristic }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({ component_depth }) }, { nmos::caps::transport::st2110_21_sender_type, nmos::make_caps_string_constraint({ sdp::type_parameters::type_N.name }) } }) }); auto audio_sender_capabilities = value_of({ value_of({ - { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::audio_L(8).name, nmos::media_types::audio_L(16).name, nmos::media_types::audio_L(20).name, nmos::media_types::audio_L(24).name }) }, - { nmos::caps::format::channel_count, nmos::make_caps_integer_constraint({ (int)impl::channels_repeat.size() }) }, + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::audio_L(24).name }) }, + { nmos::caps::format::channel_count, nmos::make_caps_integer_constraint({ channel_count }) }, { nmos::caps::format::sample_rate, nmos::make_caps_rational_constraint({ nmos::rational{48000, 1} }) }, - { nmos::caps::format::sample_depth, nmos::make_caps_integer_constraint({ 8, 16, 20, 24 }) } + { nmos::caps::format::sample_depth, nmos::make_caps_integer_constraint({ 24 }) } }) }); @@ -1531,8 +1529,11 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler if (!nmos::caps::meta::enabled(constraint_set)) continue; for (const auto& sender_caps_constraint_set : sender_capabilities.as_array()) { - if (nmos::experimental::is_constraint_subset(sender_caps_constraint_set, constraint_set, true) || // the Constraint Set makes Sender Constraint Set's constraints narrower - nmos::experimental::is_constraint_subset(constraint_set, sender_caps_constraint_set)) // the Constraint Set is wider than the Sender Constraint Set so the latter is always compliant + // the Constraint Set makes Sender Constraint Set's constraints narrower + // or + // the Constraint Set is wider than the Sender Constraint Set so the latter is always compliant + if (nmos::experimental::is_constraint_subset(sender_caps_constraint_set, constraint_set, true) || + nmos::experimental::is_constraint_subset(constraint_set, sender_caps_constraint_set)) { return; } From 9a4d99f5a18992b35a412136c00e684ba139c421 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Thu, 20 Apr 2023 21:20:39 +0400 Subject: [PATCH 061/109] Restore the elegance of make_node_implementation --- .../nmos-cpp-node/node_implementation.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index ee79d2b2a..8559a6a2a 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1732,7 +1732,11 @@ namespace impl // into the server instance for the NMOS Node. nmos::experimental::node_implementation make_node_implementation(nmos::node_model& model, slog::base_gate& gate) { - auto node_implementation = nmos::experimental::node_implementation() + const auto set_effective_edid = impl::fields::edid_support(model.settings) + ? make_node_implementation_streamcompatibility_effective_edid_setter(model.streamcompatibility_resources, gate) + : nmos::experimental::details::streamcompatibility_effective_edid_setter{}; + + return nmos::experimental::node_implementation() .on_load_server_certificates(nmos::make_load_server_certificates_handler(model.settings, gate)) .on_load_dh_param(nmos::make_load_dh_param_handler(model.settings, gate)) .on_load_ca_certificates(nmos::make_load_ca_certificates_handler(model.settings, gate)) @@ -1747,16 +1751,8 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode .on_channelmapping_activated(make_node_implementation_channelmapping_activation_handler(gate)) .on_base_edid_changed(make_node_implementation_streamcompatibility_base_edid_put_handler(gate)) .on_base_edid_deleted(make_node_implementation_streamcompatibility_base_edid_delete_handler(gate)) + .on_set_effective_edid(set_effective_edid) // may be omitted if not required .on_active_constraints_changed(make_node_implementation_streamcompatibility_active_constraints_handler(model, gate)) .on_validate_sender_resources_against_active_constraints(make_node_implementation_streamcompatibility_sender_validator()) // may be omitted if the default is sufficient .on_validate_receiver_against_transport_file(make_node_implementation_streamcompatibility_receiver_validator(gate)); - - if (impl::fields::edid_support(model.settings)) - { - node_implementation - .on_set_effective_edid(make_node_implementation_streamcompatibility_effective_edid_setter(model.streamcompatibility_resources, gate)); // may be omitted if not required - - } - - return node_implementation; } From 8e22199e85231d570c9da691a7ff7f18514b1ff3 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Thu, 20 Apr 2023 21:48:21 +0400 Subject: [PATCH 062/109] Merge Base EDID PUT and Base EDID DELETE handlers into a single one --- .../nmos-cpp-node/node_implementation.cpp | 26 +++++++++--------- Development/nmos/node_server.cpp | 2 +- Development/nmos/node_server.h | 6 ++--- Development/nmos/streamcompatibility_api.cpp | 27 ++++++++++--------- Development/nmos/streamcompatibility_api.h | 13 ++++----- 5 files changed, 34 insertions(+), 40 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 8559a6a2a..a289428cb 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1393,22 +1393,21 @@ nmos::channelmapping_activation_handler make_node_implementation_channelmapping_ // Example Stream Compatibility Management API Base EDID update callback to perform application-specific operations to apply updated Base EDID // (e.g. providing the implementation with a parsed version of Base EDID) -nmos::experimental::details::streamcompatibility_base_edid_put_handler make_node_implementation_streamcompatibility_base_edid_put_handler(slog::base_gate& gate) +nmos::experimental::details::streamcompatibility_base_edid_handler make_node_implementation_streamcompatibility_base_edid_handler(slog::base_gate& gate) { - return [&gate](const nmos::id& input_id, const utility::string_t& base_edid, bst::optional& base_edid_properties) + return [&gate](const nmos::id& input_id, const bst::optional& base_edid, web::json::value& base_edid_properties) { - base_edid_properties = bst::nullopt; + base_edid_properties = web::json::value::null(); - slog::log(gate, SLOG_FLF) << "Base EDID updated for Input " << input_id; - }; -} + if (base_edid.has_value()) + { + slog::log(gate, SLOG_FLF) << "Base EDID updated for Input " << input_id; + } + else + { + slog::log(gate, SLOG_FLF) << "Base EDID deleted for Input " << input_id; + } -// Example Stream Compatibility Management API Base EDID delete callback to perform application-specific operations on the Base EDID deletion -nmos::experimental::details::streamcompatibility_base_edid_delete_handler make_node_implementation_streamcompatibility_base_edid_delete_handler(slog::base_gate& gate) -{ - return [&gate](const nmos::id& input_id) - { - slog::log(gate, SLOG_FLF) << "Base EDID deleted for Input " << input_id; }; } @@ -1749,8 +1748,7 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode .on_connection_activated(make_node_implementation_connection_activation_handler(model, gate)) .on_validate_channelmapping_output_map(make_node_implementation_map_validator()) // may be omitted if not required .on_channelmapping_activated(make_node_implementation_channelmapping_activation_handler(gate)) - .on_base_edid_changed(make_node_implementation_streamcompatibility_base_edid_put_handler(gate)) - .on_base_edid_deleted(make_node_implementation_streamcompatibility_base_edid_delete_handler(gate)) + .on_base_edid_changed(make_node_implementation_streamcompatibility_base_edid_handler(gate)) .on_set_effective_edid(set_effective_edid) // may be omitted if not required .on_active_constraints_changed(make_node_implementation_streamcompatibility_active_constraints_handler(model, gate)) .on_validate_sender_resources_against_active_constraints(make_node_implementation_streamcompatibility_sender_validator()) // may be omitted if the default is sufficient diff --git a/Development/nmos/node_server.cpp b/Development/nmos/node_server.cpp index fea2ca501..2fbfe71f7 100644 --- a/Development/nmos/node_server.cpp +++ b/Development/nmos/node_server.cpp @@ -64,7 +64,7 @@ namespace nmos node_server.api_routers[{ {}, nmos::fields::channelmapping_port(node_model.settings) }].mount({}, nmos::make_channelmapping_api(node_model, node_implementation.validate_map, gate)); // Configure the Stream Compatibility API - node_server.api_routers[{ {}, nmos::fields::streamcompatibility_port(node_model.settings) }].mount({}, nmos::experimental::make_streamcompatibility_api(node_model, node_implementation.base_edid_changed, node_implementation.base_edid_deleted, node_implementation.set_effective_edid, node_implementation.active_constraints_changed, gate)); + node_server.api_routers[{ {}, nmos::fields::streamcompatibility_port(node_model.settings) }].mount({}, nmos::experimental::make_streamcompatibility_api(node_model, node_implementation.base_edid_changed, node_implementation.set_effective_edid, node_implementation.active_constraints_changed, gate)); auto& events_ws_api = node_server.ws_handlers[{ {}, nmos::fields::events_ws_port(node_model.settings) }]; events_ws_api.first = nmos::make_events_ws_api(node_model, events_ws_api.second, gate); diff --git a/Development/nmos/node_server.h b/Development/nmos/node_server.h index 1663f6ee5..f65240a82 100644 --- a/Development/nmos/node_server.h +++ b/Development/nmos/node_server.h @@ -61,8 +61,7 @@ namespace nmos node_implementation& on_validate_channelmapping_output_map(nmos::details::channelmapping_output_map_validator validate_map) { this->validate_map = std::move(validate_map); return *this; } node_implementation& on_channelmapping_activated(nmos::channelmapping_activation_handler channelmapping_activated) { this->channelmapping_activated = std::move(channelmapping_activated); return *this; } node_implementation& on_get_ocsp_response(nmos::ocsp_response_handler get_ocsp_response) { this->get_ocsp_response = std::move(get_ocsp_response); return *this; } - node_implementation& on_base_edid_changed(nmos::experimental::details::streamcompatibility_base_edid_put_handler base_edid_changed) { this->base_edid_changed = std::move(base_edid_changed); return *this; } - node_implementation& on_base_edid_deleted(nmos::experimental::details::streamcompatibility_base_edid_delete_handler base_edid_deleted) { this->base_edid_deleted = std::move(base_edid_deleted); return *this; } + node_implementation& on_base_edid_changed(nmos::experimental::details::streamcompatibility_base_edid_handler base_edid_changed) { this->base_edid_changed = std::move(base_edid_changed); return *this; } node_implementation& on_set_effective_edid(nmos::experimental::details::streamcompatibility_effective_edid_setter set_effective_edid) { this->set_effective_edid = std::move(set_effective_edid); return *this; } node_implementation& on_active_constraints_changed(nmos::experimental::details::streamcompatibility_active_constraints_put_handler active_constraints_changed) { this->active_constraints_changed = std::move(active_constraints_changed); return *this; } node_implementation& on_validate_sender_resources_against_active_constraints(nmos::experimental::details::streamcompatibility_sender_validator validate_sender_resources) { this->validate_sender_resources = std::move(validate_sender_resources); return *this; } @@ -97,8 +96,7 @@ namespace nmos nmos::ocsp_response_handler get_ocsp_response; - nmos::experimental::details::streamcompatibility_base_edid_put_handler base_edid_changed; - nmos::experimental::details::streamcompatibility_base_edid_delete_handler base_edid_deleted; + nmos::experimental::details::streamcompatibility_base_edid_handler base_edid_changed; nmos::experimental::details::streamcompatibility_effective_edid_setter set_effective_edid; nmos::experimental::details::streamcompatibility_active_constraints_put_handler active_constraints_changed; nmos::experimental::details::streamcompatibility_sender_validator validate_sender_resources; diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index 058699f33..f64d1597e 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -176,9 +176,9 @@ namespace nmos } } - web::http::experimental::listener::api_router make_unmounted_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_put_handler base_edid_put_handler, details::streamcompatibility_base_edid_delete_handler base_edid_delete_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate); + web::http::experimental::listener::api_router make_unmounted_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_handler base_edid_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate); - web::http::experimental::listener::api_router make_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_put_handler base_edid_put_handler, details::streamcompatibility_base_edid_delete_handler base_edid_delete_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate) + web::http::experimental::listener::api_router make_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_handler base_edid_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate) { using namespace web::http::experimental::listener::api_router_using_declarations; @@ -203,12 +203,12 @@ namespace nmos return pplx::task_from_result(true); }); - streamcompatibility_api.mount(U("/x-nmos/") + nmos::patterns::streamcompatibility_api.pattern + U("/") + nmos::patterns::version.pattern, make_unmounted_streamcompatibility_api(model, base_edid_put_handler, base_edid_delete_handler, effective_edid_setter, active_constraints_handler, gate)); + streamcompatibility_api.mount(U("/x-nmos/") + nmos::patterns::streamcompatibility_api.pattern + U("/") + nmos::patterns::version.pattern, make_unmounted_streamcompatibility_api(model, base_edid_handler, effective_edid_setter, active_constraints_handler, gate)); return streamcompatibility_api; } - web::http::experimental::listener::api_router make_unmounted_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_put_handler base_edid_put_handler, details::streamcompatibility_base_edid_delete_handler base_edid_delete_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate_) + web::http::experimental::listener::api_router make_unmounted_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_handler base_edid_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate_) { using namespace web::http::experimental::listener::api_router_using_declarations; @@ -595,7 +595,7 @@ namespace nmos return pplx::task_from_result(true); }); - streamcompatibility_api.support(U("/") + nmos::patterns::inputType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/edid/base/?"), methods::PUT, [&model, base_edid_put_handler, effective_edid_setter, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::inputType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/edid/base/?"), methods::PUT, [&model, base_edid_handler, effective_edid_setter, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) { nmos::api_gate gate(gate_, req, parameters); auto lock = model.write_lock(); @@ -613,16 +613,16 @@ namespace nmos { if (!nmos::fields::temporarily_locked(endpoint_base_edid)) { - bst::optional base_edid_properties = bst::nullopt; + web::json::value base_edid_properties = web::json::value::null(); const auto request_body = req.content_ready().get().extract_vector().get(); const utility::string_t base_edid{ request_body.begin(), request_body.end() }; slog::log(gate, SLOG_FLF) << "Base EDID update is requested for " << id_type << " with file size " << base_edid.size(); - if (base_edid_put_handler) + if (base_edid_handler) { - base_edid_put_handler(resourceId, base_edid, base_edid_properties); + base_edid_handler(resourceId, base_edid, base_edid_properties); } // Pre-check for resources existence before Base EDID modified and effective_edid_setter executed @@ -639,9 +639,9 @@ namespace nmos // Update Base EDID in streamcompatibility_resources modify_resource(resources, resourceId, [&base_edid, &base_edid_properties, &updated_timestamp](nmos::resource& input) { - if (base_edid_properties) + if (!base_edid_properties.is_null()) { - input.data[nmos::fields::base_edid_properties] = *base_edid_properties; + input.data[nmos::fields::base_edid_properties] = base_edid_properties; } input.data[nmos::fields::endpoint_base_edid] = make_streamcompatibility_edid_endpoint(base_edid); @@ -687,7 +687,7 @@ namespace nmos return pplx::task_from_result(true); }); - streamcompatibility_api.support(U("/") + nmos::patterns::inputType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/edid/base/?"), methods::DEL, [&model, base_edid_delete_handler, effective_edid_setter, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::inputType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/edid/base/?"), methods::DEL, [&model, base_edid_handler, effective_edid_setter, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) { nmos::api_gate gate(gate_, req, parameters); auto lock = model.write_lock(); @@ -707,9 +707,10 @@ namespace nmos { slog::log(gate, SLOG_FLF) << "Base EDID deletion is requested for " << id_type; - if (base_edid_delete_handler) + if (base_edid_handler) { - base_edid_delete_handler(resourceId); + web::json::value tmp; + base_edid_handler(resourceId, bst::nullopt, tmp); } // Pre-check for resources existence before Base EDID modified and effective_edid_setter executed diff --git a/Development/nmos/streamcompatibility_api.h b/Development/nmos/streamcompatibility_api.h index f49e24865..b1d944dcc 100644 --- a/Development/nmos/streamcompatibility_api.h +++ b/Development/nmos/streamcompatibility_api.h @@ -16,15 +16,12 @@ namespace nmos { namespace details { - // a streamcompatibility_base_edid_put_handler is a notification that the Base EDID for the specified IS-11 input has changed + // a streamcompatibility_base_edid_handler is a notification that the Base EDID for the specified IS-11 input has changed (PUT or DELETEd) // it can be used to perform any final validation of the specified Base EDID and set parsed Base EDID properties - // it may throw web::json::json_exception, which will be mapped to a 400 Bad Request status code with NMOS error "debug" information including the exception message + // when PUT, it may throw web::json::json_exception, which will be mapped to a 400 Bad Request status code with NMOS error "debug" information including the exception message // or std::runtime_error, which will be mapped to a 500 Internal Error status code with NMOS error "debug" information including the exception message - typedef std::function& base_edid_properties)> streamcompatibility_base_edid_put_handler; - - // a streamcompatibility_base_edid_delete_handler is a notification that the Base EDID for the specified IS-11 input has been deleted - // this callback should not throw exceptions, as the Base EDID will already have been changed and those changes will not be rolled back - typedef std::function streamcompatibility_base_edid_delete_handler; + // when DELETE, this callback should not throw exceptions, as the Base EDID will already have been changed and those changes will not be rolled back + typedef std::function& base_edid, web::json::value& base_edid_properties)> streamcompatibility_base_edid_handler; // a streamcompatibility_active_constraints_put_handler is a notification that the Active Constraints for the specified IS-11 sender has changed // it can be used to perform any final validation of the specified Active Constraints @@ -40,7 +37,7 @@ namespace nmos typedef std::function& effective_edid, bst::optional& effective_edid_properties)> streamcompatibility_effective_edid_setter; } - web::http::experimental::listener::api_router make_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_put_handler base_edid_put_handler, details::streamcompatibility_base_edid_delete_handler base_edid_delete_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate); + web::http::experimental::listener::api_router make_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_handler base_edid_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate); } } From 0195a9e1d6260fb04646a9d40f9bb81e57d2bf17 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Thu, 20 Apr 2023 22:03:33 +0400 Subject: [PATCH 063/109] Remove has_value() --- Development/nmos-cpp-node/node_implementation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index a289428cb..a33f97c05 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1399,7 +1399,7 @@ nmos::experimental::details::streamcompatibility_base_edid_handler make_node_imp { base_edid_properties = web::json::value::null(); - if (base_edid.has_value()) + if (base_edid) { slog::log(gate, SLOG_FLF) << "Base EDID updated for Input " << input_id; } From 389bcd226d26542599df0b509a45e4449a851fa5 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 21 Apr 2023 02:57:51 +0400 Subject: [PATCH 064/109] streamcompatibility_active_constraints_put_handler: take streamcompatiblity_sender instead of sender_id --- Development/nmos-cpp-node/node_implementation.cpp | 3 ++- Development/nmos/streamcompatibility_api.cpp | 10 +++++----- Development/nmos/streamcompatibility_api.h | 3 ++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index a33f97c05..808a0c8b5 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1515,8 +1515,9 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler }) }); - return [&gate, video_sender_capabilities, audio_sender_capabilities, video_sender_ids, audio_sender_ids](const nmos::id& sender_id, const web::json::value& active_constraints) + return [&gate, video_sender_capabilities, audio_sender_capabilities, video_sender_ids, audio_sender_ids](const nmos::resource& streamcompatibility_sender, const web::json::value& active_constraints) { + const auto sender_id = streamcompatibility_sender.id; const bool video_found = video_sender_ids.end() != boost::range::find(video_sender_ids, sender_id); const bool audio_found = audio_sender_ids.end() != boost::range::find(audio_sender_ids, sender_id); diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index f64d1597e..0b6ca5ff8 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -784,14 +784,14 @@ namespace nmos const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); const std::pair id_type{ resourceId, nmos::types::sender }; - auto resource = find_resource(resources, id_type); - if (resources.end() != resource) + auto streamcompatibility_sender = find_resource(resources, id_type); + if (resources.end() != streamcompatibility_sender) { - auto& endpoint_active_constraints = nmos::fields::endpoint_active_constraints(resource->data); + auto& endpoint_active_constraints = nmos::fields::endpoint_active_constraints(streamcompatibility_sender->data); if (!nmos::fields::temporarily_locked(endpoint_active_constraints)) { - const auto supported_param_constraints = boost::copy_range>(nmos::fields::parameter_constraints(nmos::fields::supported_param_constraints(resource->data)) | boost::adaptors::transformed([](const web::json::value& param_constraint) + const auto supported_param_constraints = boost::copy_range>(nmos::fields::parameter_constraints(nmos::fields::supported_param_constraints(streamcompatibility_sender->data)) | boost::adaptors::transformed([](const web::json::value& param_constraint) { return std::unordered_set::value_type{ param_constraint.as_string() }; })); @@ -808,7 +808,7 @@ namespace nmos { try { - active_constraints_handler(resourceId, data); + active_constraints_handler(*streamcompatibility_sender, data); } catch(const std::logic_error& e) { diff --git a/Development/nmos/streamcompatibility_api.h b/Development/nmos/streamcompatibility_api.h index b1d944dcc..5544da5d2 100644 --- a/Development/nmos/streamcompatibility_api.h +++ b/Development/nmos/streamcompatibility_api.h @@ -11,6 +11,7 @@ namespace nmos { struct node_model; + struct resource; namespace experimental { @@ -28,7 +29,7 @@ namespace nmos // it may throw web::json::json_exception, which will be mapped to a 400 Bad Request status code with NMOS error "debug" information including the exception message // or std::logic_error, which will be mapped to a 422 Unprocessible Entity status code with NMOS error "debug" information including the exception message // or std::runtime_error, which will be mapped to a 500 Internal Error status code with NMOS error "debug" information including the exception message - typedef std::function streamcompatibility_active_constraints_put_handler; + typedef std::function streamcompatibility_active_constraints_put_handler; // a streamcompatibility_effective_edid_setter updates the specified Effective EDID for the specified IS-11 input // effective EDID of the input is updated when either Active Constraints of a Sender associated with this input are updated From 1d1f8780ed346221d5c8fc20ee09082217382eb6 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sat, 22 Apr 2023 01:06:44 +0400 Subject: [PATCH 065/109] Add get_constraint_set_intersection for streamcompatibility_active_constraints_put_handler --- .../nmos-cpp-node/node_implementation.cpp | 6 +- Development/nmos/constraints.cpp | 223 +++++++++++++++--- Development/nmos/constraints.h | 5 +- Development/nmos/test/constraints_test.cpp | 148 +++++++++++- 4 files changed, 344 insertions(+), 38 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 808a0c8b5..aa967e312 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1529,11 +1529,7 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler if (!nmos::caps::meta::enabled(constraint_set)) continue; for (const auto& sender_caps_constraint_set : sender_capabilities.as_array()) { - // the Constraint Set makes Sender Constraint Set's constraints narrower - // or - // the Constraint Set is wider than the Sender Constraint Set so the latter is always compliant - if (nmos::experimental::is_constraint_subset(sender_caps_constraint_set, constraint_set, true) || - nmos::experimental::is_constraint_subset(constraint_set, sender_caps_constraint_set)) + if (!nmos::experimental::get_constraint_set_intersection(sender_caps_constraint_set, constraint_set, true).is_null()) { return; } diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index b90bc769e..8609cfb81 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -1,7 +1,12 @@ #include "nmos/constraints.h" +#include #include #include +#include +#include +#include "cpprest/basic_utils.h" // for utility::us2s +#include "cpprest/json_utils.h" #include "nmos/capabilities.h" #include "nmos/json_fields.h" #include "nmos/rational.h" @@ -10,6 +15,189 @@ namespace nmos { namespace experimental { + bool constraint_comp(const web::json::value& lhs, const web::json::value& rhs) + { + if (nmos::is_rational(lhs) && nmos::is_rational(rhs)) + { + if (nmos::parse_rational(lhs) < nmos::parse_rational(rhs)) + { + return true; + } + } + else if (lhs.is_integer() && rhs.is_integer()) + { + if (lhs.as_number().to_int64() < rhs.as_number().to_int64()) + { + return true; + } + } + else if (lhs.is_double() && rhs.is_double()) + { + if (lhs.as_double() < rhs.as_double()) + { + return true; + } + } + else if (lhs.is_string() && rhs.is_string()) + { + if (lhs.as_string() < rhs.as_string()) + { + return true; + } + } + return false; + } + + web::json::value get_intersection(const web::json::array& lhs, const web::json::array& rhs) + { + std::vector v(std::min(lhs.size(), rhs.size())); + + // "Arrays are used for ordered elements." + // https://json-schema.org/understanding-json-schema/reference/array.html + const auto it = std::set_intersection(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), v.begin(), constraint_comp); + v.resize(it - v.begin()); + + return web::json::value_from_elements(v); + } + + web::json::value get_intersection(const web::json::array& enumeration, const web::json::value& constraint) + { + return web::json::value_from_elements(enumeration | boost::adaptors::filtered([&constraint](const web::json::value& enum_value) + { + return match_constraint(enum_value, constraint); + })); + } + + web::json::value get_constraint_intersection(const web::json::value& lhs, const web::json::value& rhs) + { + web::json::value result; + + // "enum" + if (lhs.has_field(nmos::fields::constraint_enum) && rhs.has_field(nmos::fields::constraint_enum)) + { + result[nmos::fields::constraint_enum] = get_intersection(nmos::fields::constraint_enum(lhs).as_array(), nmos::fields::constraint_enum(rhs).as_array()); + } + else if (lhs.has_field(nmos::fields::constraint_enum)) + { + result[nmos::fields::constraint_enum] = nmos::fields::constraint_enum(lhs); + } + else if (rhs.has_field(nmos::fields::constraint_enum)) + { + result[nmos::fields::constraint_enum] = nmos::fields::constraint_enum(rhs); + } + + // "minimum" + if (lhs.has_field(nmos::fields::constraint_minimum) && rhs.has_field(nmos::fields::constraint_minimum)) + { + result[nmos::fields::constraint_minimum] = std::max(nmos::fields::constraint_minimum(lhs), nmos::fields::constraint_minimum(rhs), constraint_comp); + } + else if (lhs.has_field(nmos::fields::constraint_minimum)) + { + result[nmos::fields::constraint_minimum] = nmos::fields::constraint_minimum(lhs); + } + else if (rhs.has_field(nmos::fields::constraint_minimum)) + { + result[nmos::fields::constraint_minimum] = nmos::fields::constraint_minimum(rhs); + } + + // "maximum" + if (lhs.has_field(nmos::fields::constraint_maximum) && rhs.has_field(nmos::fields::constraint_maximum)) + { + result[nmos::fields::constraint_maximum] = std::min(nmos::fields::constraint_maximum(lhs), nmos::fields::constraint_maximum(rhs), constraint_comp); + } + else if (lhs.has_field(nmos::fields::constraint_maximum)) + { + result[nmos::fields::constraint_maximum] = nmos::fields::constraint_maximum(lhs); + } + else if (rhs.has_field(nmos::fields::constraint_maximum)) + { + result[nmos::fields::constraint_maximum] = nmos::fields::constraint_maximum(rhs); + } + + // "min" > "max" + if (result.has_field(nmos::fields::constraint_minimum) && result.has_field(nmos::fields::constraint_maximum)) + { + if (!constraint_comp(nmos::fields::constraint_minimum(result), nmos::fields::constraint_maximum(result))) + { + return web::json::value::null(); + } + } + + // "enum" matches "min"/"max" + if (result.has_field(nmos::fields::constraint_enum) && (result.has_field(nmos::fields::constraint_minimum) || result.has_field(nmos::fields::constraint_maximum))) + { + result[nmos::fields::constraint_enum] = get_intersection(nmos::fields::constraint_enum(result).as_array(), result); + for (const auto& keyword : { nmos::fields::constraint_minimum, nmos::fields::constraint_maximum }) + { + if (result.has_field(keyword)) + { + result.erase(keyword); + } + } + } + + // no "min"/"max" and "enum" is empty + if (result.has_field(nmos::fields::constraint_enum) && (!result.has_field(nmos::fields::constraint_minimum) && !result.has_field(nmos::fields::constraint_maximum))) + { + if (0 == nmos::fields::constraint_enum(result).as_array().size()) + { + return web::json::value::null(); + } + } + + return result; + } + + web::json::value get_constraint_set_intersection(const web::json::value& lhs, const web::json::value& rhs, bool merge_left_to_right) + { + using web::json::value; + using web::json::value_from_elements; + + const auto& lhs_param_constraints = lhs.as_object(); + auto rhs_param_constraints = rhs.as_object(); + + if (merge_left_to_right) + { + rhs_param_constraints = lhs_param_constraints; + std::for_each(rhs.as_object().begin(), rhs.as_object().end(), [&rhs_param_constraints](const std::pair& rhs_constraint) + { + rhs_param_constraints[rhs_constraint.first] = rhs_constraint.second; + }); + } + + const auto common_param_constraints = get_intersection( + value_from_elements(lhs_param_constraints | boost::adaptors::filtered([](const std::pair& constraint) { return !boost::algorithm::starts_with(constraint.first, U("urn:x-nmos:cap:meta:")); }) | boost::adaptors::transformed([](const std::pair& constraint) { return constraint.first; })).as_array(), + value_from_elements(rhs_param_constraints | boost::adaptors::filtered([](const std::pair& constraint) { return !boost::algorithm::starts_with(constraint.first, U("urn:x-nmos:cap:meta:")); }) | boost::adaptors::transformed([](const std::pair& constraint) { return constraint.first; })).as_array() + ).as_array(); + + if (0 == common_param_constraints.size()) + { + return value::null(); + } + + try + { + value result; + + std::for_each(common_param_constraints.begin(), common_param_constraints.end(), [&result, &lhs_param_constraints, &rhs_param_constraints](const web::json::value& constraint_name_) + { + const auto& constraint_name = constraint_name_.as_string(); + const web::json::value intersection = get_constraint_intersection(lhs_param_constraints.at(constraint_name), rhs_param_constraints.at(constraint_name)); + if (intersection.is_null()) + { + throw std::runtime_error(utility::us2s(constraint_name) + " gives empty intersection"); + } + result[constraint_name] = intersection; + }); + + return result; + } + catch (const std::runtime_error& e) + { + return value::null(); + } + } + // Constraint B is a subconstraint of Constraint A if: // 1. Constraint B has enum keyword when Constraint A has it and enumerated values of Constraint B are a subset of enumerated values of Constraint A // 2. Constraint B has enum or minimum keyword when Constraint A has minimum keyword and allowed values of Constraint B are greater than minimum value of Constraint A @@ -36,45 +224,20 @@ namespace nmos const auto& constraint_min = nmos::fields::constraint_minimum(constraint); const auto& subconstraint_min = nmos::fields::constraint_minimum(subconstraint); - if (nmos::is_rational(constraint_min)) - { - if (nmos::parse_rational(constraint_min) > nmos::parse_rational(subconstraint_min)) - { - return false; - } - } - else if (constraint_min.is_integer() && subconstraint_min.is_integer()) + if (constraint_min != subconstraint_min) { - if (constraint_min.as_number().to_int64() > subconstraint_min.as_number().to_int64()) + if (!constraint_comp(constraint_min, subconstraint_min)) { return false; } } - else if (constraint_min.as_double() > subconstraint_min.as_double()) - { - return false; - } } if (constraint.has_field(nmos::fields::constraint_maximum) && subconstraint.has_field(nmos::fields::constraint_maximum)) { const auto& constraint_max = nmos::fields::constraint_maximum(constraint); const auto& subconstraint_max = nmos::fields::constraint_maximum(subconstraint); - if (nmos::is_rational(constraint_max)) - { - if (nmos::parse_rational(constraint_max) < nmos::parse_rational(subconstraint_max)) - { - return false; - } - } - else if (constraint_max.is_integer() && subconstraint_max.is_integer()) - { - if (constraint_max.as_number().to_int64() < subconstraint_max.as_number().to_int64()) - { - return false; - } - } - else if (constraint_max.as_double() < subconstraint_max.as_double()) + if (constraint_comp(constraint_max, subconstraint_max)) { return false; } @@ -93,14 +256,14 @@ namespace nmos // Constraint Set B is a subset of Constraint Set A if all Parameter Constraints of Constraint Set A are present in Constraint Set B, and for each Parameter Constraint // that is present in both, the Parameter Constraint of Constraint Set B is a subconstraint of the Parameter Constraint of Constraint Set A. - bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset, bool merge) + bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset, bool merge_left_to_right) { using web::json::value; const auto& param_constraints_set = constraint_set.as_object(); auto param_constraints_subset = constraint_subset.as_object(); - if (merge) + if (merge_left_to_right) { param_constraints_subset = param_constraints_set; std::for_each(constraint_subset.as_object().begin(), constraint_subset.as_object().end(), [¶m_constraints_subset](const std::pair& subconstraint) diff --git a/Development/nmos/constraints.h b/Development/nmos/constraints.h index 79667c032..275eb2adc 100644 --- a/Development/nmos/constraints.h +++ b/Development/nmos/constraints.h @@ -13,8 +13,11 @@ namespace nmos { namespace experimental { + web::json::value get_constraint_intersection(const web::json::value& lhs, const web::json::value& rhs); + web::json::value get_constraint_set_intersection(const web::json::value& lhs, const web::json::value& rhs, bool merge_left_to_right = false); + bool is_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint); - bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset, bool merge = false); + bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset, bool merge_left_to_right = false); } } diff --git a/Development/nmos/test/constraints_test.cpp b/Development/nmos/test/constraints_test.cpp index b9a215139..77921068e 100644 --- a/Development/nmos/test/constraints_test.cpp +++ b/Development/nmos/test/constraints_test.cpp @@ -136,9 +136,153 @@ BST_TEST_CASE(testRationalMinMaxSubconstraints) using web::json::value_of; using nmos::experimental::is_subconstraint; - auto wideRange = nmos::make_caps_rational_constraint({}, nmos::rates::rate25, nmos::rates::rate30); - auto narrowRange = nmos::make_caps_rational_constraint({}, nmos::rates::rate25, nmos::rates::rate29_97); + const auto wideRange = nmos::make_caps_rational_constraint({}, nmos::rates::rate25, nmos::rates::rate30); + const auto narrowRange = nmos::make_caps_rational_constraint({}, nmos::rates::rate25, nmos::rates::rate29_97); BST_REQUIRE(is_subconstraint(wideRange, narrowRange)); } } + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testEnumConstraintIntersection) +{ + { + using web::json::value_of; + using nmos::experimental::get_constraint_intersection; + + const auto a = nmos::make_caps_integer_constraint({ 8, 10 }); + const auto b = nmos::make_caps_integer_constraint({ 10, 12 }); + + const auto c = nmos::make_caps_integer_constraint({ 10 }); + + BST_REQUIRE_EQUAL(get_constraint_intersection(a, b), c); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testNoEnumConstraintIntersection) +{ + { + using web::json::value_of; + using nmos::experimental::get_constraint_intersection; + + const auto a = nmos::make_caps_integer_constraint({}, 8, 12); + const auto b = nmos::make_caps_integer_constraint({}, 8, 12); + + const auto c = nmos::make_caps_integer_constraint({}, 8, 12); + + BST_REQUIRE_EQUAL(get_constraint_intersection(a, b), c); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testEnumRationalConstraintIntersection) +{ + { + using web::json::value_of; + using nmos::experimental::get_constraint_intersection; + + const auto a = nmos::make_caps_rational_constraint({ nmos::rates::rate25, nmos::rates::rate29_97, nmos::rates::rate60 }); + const auto b = nmos::make_caps_rational_constraint({ nmos::rates::rate60 }); + + const auto c = nmos::make_caps_rational_constraint({ nmos::rates::rate60 }); + + BST_REQUIRE_EQUAL(b, c); + BST_REQUIRE_EQUAL(get_constraint_intersection(a, b), c); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testConstraintSetIntersection) +{ + { + using web::json::value_of; + using nmos::experimental::get_constraint_set_intersection; + + const auto a = value_of({ + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25, nmos::rates::rate29_97 }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, + { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ sdp::samplings::YCbCr_4_2_2.name }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_bff.name, nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, + { nmos::caps::format::colorspace, nmos::make_caps_string_constraint({ sdp::colorimetries::BT2020.name, sdp::colorimetries::BT709.name }) }, + { nmos::caps::format::transfer_characteristic, nmos::make_caps_string_constraint({ sdp::transfer_characteristic_systems::SDR.name }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({}, 8, 12) }, + { nmos::caps::transport::st2110_21_sender_type, nmos::make_caps_string_constraint({ sdp::type_parameters::type_N.name }) } + }); + + const auto b = value_of({ + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25 }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, + { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ sdp::samplings::YCbCr_4_2_2.name }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, + { nmos::caps::format::colorspace, nmos::make_caps_string_constraint({ sdp::colorimetries::BT2020.name, sdp::colorimetries::BT709.name }) }, + { nmos::caps::format::transfer_characteristic, nmos::make_caps_string_constraint({ sdp::transfer_characteristic_systems::SDR.name }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({ 10 }) }, + { nmos::caps::transport::st2110_21_sender_type, nmos::make_caps_string_constraint({ sdp::type_parameters::type_N.name }) } + }); + + const auto c = value_of({ + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25 }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, + { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ sdp::samplings::YCbCr_4_2_2.name }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, + { nmos::caps::format::colorspace, nmos::make_caps_string_constraint({ sdp::colorimetries::BT2020.name, sdp::colorimetries::BT709.name }) }, + { nmos::caps::format::transfer_characteristic, nmos::make_caps_string_constraint({ sdp::transfer_characteristic_systems::SDR.name }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({ 10 }) }, + { nmos::caps::transport::st2110_21_sender_type, nmos::make_caps_string_constraint({ sdp::type_parameters::type_N.name }) } + }); + + BST_REQUIRE_EQUAL(get_constraint_set_intersection(a, b), c); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testConstraintSetIntersectionMerge) +{ + { + using web::json::value_of; + using nmos::experimental::get_constraint_set_intersection; + + const auto a = value_of({ + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25, nmos::rates::rate29_97 }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, + { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ sdp::samplings::YCbCr_4_2_2.name }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_bff.name, nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, + { nmos::caps::format::colorspace, nmos::make_caps_string_constraint({ sdp::colorimetries::BT2020.name, sdp::colorimetries::BT709.name }) }, + { nmos::caps::format::transfer_characteristic, nmos::make_caps_string_constraint({ sdp::transfer_characteristic_systems::SDR.name }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({}, 8, 12) }, + { nmos::caps::transport::st2110_21_sender_type, nmos::make_caps_string_constraint({ sdp::type_parameters::type_N.name }) } + }); + + const auto b = value_of({ + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25 }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({ 10 }) } + }); + + const auto c = value_of({ + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25 }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, + { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ sdp::samplings::YCbCr_4_2_2.name }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, + { nmos::caps::format::colorspace, nmos::make_caps_string_constraint({ sdp::colorimetries::BT2020.name, sdp::colorimetries::BT709.name }) }, + { nmos::caps::format::transfer_characteristic, nmos::make_caps_string_constraint({ sdp::transfer_characteristic_systems::SDR.name }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({ 10 }) }, + { nmos::caps::transport::st2110_21_sender_type, nmos::make_caps_string_constraint({ sdp::type_parameters::type_N.name }) } + }); + + BST_REQUIRE_EQUAL(get_constraint_set_intersection(a, b, true), c); + } +} From 56d955547a738d06e880a84d84edf0271ce6e522 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sun, 23 Apr 2023 01:32:27 +0400 Subject: [PATCH 066/109] Store intersection of Sender Caps and Active Constraints in streamcompatibility_sender --- .../nmos-cpp-node/node_implementation.cpp | 56 +++++++++++++++---- Development/nmos/json_fields.h | 1 + Development/nmos/streamcompatibility_api.cpp | 23 ++++---- Development/nmos/streamcompatibility_api.h | 2 +- .../nmos/streamcompatibility_resources.cpp | 1 + 5 files changed, 60 insertions(+), 23 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index aa967e312..efa0ccd18 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1440,14 +1440,28 @@ nmos::experimental::details::streamcompatibility_effective_edid_setter make_node bst::optional base_edid = bst::nullopt; const std::pair id_type{ input_id, nmos::types::input }; - auto resource = find_resource(streamcompatibility_resources, id_type); - if (streamcompatibility_resources.end() != resource) + const auto streamcompatibility_input = find_resource(streamcompatibility_resources, id_type); + if (streamcompatibility_resources.end() != streamcompatibility_input) { - auto& edid_endpoint = nmos::fields::endpoint_base_edid(resource->data); + for (const auto& sender_id : nmos::fields::senders(streamcompatibility_input->data)) + { + const std::pair sender_id_type{ sender_id.as_string(), nmos::types::sender }; + const auto streamcompatibility_sender = find_resource(streamcompatibility_resources, sender_id_type); + if (streamcompatibility_resources.end() != streamcompatibility_sender) + { + slog::log(gate, SLOG_FLF) << "Intersection of Sender Caps and Active Constraints for Sender " << sender_id << " is"; + for (const auto& item : nmos::fields::intersection_of_caps_and_constraints(streamcompatibility_sender->data).as_array()) + { + slog::log(gate, SLOG_FLF) << item; + } + } + } + + const auto& edid_endpoint = nmos::fields::endpoint_base_edid(streamcompatibility_input->data); if (!edid_endpoint.is_null()) { - auto& edid_binary = nmos::fields::edid_binary(edid_endpoint); + const auto& edid_binary = nmos::fields::edid_binary(edid_endpoint); if (!edid_binary.is_null()) { @@ -1455,6 +1469,11 @@ nmos::experimental::details::streamcompatibility_effective_edid_setter make_node } } } + else + { + slog::log(gate, SLOG_FLF) << "Requested " << id_type << " not found"; + return; + } if (base_edid) { @@ -1515,28 +1534,41 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler }) }); - return [&gate, video_sender_capabilities, audio_sender_capabilities, video_sender_ids, audio_sender_ids](const nmos::resource& streamcompatibility_sender, const web::json::value& active_constraints) + return [&gate, video_sender_capabilities, audio_sender_capabilities, video_sender_ids, audio_sender_ids](const nmos::resource& streamcompatibility_sender, const web::json::value& active_constraints, web::json::value& intersection) { - const auto sender_id = streamcompatibility_sender.id; + const auto& sender_id = streamcompatibility_sender.id; const bool video_found = video_sender_ids.end() != boost::range::find(video_sender_ids, sender_id); const bool audio_found = audio_sender_ids.end() != boost::range::find(audio_sender_ids, sender_id); - const auto& sender_capabilities = video_found ? video_sender_capabilities : audio_found ? audio_sender_capabilities : throw std::logic_error("No Sender Capabilities found"); - + const auto& sender_capabilities_ = video_found ? video_sender_capabilities : audio_found ? audio_sender_capabilities : throw std::logic_error("No Sender Capabilities found"); + const auto& sender_capabilities = sender_capabilities_.as_array(); const auto& constraint_sets = nmos::fields::constraint_sets(active_constraints).as_array(); + + std::vector v(constraint_sets.size() * sender_capabilities.size()); + auto iter = v.begin(); + for (const auto& constraint_set : constraint_sets) { if (!nmos::caps::meta::enabled(constraint_set)) continue; - for (const auto& sender_caps_constraint_set : sender_capabilities.as_array()) + for (const auto& sender_caps_constraint_set : sender_capabilities) { - if (!nmos::experimental::get_constraint_set_intersection(sender_caps_constraint_set, constraint_set, true).is_null()) + const auto intersection = nmos::experimental::get_constraint_set_intersection(sender_caps_constraint_set, constraint_set, true); + if (!intersection.is_null()) { - return; + *iter++ = std::move(intersection); } } } + + v.resize(iter - v.begin()); + if (!v.empty()) + { + intersection = value_from_elements(v); + return; + } + slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " doesn't support proposed Active Constraints"; - throw std::logic_error("sender capabilities are " + utility::us2s(sender_capabilities.serialize())); + throw std::logic_error("sender capabilities are " + utility::us2s(sender_capabilities_.serialize())); }; } diff --git a/Development/nmos/json_fields.h b/Development/nmos/json_fields.h index a01f3babc..9c523960d 100644 --- a/Development/nmos/json_fields.h +++ b/Development/nmos/json_fields.h @@ -249,6 +249,7 @@ namespace nmos const web::json::field_as_value active_constraint_sets{ U("active_constraint_sets") }; // object const web::json::field_as_value supported_param_constraints{ U("supported_param_constraints") }; // object const web::json::field_as_array parameter_constraints{ U("parameter_constraints") }; + const web::json::field_as_value intersection_of_caps_and_constraints{ U("intersection_of_caps_and_constraints") }; // array // for status const web::json::field_as_value status{ U("status") }; // object diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index 0b6ca5ff8..4686ee5b2 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -120,7 +120,7 @@ namespace nmos } // it's expected that write lock is already catched for the model and IS-11 sender exists - void set_active_constraints(nmos::node_model& model, const nmos::id& sender_id, const web::json::value& constraints, const streamcompatibility_effective_edid_setter& effective_edid_setter) + void set_active_constraints(nmos::node_model& model, const nmos::id& sender_id, const web::json::value& constraints, const web::json::value& intersection, const streamcompatibility_effective_edid_setter& effective_edid_setter) { const std::pair sender_id_type{ sender_id, nmos::types::sender }; auto resource = find_resource(model.streamcompatibility_resources, sender_id_type); @@ -154,8 +154,9 @@ namespace nmos utility::string_t updated_timestamp; // Update Active Constraints in streamcompatibility_resources - modify_resource(model.streamcompatibility_resources, sender_id, [&constraints, &updated_timestamp](nmos::resource& sender) + modify_resource(model.streamcompatibility_resources, sender_id, [&constraints, &intersection, &updated_timestamp](nmos::resource& sender) { + sender.data[nmos::fields::intersection_of_caps_and_constraints] = intersection; sender.data[nmos::fields::endpoint_active_constraints] = make_streamcompatibility_active_constraints_endpoint(constraints); updated_timestamp = nmos::make_version(); @@ -801,6 +802,7 @@ namespace nmos slog::log(gate, SLOG_FLF) << "Active Constraints update is requested for " << id_type; bool can_adhere = true; + web::json::value intersection = web::json::value::array(); if (!web::json::empty(nmos::fields::constraint_sets(data).as_array())) { @@ -808,7 +810,7 @@ namespace nmos { try { - active_constraints_handler(*streamcompatibility_sender, data); + active_constraints_handler(*streamcompatibility_sender, data, intersection); } catch(const std::logic_error& e) { @@ -822,7 +824,7 @@ namespace nmos if (can_adhere) { - details::set_active_constraints(model, resourceId, nmos::fields::constraint_sets(data), effective_edid_setter); + details::set_active_constraints(model, resourceId, nmos::fields::constraint_sets(data), intersection, effective_edid_setter); set_reply(res, status_codes::OK, data); } } @@ -859,18 +861,19 @@ namespace nmos const string_t resourceId = parameters.at(nmos::patterns::resourceId.name); const std::pair id_type{ resourceId, nmos::types::sender }; - auto resource = find_resource(resources, id_type); - if (resources.end() != resource) + const auto streamcompatibility_sender = find_resource(resources, id_type); + + if (resources.end() != streamcompatibility_sender) { - auto& endpoint_active_constraints = nmos::fields::endpoint_active_constraints(resource->data); + const auto& endpoint_active_constraints = nmos::fields::endpoint_active_constraints(streamcompatibility_sender->data); if (!nmos::fields::temporarily_locked(endpoint_active_constraints)) { slog::log(gate, SLOG_FLF) << "Active Constraints deletion is requested for " << id_type; - auto data = web::json::value::array(); - details::set_active_constraints(model, resourceId, data, effective_edid_setter); - set_reply(res, status_codes::OK, nmos::fields::active_constraint_sets(nmos::fields::endpoint_active_constraints(resource->data))); + const auto empty_array = web::json::value::array(); + details::set_active_constraints(model, resourceId, empty_array, empty_array, effective_edid_setter); + set_reply(res, status_codes::OK, nmos::fields::active_constraint_sets(nmos::fields::endpoint_active_constraints(streamcompatibility_sender->data))); } else { diff --git a/Development/nmos/streamcompatibility_api.h b/Development/nmos/streamcompatibility_api.h index 5544da5d2..702b7d3f2 100644 --- a/Development/nmos/streamcompatibility_api.h +++ b/Development/nmos/streamcompatibility_api.h @@ -29,7 +29,7 @@ namespace nmos // it may throw web::json::json_exception, which will be mapped to a 400 Bad Request status code with NMOS error "debug" information including the exception message // or std::logic_error, which will be mapped to a 422 Unprocessible Entity status code with NMOS error "debug" information including the exception message // or std::runtime_error, which will be mapped to a 500 Internal Error status code with NMOS error "debug" information including the exception message - typedef std::function streamcompatibility_active_constraints_put_handler; + typedef std::function streamcompatibility_active_constraints_put_handler; // a streamcompatibility_effective_edid_setter updates the specified Effective EDID for the specified IS-11 input // effective EDID of the input is updated when either Active Constraints of a Sender associated with this input are updated diff --git a/Development/nmos/streamcompatibility_resources.cpp b/Development/nmos/streamcompatibility_resources.cpp index 22cf00998..d54ab253c 100644 --- a/Development/nmos/streamcompatibility_resources.cpp +++ b/Development/nmos/streamcompatibility_resources.cpp @@ -41,6 +41,7 @@ namespace nmos { nmos::fields::inputs, value_from_elements(inputs) }, { nmos::fields::supported_param_constraints, supported_param_constraints }, { nmos::fields::status, value_of({ { nmos::fields::state, nmos::sender_states::unconstrained.name } }) }, + { nmos::fields::intersection_of_caps_and_constraints, value::array() } }); return{ is11_versions::v1_0, types::sender, std::move(data), id, false }; From abd69b131126a22328161a0efe10fbdfb5720d34 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sun, 23 Apr 2023 02:08:29 +0400 Subject: [PATCH 067/109] Fix print-outs --- Development/nmos-cpp-node/node_implementation.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index efa0ccd18..d09f1a16c 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1443,17 +1443,16 @@ nmos::experimental::details::streamcompatibility_effective_edid_setter make_node const auto streamcompatibility_input = find_resource(streamcompatibility_resources, id_type); if (streamcompatibility_resources.end() != streamcompatibility_input) { - for (const auto& sender_id : nmos::fields::senders(streamcompatibility_input->data)) + for (const auto& sender_id_ : nmos::fields::senders(streamcompatibility_input->data)) { - const std::pair sender_id_type{ sender_id.as_string(), nmos::types::sender }; + const auto sender_id = sender_id_.as_string(); + const std::pair sender_id_type{ sender_id, nmos::types::sender }; const auto streamcompatibility_sender = find_resource(streamcompatibility_resources, sender_id_type); if (streamcompatibility_resources.end() != streamcompatibility_sender) { - slog::log(gate, SLOG_FLF) << "Intersection of Sender Caps and Active Constraints for Sender " << sender_id << " is"; - for (const auto& item : nmos::fields::intersection_of_caps_and_constraints(streamcompatibility_sender->data).as_array()) - { - slog::log(gate, SLOG_FLF) << item; - } + slog::log(gate, SLOG_FLF) + << "Intersection of Sender Caps and Active Constraints for Sender " << sender_id << " is " + << utility::us2s(nmos::fields::intersection_of_caps_and_constraints(streamcompatibility_sender->data).serialize()); } } From 9bc3c9155266f72224a1f1f4026c4f0dd7c7e1a9 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Tue, 25 Apr 2023 15:22:56 +0400 Subject: [PATCH 068/109] constraints.cpp tweaks --- Development/nmos/constraints.cpp | 83 +++++++--------------- Development/nmos/constraints.h | 7 +- Development/nmos/test/constraints_test.cpp | 26 ++++++- 3 files changed, 56 insertions(+), 60 deletions(-) diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index 8609cfb81..286661fc3 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -1,7 +1,7 @@ #include "nmos/constraints.h" #include -#include +#include #include #include #include @@ -15,46 +15,30 @@ namespace nmos { namespace experimental { - bool constraint_comp(const web::json::value& lhs, const web::json::value& rhs) + namespace detail { - if (nmos::is_rational(lhs) && nmos::is_rational(rhs)) + bool constraint_value_less(const web::json::value& lhs, const web::json::value& rhs) { - if (nmos::parse_rational(lhs) < nmos::parse_rational(rhs)) + if (nmos::is_rational(lhs) && nmos::is_rational(rhs)) { - return true; - } - } - else if (lhs.is_integer() && rhs.is_integer()) - { - if (lhs.as_number().to_int64() < rhs.as_number().to_int64()) - { - return true; - } - } - else if (lhs.is_double() && rhs.is_double()) - { - if (lhs.as_double() < rhs.as_double()) - { - return true; - } - } - else if (lhs.is_string() && rhs.is_string()) - { - if (lhs.as_string() < rhs.as_string()) - { - return true; + if (nmos::parse_rational(lhs) < nmos::parse_rational(rhs)) + { + return true; + } + return false; } + return lhs < rhs; } - return false; } - web::json::value get_intersection(const web::json::array& lhs, const web::json::array& rhs) + web::json::value get_intersection(const web::json::array& lhs_, const web::json::array& rhs_) { + std::set lhs(lhs_.begin(), lhs_.end(), &detail::constraint_value_less); + std::set rhs(rhs_.begin(), rhs_.end(), &detail::constraint_value_less); + std::vector v(std::min(lhs.size(), rhs.size())); - // "Arrays are used for ordered elements." - // https://json-schema.org/understanding-json-schema/reference/array.html - const auto it = std::set_intersection(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), v.begin(), constraint_comp); + const auto it = std::set_intersection(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), v.begin(), detail::constraint_value_less); v.resize(it - v.begin()); return web::json::value_from_elements(v); @@ -89,7 +73,7 @@ namespace nmos // "minimum" if (lhs.has_field(nmos::fields::constraint_minimum) && rhs.has_field(nmos::fields::constraint_minimum)) { - result[nmos::fields::constraint_minimum] = std::max(nmos::fields::constraint_minimum(lhs), nmos::fields::constraint_minimum(rhs), constraint_comp); + result[nmos::fields::constraint_minimum] = std::max(nmos::fields::constraint_minimum(lhs), nmos::fields::constraint_minimum(rhs), detail::constraint_value_less); } else if (lhs.has_field(nmos::fields::constraint_minimum)) { @@ -103,7 +87,7 @@ namespace nmos // "maximum" if (lhs.has_field(nmos::fields::constraint_maximum) && rhs.has_field(nmos::fields::constraint_maximum)) { - result[nmos::fields::constraint_maximum] = std::min(nmos::fields::constraint_maximum(lhs), nmos::fields::constraint_maximum(rhs), constraint_comp); + result[nmos::fields::constraint_maximum] = std::min(nmos::fields::constraint_maximum(lhs), nmos::fields::constraint_maximum(rhs), detail::constraint_value_less); } else if (lhs.has_field(nmos::fields::constraint_maximum)) { @@ -117,7 +101,7 @@ namespace nmos // "min" > "max" if (result.has_field(nmos::fields::constraint_minimum) && result.has_field(nmos::fields::constraint_maximum)) { - if (!constraint_comp(nmos::fields::constraint_minimum(result), nmos::fields::constraint_maximum(result))) + if (detail::constraint_value_less(nmos::fields::constraint_maximum(result), nmos::fields::constraint_minimum(result))) { return web::json::value::null(); } @@ -136,13 +120,10 @@ namespace nmos } } - // no "min"/"max" and "enum" is empty - if (result.has_field(nmos::fields::constraint_enum) && (!result.has_field(nmos::fields::constraint_minimum) && !result.has_field(nmos::fields::constraint_maximum))) + // "enum" is empty + if (result.has_field(nmos::fields::constraint_enum) && web::json::empty(nmos::fields::constraint_enum(result))) { - if (0 == nmos::fields::constraint_enum(result).as_array().size()) - { - return web::json::value::null(); - } + return web::json::value::null(); } return result; @@ -224,12 +205,9 @@ namespace nmos const auto& constraint_min = nmos::fields::constraint_minimum(constraint); const auto& subconstraint_min = nmos::fields::constraint_minimum(subconstraint); - if (constraint_min != subconstraint_min) + if (detail::constraint_value_less(subconstraint_min, constraint_min)) { - if (!constraint_comp(constraint_min, subconstraint_min)) - { - return false; - } + return false; } } if (constraint.has_field(nmos::fields::constraint_maximum) && subconstraint.has_field(nmos::fields::constraint_maximum)) @@ -237,7 +215,7 @@ namespace nmos const auto& constraint_max = nmos::fields::constraint_maximum(constraint); const auto& subconstraint_max = nmos::fields::constraint_maximum(subconstraint); - if (constraint_comp(constraint_max, subconstraint_max)) + if (detail::constraint_value_less(constraint_max, subconstraint_max)) { return false; } @@ -256,21 +234,12 @@ namespace nmos // Constraint Set B is a subset of Constraint Set A if all Parameter Constraints of Constraint Set A are present in Constraint Set B, and for each Parameter Constraint // that is present in both, the Parameter Constraint of Constraint Set B is a subconstraint of the Parameter Constraint of Constraint Set A. - bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset, bool merge_left_to_right) + bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset) { using web::json::value; const auto& param_constraints_set = constraint_set.as_object(); - auto param_constraints_subset = constraint_subset.as_object(); - - if (merge_left_to_right) - { - param_constraints_subset = param_constraints_set; - std::for_each(constraint_subset.as_object().begin(), constraint_subset.as_object().end(), [¶m_constraints_subset](const std::pair& subconstraint) - { - param_constraints_subset[subconstraint.first] = subconstraint.second; - }); - } + const auto& param_constraints_subset = constraint_subset.as_object(); return param_constraints_set.end() == std::find_if_not(param_constraints_set.begin(), param_constraints_set.end(), [¶m_constraints_subset](const std::pair& constraint) { diff --git a/Development/nmos/constraints.h b/Development/nmos/constraints.h index 275eb2adc..6d4d61ec4 100644 --- a/Development/nmos/constraints.h +++ b/Development/nmos/constraints.h @@ -13,11 +13,16 @@ namespace nmos { namespace experimental { + namespace detail + { + bool constraint_value_less(const web::json::value& lhs, const web::json::value& rhs); + } + web::json::value get_constraint_intersection(const web::json::value& lhs, const web::json::value& rhs); web::json::value get_constraint_set_intersection(const web::json::value& lhs, const web::json::value& rhs, bool merge_left_to_right = false); bool is_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint); - bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset, bool merge_left_to_right = false); + bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset); } } diff --git a/Development/nmos/test/constraints_test.cpp b/Development/nmos/test/constraints_test.cpp index 77921068e..976e52970 100644 --- a/Development/nmos/test/constraints_test.cpp +++ b/Development/nmos/test/constraints_test.cpp @@ -7,6 +7,28 @@ #include "bst/test/test.h" +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testJsonComparator) +{ + { + using nmos::experimental::detail::constraint_value_less; + + const auto a = nmos::make_caps_rational_constraint({}, nmos::rates::rate25, nmos::rates::rate30); + const auto b = nmos::make_caps_rational_constraint({}, nmos::rates::rate25, nmos::rates::rate29_97); + + BST_REQUIRE(constraint_value_less(nmos::fields::constraint_minimum(a), nmos::fields::constraint_maximum(a))); + BST_REQUIRE(constraint_value_less(nmos::fields::constraint_minimum(a), nmos::fields::constraint_maximum(b))); + BST_REQUIRE(constraint_value_less(nmos::fields::constraint_minimum(b), nmos::fields::constraint_maximum(a))); + BST_REQUIRE(constraint_value_less(nmos::fields::constraint_minimum(b), nmos::fields::constraint_maximum(b))); + + BST_REQUIRE(!constraint_value_less(nmos::fields::constraint_minimum(a), nmos::fields::constraint_minimum(b))); + BST_REQUIRE(!constraint_value_less(nmos::fields::constraint_maximum(a), nmos::fields::constraint_maximum(b))); + + BST_REQUIRE(!constraint_value_less(nmos::fields::constraint_minimum(b), nmos::fields::constraint_minimum(a))); + BST_REQUIRE(constraint_value_less(nmos::fields::constraint_maximum(b), nmos::fields::constraint_maximum(a))); + } +} + //////////////////////////////////////////////////////////////////////////////////////////// BST_TEST_CASE(testSimpleCase) { @@ -231,7 +253,7 @@ BST_TEST_CASE(testConstraintSetIntersection) { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ sdp::samplings::YCbCr_4_2_2.name }) }, - { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_psf.name, nmos::interlace_modes::interlaced_tff.name }) }, { nmos::caps::format::colorspace, nmos::make_caps_string_constraint({ sdp::colorimetries::BT2020.name, sdp::colorimetries::BT709.name }) }, { nmos::caps::format::transfer_characteristic, nmos::make_caps_string_constraint({ sdp::transfer_characteristic_systems::SDR.name }) }, { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({ 10 }) }, @@ -276,7 +298,7 @@ BST_TEST_CASE(testConstraintSetIntersectionMerge) { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ sdp::samplings::YCbCr_4_2_2.name }) }, - { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_psf.name, nmos::interlace_modes::interlaced_tff.name }) }, { nmos::caps::format::colorspace, nmos::make_caps_string_constraint({ sdp::colorimetries::BT2020.name, sdp::colorimetries::BT709.name }) }, { nmos::caps::format::transfer_characteristic, nmos::make_caps_string_constraint({ sdp::transfer_characteristic_systems::SDR.name }) }, { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({ 10 }) }, From fd6942384e39722bcb3feed1b7ed4167c199d81c Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Tue, 25 Apr 2023 16:16:43 +0400 Subject: [PATCH 069/109] streamcompatibility_active_constraints_put_handler -> streamcompatibility_active_constraints_handler --- .../nmos-cpp-node/node_implementation.cpp | 17 ++++++---- Development/nmos/node_server.h | 4 +-- Development/nmos/streamcompatibility_api.cpp | 33 +++++++++---------- Development/nmos/streamcompatibility_api.h | 7 ++-- 4 files changed, 32 insertions(+), 29 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index d09f1a16c..5afdc55d0 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1488,7 +1488,7 @@ nmos::experimental::details::streamcompatibility_effective_edid_setter make_node } // Example Stream Compatibility Management API callback to update Active Constraints of a Sender -nmos::experimental::details::streamcompatibility_active_constraints_put_handler make_node_implementation_streamcompatibility_active_constraints_handler(const nmos::node_model& model, slog::base_gate& gate) +nmos::experimental::details::streamcompatibility_active_constraints_handler make_node_implementation_streamcompatibility_active_constraints_handler(const nmos::node_model& model, slog::base_gate& gate) { using web::json::value_of; @@ -1535,13 +1535,20 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler return [&gate, video_sender_capabilities, audio_sender_capabilities, video_sender_ids, audio_sender_ids](const nmos::resource& streamcompatibility_sender, const web::json::value& active_constraints, web::json::value& intersection) { + const auto& constraint_sets = nmos::fields::constraint_sets(active_constraints).as_array(); + + if (web::json::empty(constraint_sets)) + { + intersection = web::json::value::array(); + return; + } + const auto& sender_id = streamcompatibility_sender.id; const bool video_found = video_sender_ids.end() != boost::range::find(video_sender_ids, sender_id); const bool audio_found = audio_sender_ids.end() != boost::range::find(audio_sender_ids, sender_id); const auto& sender_capabilities_ = video_found ? video_sender_capabilities : audio_found ? audio_sender_capabilities : throw std::logic_error("No Sender Capabilities found"); const auto& sender_capabilities = sender_capabilities_.as_array(); - const auto& constraint_sets = nmos::fields::constraint_sets(active_constraints).as_array(); std::vector v(constraint_sets.size() * sender_capabilities.size()); auto iter = v.begin(); @@ -1562,12 +1569,10 @@ nmos::experimental::details::streamcompatibility_active_constraints_put_handler v.resize(iter - v.begin()); if (!v.empty()) { - intersection = value_from_elements(v); - return; + slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " doesn't support proposed Active Constraints"; } - slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " doesn't support proposed Active Constraints"; - throw std::logic_error("sender capabilities are " + utility::us2s(sender_capabilities_.serialize())); + intersection = value_from_elements(v); }; } diff --git a/Development/nmos/node_server.h b/Development/nmos/node_server.h index f65240a82..d390185d6 100644 --- a/Development/nmos/node_server.h +++ b/Development/nmos/node_server.h @@ -63,7 +63,7 @@ namespace nmos node_implementation& on_get_ocsp_response(nmos::ocsp_response_handler get_ocsp_response) { this->get_ocsp_response = std::move(get_ocsp_response); return *this; } node_implementation& on_base_edid_changed(nmos::experimental::details::streamcompatibility_base_edid_handler base_edid_changed) { this->base_edid_changed = std::move(base_edid_changed); return *this; } node_implementation& on_set_effective_edid(nmos::experimental::details::streamcompatibility_effective_edid_setter set_effective_edid) { this->set_effective_edid = std::move(set_effective_edid); return *this; } - node_implementation& on_active_constraints_changed(nmos::experimental::details::streamcompatibility_active_constraints_put_handler active_constraints_changed) { this->active_constraints_changed = std::move(active_constraints_changed); return *this; } + node_implementation& on_active_constraints_changed(nmos::experimental::details::streamcompatibility_active_constraints_handler active_constraints_changed) { this->active_constraints_changed = std::move(active_constraints_changed); return *this; } node_implementation& on_validate_sender_resources_against_active_constraints(nmos::experimental::details::streamcompatibility_sender_validator validate_sender_resources) { this->validate_sender_resources = std::move(validate_sender_resources); return *this; } node_implementation& on_validate_receiver_against_transport_file(nmos::experimental::details::streamcompatibility_receiver_validator validate_receiver) { this->validate_receiver = std::move(validate_receiver); return *this; } @@ -98,7 +98,7 @@ namespace nmos nmos::experimental::details::streamcompatibility_base_edid_handler base_edid_changed; nmos::experimental::details::streamcompatibility_effective_edid_setter set_effective_edid; - nmos::experimental::details::streamcompatibility_active_constraints_put_handler active_constraints_changed; + nmos::experimental::details::streamcompatibility_active_constraints_handler active_constraints_changed; nmos::experimental::details::streamcompatibility_sender_validator validate_sender_resources; nmos::experimental::details::streamcompatibility_receiver_validator validate_receiver; }; diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index 4686ee5b2..fa91092b0 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -177,9 +177,9 @@ namespace nmos } } - web::http::experimental::listener::api_router make_unmounted_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_handler base_edid_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate); + web::http::experimental::listener::api_router make_unmounted_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_handler base_edid_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_handler active_constraints_handler, slog::base_gate& gate); - web::http::experimental::listener::api_router make_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_handler base_edid_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate) + web::http::experimental::listener::api_router make_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_handler base_edid_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_handler active_constraints_handler, slog::base_gate& gate) { using namespace web::http::experimental::listener::api_router_using_declarations; @@ -209,7 +209,7 @@ namespace nmos return streamcompatibility_api; } - web::http::experimental::listener::api_router make_unmounted_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_handler base_edid_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate_) + web::http::experimental::listener::api_router make_unmounted_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_handler base_edid_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_handler active_constraints_handler, slog::base_gate& gate_) { using namespace web::http::experimental::listener::api_router_using_declarations; @@ -804,21 +804,16 @@ namespace nmos bool can_adhere = true; web::json::value intersection = web::json::value::array(); - if (!web::json::empty(nmos::fields::constraint_sets(data).as_array())) + if (active_constraints_handler) { - if (active_constraints_handler) + active_constraints_handler(*streamcompatibility_sender, data, intersection); + + if (web::json::empty(intersection)) { - try - { - active_constraints_handler(*streamcompatibility_sender, data, intersection); - } - catch(const std::logic_error& e) - { - can_adhere = false; - - slog::log(gate, SLOG_FLF) << "Active Constraints update is requested for " << id_type << " but this sender can't adhere to these Constraints"; - set_error_reply(res, status_codes::UnprocessableEntity, e); - } + can_adhere = false; + + slog::log(gate, SLOG_FLF) << "Active Constraints update is requested for " << id_type << " but this sender can't adhere to these Constraints"; + set_error_reply(res, status_codes::UnprocessableEntity); } } @@ -852,7 +847,7 @@ namespace nmos }); }); - streamcompatibility_api.support(U("/") + nmos::patterns::senderType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/constraints/active/?"), methods::DEL, [&model, effective_edid_setter, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + streamcompatibility_api.support(U("/") + nmos::patterns::senderType.pattern + U("/") + nmos::patterns::resourceId.pattern + U("/constraints/active/?"), methods::DEL, [&model, effective_edid_setter, active_constraints_handler, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) { nmos::api_gate gate(gate_, req, parameters); auto lock = model.write_lock(); @@ -872,7 +867,11 @@ namespace nmos slog::log(gate, SLOG_FLF) << "Active Constraints deletion is requested for " << id_type; const auto empty_array = web::json::value::array(); + web::json::value intersection = web::json::value::array(); + + active_constraints_handler(*streamcompatibility_sender, empty_array, intersection); details::set_active_constraints(model, resourceId, empty_array, empty_array, effective_edid_setter); + set_reply(res, status_codes::OK, nmos::fields::active_constraint_sets(nmos::fields::endpoint_active_constraints(streamcompatibility_sender->data))); } else diff --git a/Development/nmos/streamcompatibility_api.h b/Development/nmos/streamcompatibility_api.h index 702b7d3f2..20394ccbc 100644 --- a/Development/nmos/streamcompatibility_api.h +++ b/Development/nmos/streamcompatibility_api.h @@ -24,12 +24,11 @@ namespace nmos // when DELETE, this callback should not throw exceptions, as the Base EDID will already have been changed and those changes will not be rolled back typedef std::function& base_edid, web::json::value& base_edid_properties)> streamcompatibility_base_edid_handler; - // a streamcompatibility_active_constraints_put_handler is a notification that the Active Constraints for the specified IS-11 sender has changed + // a streamcompatibility_active_constraints_handler is a notification that the Active Constraints for the specified IS-11 sender has changed (PUT or DELETEd) // it can be used to perform any final validation of the specified Active Constraints // it may throw web::json::json_exception, which will be mapped to a 400 Bad Request status code with NMOS error "debug" information including the exception message - // or std::logic_error, which will be mapped to a 422 Unprocessible Entity status code with NMOS error "debug" information including the exception message // or std::runtime_error, which will be mapped to a 500 Internal Error status code with NMOS error "debug" information including the exception message - typedef std::function streamcompatibility_active_constraints_put_handler; + typedef std::function streamcompatibility_active_constraints_handler; // a streamcompatibility_effective_edid_setter updates the specified Effective EDID for the specified IS-11 input // effective EDID of the input is updated when either Active Constraints of a Sender associated with this input are updated @@ -38,7 +37,7 @@ namespace nmos typedef std::function& effective_edid, bst::optional& effective_edid_properties)> streamcompatibility_effective_edid_setter; } - web::http::experimental::listener::api_router make_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_handler base_edid_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_put_handler active_constraints_handler, slog::base_gate& gate); + web::http::experimental::listener::api_router make_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_handler base_edid_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_handler active_constraints_handler, slog::base_gate& gate); } } From 6b75505fae7a5026adc7df9d5991c3f482f49eb1 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Tue, 25 Apr 2023 16:59:51 +0400 Subject: [PATCH 070/109] Separate 422 and empty intersection --- Development/nmos-cpp-node/node_implementation.cpp | 6 ++++-- Development/nmos/streamcompatibility_api.cpp | 12 ++++++------ Development/nmos/streamcompatibility_api.h | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 5afdc55d0..544e31a00 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1540,7 +1540,7 @@ nmos::experimental::details::streamcompatibility_active_constraints_handler make if (web::json::empty(constraint_sets)) { intersection = web::json::value::array(); - return; + return true; } const auto& sender_id = streamcompatibility_sender.id; @@ -1567,12 +1567,14 @@ nmos::experimental::details::streamcompatibility_active_constraints_handler make } v.resize(iter - v.begin()); - if (!v.empty()) + if (v.empty()) { slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " doesn't support proposed Active Constraints"; + return false; } intersection = value_from_elements(v); + return true; }; } diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index fa91092b0..f0f3d3475 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -806,9 +806,7 @@ namespace nmos if (active_constraints_handler) { - active_constraints_handler(*streamcompatibility_sender, data, intersection); - - if (web::json::empty(intersection)) + if (!active_constraints_handler(*streamcompatibility_sender, data, intersection)) { can_adhere = false; @@ -866,11 +864,13 @@ namespace nmos { slog::log(gate, SLOG_FLF) << "Active Constraints deletion is requested for " << id_type; - const auto empty_array = web::json::value::array(); + const auto active_constraints = web::json::value_of({ + { nmos::fields::constraint_sets, web::json::value::array() } + }); web::json::value intersection = web::json::value::array(); - active_constraints_handler(*streamcompatibility_sender, empty_array, intersection); - details::set_active_constraints(model, resourceId, empty_array, empty_array, effective_edid_setter); + active_constraints_handler(*streamcompatibility_sender, active_constraints, intersection); + details::set_active_constraints(model, resourceId, nmos::fields::constraint_sets(active_constraints), intersection, effective_edid_setter); set_reply(res, status_codes::OK, nmos::fields::active_constraint_sets(nmos::fields::endpoint_active_constraints(streamcompatibility_sender->data))); } diff --git a/Development/nmos/streamcompatibility_api.h b/Development/nmos/streamcompatibility_api.h index 20394ccbc..aed732477 100644 --- a/Development/nmos/streamcompatibility_api.h +++ b/Development/nmos/streamcompatibility_api.h @@ -28,7 +28,7 @@ namespace nmos // it can be used to perform any final validation of the specified Active Constraints // it may throw web::json::json_exception, which will be mapped to a 400 Bad Request status code with NMOS error "debug" information including the exception message // or std::runtime_error, which will be mapped to a 500 Internal Error status code with NMOS error "debug" information including the exception message - typedef std::function streamcompatibility_active_constraints_handler; + typedef std::function streamcompatibility_active_constraints_handler; // a streamcompatibility_effective_edid_setter updates the specified Effective EDID for the specified IS-11 input // effective EDID of the input is updated when either Active Constraints of a Sender associated with this input are updated From e0b29e1fe682354e1732fb0ee302213f3ddb54e5 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Tue, 25 Apr 2023 17:15:37 +0400 Subject: [PATCH 071/109] Run IS-11 test suite as part of CI --- .github/workflows/build-test.yml | 10 ++++++---- .github/workflows/src/amwa-test.yml | 7 ++++--- Sandbox/run_nmos_testing.sh | 3 +++ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 2f92f4959..386d7e2e5 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -340,9 +340,10 @@ jobs: set -x root_dir=`pwd` - # Install AMWA NMOS Testing Tool - git clone https://github.com/AMWA-TV/nmos-testing.git + # Install AMWA NMOS Testing Tool (gwgeorgea instead of AMWA-TV until IS-11 tests are merged) + git clone https://github.com/gwgeorgea/nmos-testing.git cd nmos-testing + git checkout is-11-initial-tests-general-senders-outputs-receivers_ # Configure the Testing Tool so all APIs are tested with TLS printf "from . import Config as CONFIG\nCONFIG.ENABLE_HTTPS = True\n" > nmostesting/UserConfig.py @@ -847,9 +848,10 @@ jobs: set -x root_dir=`pwd` - # Install AMWA NMOS Testing Tool - git clone https://github.com/AMWA-TV/nmos-testing.git + # Install AMWA NMOS Testing Tool (gwgeorgea instead of AMWA-TV until IS-11 tests are merged) + git clone https://github.com/gwgeorgea/nmos-testing.git cd nmos-testing + git checkout is-11-initial-tests-general-senders-outputs-receivers_ # Configure the Testing Tool so all APIs are tested with TLS printf "from . import Config as CONFIG\nCONFIG.ENABLE_HTTPS = True\n" > nmostesting/UserConfig.py diff --git a/.github/workflows/src/amwa-test.yml b/.github/workflows/src/amwa-test.yml index cca0fa18d..7337ac385 100644 --- a/.github/workflows/src/amwa-test.yml +++ b/.github/workflows/src/amwa-test.yml @@ -15,9 +15,10 @@ set -x root_dir=`pwd` - # Install AMWA NMOS Testing Tool - git clone https://github.com/AMWA-TV/nmos-testing.git + # Install AMWA NMOS Testing Tool (gwgeorgea instead of AMWA-TV until IS-11 tests are merged) + git clone https://github.com/gwgeorgea/nmos-testing.git cd nmos-testing + git checkout is-11-initial-tests-general-senders-outputs-receivers_ # Configure the Testing Tool so all APIs are tested with TLS printf "from . import Config as CONFIG\nCONFIG.ENABLE_HTTPS = True\n" > nmostesting/UserConfig.py @@ -146,7 +147,7 @@ # nmos-cpp-node doesn't currently support advertising hostnames to Avahi avahi-publish -a -R nmos-api.local ${{ env.HOST_IP_ADDRESS }} & fi - + ${{ env.GITHUB_WORKSPACE_BASH }}/Sandbox/run_nmos_testing.sh "$run_python" ${domain} ${root_dir}/build/nmos-cpp-node ${root_dir}/build/nmos-cpp-registry results badges $GITHUB_STEP_SUMMARY ${{ env.HOST_IP_ADDRESS }} "${{ env.GITHUB_COMMIT }}-${{ env.BUILD_NAME }}-" if [[ "${{ runner.os }}" == "Linux" ]]; then diff --git a/Sandbox/run_nmos_testing.sh b/Sandbox/run_nmos_testing.sh index 9e45dcc6a..8ab4f8429 100755 --- a/Sandbox/run_nmos_testing.sh +++ b/Sandbox/run_nmos_testing.sh @@ -36,6 +36,7 @@ expected_disabled_IS_08_02=0 expected_disabled_IS_09_02=0 expected_disabled_IS_04_02=0 expected_disabled_IS_09_01=0 +expected_disabled_IS_11_01=0 config_secure=`${run_python} -c $'from nmostesting import Config\nprint(Config.ENABLE_HTTPS)'` || (echo "error running python"; exit 1) config_dns_sd_mode=`${run_python} -c $'from nmostesting import Config\nprint(Config.DNS_SD_MODE)'` || (echo "error running python"; exit 1) @@ -193,6 +194,8 @@ do_run_test IS-08-02 $expected_disabled_IS_08_02 --host "${host}" "${host}" --po do_run_test IS-09-02 $expected_disabled_IS_09_02 --host "${host}" null --port 0 0 --version null v1.0 +do_run_test IS-11-01 $expected_disabled_IS_11_01 --host "${host}" "${host}" "${host}" --port 1080 1080 1080 --version v1.0 v1.3 v1.1 + # Run Registry tests (leave Node running) "${registry_command}" "{\"pri\":0,\"http_port\":8088 ${common_params}}" > ${results_dir}/registryoutput 2>&1 & REGISTRY_PID=$! From 9e9ac54a0d7c06bb9d2bd955f25336d94c780e83 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Tue, 25 Apr 2023 18:15:08 +0400 Subject: [PATCH 072/109] Quickfix --- Sandbox/run_nmos_testing.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/Sandbox/run_nmos_testing.sh b/Sandbox/run_nmos_testing.sh index 8ab4f8429..e0118a645 100755 --- a/Sandbox/run_nmos_testing.sh +++ b/Sandbox/run_nmos_testing.sh @@ -123,6 +123,7 @@ else (( expected_disabled_IS_07_02+=21 )) (( expected_disabled_IS_08_01+=7 )) (( expected_disabled_IS_08_02+=14 )) + (( expected_disabled_IS_11_01+=21 )) # test_33, test_33_1 (( expected_disabled_IS_04_02+=16 )) (( expected_disabled_IS_09_01+=7 )) From 1631623259ab431f5aab4e9b35d116801170302e Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Tue, 25 Apr 2023 18:25:19 +0400 Subject: [PATCH 073/109] Temporarily ignore auto_connection_21 --- Sandbox/run_nmos_testing.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sandbox/run_nmos_testing.sh b/Sandbox/run_nmos_testing.sh index e0118a645..348ed9a81 100755 --- a/Sandbox/run_nmos_testing.sh +++ b/Sandbox/run_nmos_testing.sh @@ -124,6 +124,8 @@ else (( expected_disabled_IS_08_01+=7 )) (( expected_disabled_IS_08_02+=14 )) (( expected_disabled_IS_11_01+=21 )) + # auto_connection_21 + (( expected_disabled_IS_11_01+=1 )) # test_33, test_33_1 (( expected_disabled_IS_04_02+=16 )) (( expected_disabled_IS_09_01+=7 )) @@ -195,7 +197,7 @@ do_run_test IS-08-02 $expected_disabled_IS_08_02 --host "${host}" "${host}" --po do_run_test IS-09-02 $expected_disabled_IS_09_02 --host "${host}" null --port 0 0 --version null v1.0 -do_run_test IS-11-01 $expected_disabled_IS_11_01 --host "${host}" "${host}" "${host}" --port 1080 1080 1080 --version v1.0 v1.3 v1.1 +do_run_test IS-11-01 $expected_disabled_IS_11_01 --host "${host}" "${host}" "${host}" --port 1080 1080 1080 --version v1.0 v1.3 v1.1 --ignore auto_connection_21 # Run Registry tests (leave Node running) "${registry_command}" "{\"pri\":0,\"http_port\":8088 ${common_params}}" > ${results_dir}/registryoutput 2>&1 & From dff559173142929475ccd7c9cd8862fc38ab77e4 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Thu, 27 Apr 2023 14:02:28 +0400 Subject: [PATCH 074/109] Revert "Temporarily ignore auto_connection_21" This reverts commit 1631623259ab431f5aab4e9b35d116801170302e. --- Sandbox/run_nmos_testing.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Sandbox/run_nmos_testing.sh b/Sandbox/run_nmos_testing.sh index 348ed9a81..e0118a645 100755 --- a/Sandbox/run_nmos_testing.sh +++ b/Sandbox/run_nmos_testing.sh @@ -124,8 +124,6 @@ else (( expected_disabled_IS_08_01+=7 )) (( expected_disabled_IS_08_02+=14 )) (( expected_disabled_IS_11_01+=21 )) - # auto_connection_21 - (( expected_disabled_IS_11_01+=1 )) # test_33, test_33_1 (( expected_disabled_IS_04_02+=16 )) (( expected_disabled_IS_09_01+=7 )) @@ -197,7 +195,7 @@ do_run_test IS-08-02 $expected_disabled_IS_08_02 --host "${host}" "${host}" --po do_run_test IS-09-02 $expected_disabled_IS_09_02 --host "${host}" null --port 0 0 --version null v1.0 -do_run_test IS-11-01 $expected_disabled_IS_11_01 --host "${host}" "${host}" "${host}" --port 1080 1080 1080 --version v1.0 v1.3 v1.1 --ignore auto_connection_21 +do_run_test IS-11-01 $expected_disabled_IS_11_01 --host "${host}" "${host}" "${host}" --port 1080 1080 1080 --version v1.0 v1.3 v1.1 # Run Registry tests (leave Node running) "${registry_command}" "{\"pri\":0,\"http_port\":8088 ${common_params}}" > ${results_dir}/registryoutput 2>&1 & From 3d714418bfb104626717e01e48d6687f36da1788 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 28 Apr 2023 12:26:20 +0400 Subject: [PATCH 075/109] CI: switch IS-11 branch --- .github/workflows/build-test.yml | 12 ++++++------ .github/workflows/src/amwa-test.yml | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 386d7e2e5..c60dd7908 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -340,10 +340,10 @@ jobs: set -x root_dir=`pwd` - # Install AMWA NMOS Testing Tool (gwgeorgea instead of AMWA-TV until IS-11 tests are merged) - git clone https://github.com/gwgeorgea/nmos-testing.git + # Install AMWA NMOS Testing Tool (is-11 instead of master until IS-11 tests are merged) + git clone https://github.com/AMWA-TV/nmos-testing.git cd nmos-testing - git checkout is-11-initial-tests-general-senders-outputs-receivers_ + git checkout is-11 # Configure the Testing Tool so all APIs are tested with TLS printf "from . import Config as CONFIG\nCONFIG.ENABLE_HTTPS = True\n" > nmostesting/UserConfig.py @@ -848,10 +848,10 @@ jobs: set -x root_dir=`pwd` - # Install AMWA NMOS Testing Tool (gwgeorgea instead of AMWA-TV until IS-11 tests are merged) - git clone https://github.com/gwgeorgea/nmos-testing.git + # Install AMWA NMOS Testing Tool (is-11 instead of master until IS-11 tests are merged) + git clone https://github.com/AMWA-TV/nmos-testing.git cd nmos-testing - git checkout is-11-initial-tests-general-senders-outputs-receivers_ + git checkout is-11 # Configure the Testing Tool so all APIs are tested with TLS printf "from . import Config as CONFIG\nCONFIG.ENABLE_HTTPS = True\n" > nmostesting/UserConfig.py diff --git a/.github/workflows/src/amwa-test.yml b/.github/workflows/src/amwa-test.yml index 7337ac385..09f885c7a 100644 --- a/.github/workflows/src/amwa-test.yml +++ b/.github/workflows/src/amwa-test.yml @@ -15,10 +15,10 @@ set -x root_dir=`pwd` - # Install AMWA NMOS Testing Tool (gwgeorgea instead of AMWA-TV until IS-11 tests are merged) - git clone https://github.com/gwgeorgea/nmos-testing.git + # Install AMWA NMOS Testing Tool (is-11 instead of master until IS-11 tests are merged) + git clone https://github.com/AMWA-TV/nmos-testing.git cd nmos-testing - git checkout is-11-initial-tests-general-senders-outputs-receivers_ + git checkout is-11 # Configure the Testing Tool so all APIs are tested with TLS printf "from . import Config as CONFIG\nCONFIG.ENABLE_HTTPS = True\n" > nmostesting/UserConfig.py From e5efb1d532c4ba959f2dd07f8c97ae2d050fe2fe Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 28 Apr 2023 17:33:12 +0400 Subject: [PATCH 076/109] Move video_jxsv_sender_resources_matcher to video_jxsv.{h,cpp} --- .../nmos-cpp-node/node_implementation.cpp | 19 +---------------- Development/nmos/video_jxsv.cpp | 21 +++++++++++++++++++ Development/nmos/video_jxsv.h | 2 ++ 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 544e31a00..03d0d7d8a 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1583,24 +1583,7 @@ nmos::experimental::details::streamcompatibility_sender_validator make_node_impl using nmos::experimental::make_streamcompatibility_sender_resources_validator; using nmos::experimental::make_streamcompatibility_sdp_constraint_sets_matcher; - const auto video_jxsv_sender_resources_matcher = [](const nmos::resource& resource, const web::json::value& constraint_set) -> bool - { - const std::map resource_parameter_constraints - { - { nmos::types::source, nmos::experimental::source_parameter_constraints }, - { nmos::types::flow, nmos::experimental::video_jxsv_flow_parameter_constraints }, - { nmos::types::sender, nmos::experimental::video_jxsv_sender_parameter_constraints } - }; - - if (0 == resource_parameter_constraints.count(resource.type)) - { - throw std::logic_error("wrong resource type"); - } - - return nmos::experimental::detail::match_resource_parameters_constraint_set(resource_parameter_constraints.at(resource.type), resource.data, constraint_set); - }; - - const auto validate_video_jxsv_sender_resources = make_streamcompatibility_sender_resources_validator(video_jxsv_sender_resources_matcher, make_streamcompatibility_sdp_constraint_sets_matcher(&nmos::match_video_jxsv_sdp_parameters_constraint_sets)); + const auto validate_video_jxsv_sender_resources = make_streamcompatibility_sender_resources_validator(&nmos::experimental::match_video_jxsv_resource_parameters_constraint_set, make_streamcompatibility_sdp_constraint_sets_matcher(&nmos::match_video_jxsv_sdp_parameters_constraint_sets)); const auto validate_sender_resources = make_streamcompatibility_sender_resources_validator(&nmos::experimental::match_resource_parameters_constraint_set, make_streamcompatibility_sdp_constraint_sets_matcher(&nmos::match_sdp_parameters_constraint_sets)); // this example uses a custom sender resources validator to handle video/jxsv in addition to the core media types diff --git a/Development/nmos/video_jxsv.cpp b/Development/nmos/video_jxsv.cpp index 516821554..8e7450b19 100644 --- a/Development/nmos/video_jxsv.cpp +++ b/Development/nmos/video_jxsv.cpp @@ -6,6 +6,7 @@ #include "nmos/interlace_mode.h" #include "nmos/json_fields.h" #include "nmos/resource.h" +#include "nmos/streamcompatibility_validation.h" namespace sdp { @@ -343,6 +344,26 @@ namespace nmos return constraint_sets.end() != found; } + namespace experimental + { + bool match_video_jxsv_resource_parameters_constraint_set(const nmos::resource& resource, const web::json::value& constraint_set) + { + const std::map resource_parameter_constraints + { + { nmos::types::source, nmos::experimental::source_parameter_constraints }, + { nmos::types::flow, nmos::experimental::video_jxsv_flow_parameter_constraints }, + { nmos::types::sender, nmos::experimental::video_jxsv_sender_parameter_constraints } + }; + + if (0 == resource_parameter_constraints.count(resource.type)) + { + throw std::logic_error("wrong resource type"); + } + + return nmos::experimental::detail::match_resource_parameters_constraint_set(resource_parameter_constraints.at(resource.type), resource.data, constraint_set); + }; + } + // See https://specs.amwa.tv/bcp-006-01/branches/v1.0-dev/docs/NMOS_With_JPEG_XS.html#flows // cf. nmos::make_coded_video_flow nmos::resource make_video_jxsv_flow( diff --git a/Development/nmos/video_jxsv.h b/Development/nmos/video_jxsv.h index d4aac24d2..dce0e895f 100644 --- a/Development/nmos/video_jxsv.h +++ b/Development/nmos/video_jxsv.h @@ -417,6 +417,8 @@ namespace nmos { nmos::caps::transport::st2110_21_sender_type, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_string_constraint(nmos::fields::st2110_21_sender_type(sender), con); } }, { nmos::caps::transport::bit_rate, [](const web::json::value& sender, const web::json::value& con) { return nmos::match_integer_constraint(nmos::fields::bit_rate(sender), con); } } }; + + bool match_video_jxsv_resource_parameters_constraint_set(const nmos::resource& resource, const web::json::value& constraint_set); } } From ecf6aaf963a63850ad03438cfa205653513fe64c Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 28 Apr 2023 17:43:11 +0400 Subject: [PATCH 077/109] Fix streamcompatibility_sender_validator typedef --- Development/nmos-cpp-node/node_implementation.cpp | 6 +++--- Development/nmos/streamcompatibility_behaviour.cpp | 3 +-- Development/nmos/streamcompatibility_validation.cpp | 4 +++- Development/nmos/streamcompatibility_validation.h | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 03d0d7d8a..8244f629d 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1588,17 +1588,17 @@ nmos::experimental::details::streamcompatibility_sender_validator make_node_impl // this example uses a custom sender resources validator to handle video/jxsv in addition to the core media types // (if this callback is specified, an 'empty' std::function is not allowed) - return [validate_video_jxsv_sender_resources, validate_sender_resources](const web::json::value& transport_file, const nmos::resource& sender, const nmos::resource& flow, const nmos::resource& source, const web::json::array& constraint_sets) -> std::pair + return [validate_video_jxsv_sender_resources, validate_sender_resources](const nmos::resource& source, const nmos::resource& flow, const nmos::resource& sender, const nmos::resource& connection_sender, const web::json::array& constraint_sets) -> std::pair { if (nmos::media_types::video_jxsv.name == nmos::fields::media_type(flow.data)) { - std::pair res = validate_video_jxsv_sender_resources(transport_file, sender, flow, source, constraint_sets); + std::pair res = validate_video_jxsv_sender_resources(source, flow, sender, connection_sender, constraint_sets); return res; } else { // validate core media types, i.e., "video/raw", "audio/L", "video/smpte291" and "video/SMPTE2022-6" - return validate_sender_resources(transport_file, sender, flow, source, constraint_sets); + return validate_sender_resources(source, flow, sender, connection_sender, constraint_sets); } }; } diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index 58164047e..a0e6811db 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -93,12 +93,11 @@ namespace nmos if (sender_state != nmos::sender_states::no_essence && sender_state != nmos::sender_states::awaiting_essence) { const auto& constraint_sets = nmos::fields::constraint_sets(nmos::fields::active_constraint_sets(nmos::fields::endpoint_active_constraints(streamcompatibility_sender->data))).as_array(); - auto& transport_file = nmos::fields::endpoint_transportfile(connection_sender->data); slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " is being validated with its Flow, Source and transport file"; if (validate_sender_resources) { - std::tie(sender_state, sender_state_debug) = validate_sender_resources(transport_file, *sender, *flow, *source, constraint_sets); + std::tie(sender_state, sender_state_debug) = validate_sender_resources(*source, *flow, *sender, *connection_sender, constraint_sets); } } diff --git a/Development/nmos/streamcompatibility_validation.cpp b/Development/nmos/streamcompatibility_validation.cpp index 0c98edbbf..bf7dee354 100644 --- a/Development/nmos/streamcompatibility_validation.cpp +++ b/Development/nmos/streamcompatibility_validation.cpp @@ -91,7 +91,7 @@ namespace nmos details::streamcompatibility_sender_validator make_streamcompatibility_sender_resources_validator(const details::resource_constraints_matcher& match_resource_constraint_set, const details::transport_file_constraint_sets_matcher& match_transport_file_constraint_sets) { - return [match_resource_constraint_set, match_transport_file_constraint_sets](const web::json::value& transport_file, const nmos::resource& sender, const nmos::resource& flow, const nmos::resource& source, const web::json::array& constraint_sets) -> std::pair + return [match_resource_constraint_set, match_transport_file_constraint_sets](const nmos::resource& source, const nmos::resource& flow, const nmos::resource& sender, const nmos::resource& connection_sender, const web::json::array& constraint_sets) -> std::pair { nmos::sender_state sender_state; @@ -104,6 +104,8 @@ namespace nmos constrained = constraint_sets.end() != source_found && constraint_sets.end() != flow_found && constraint_sets.end() != sender_found; + const auto& transport_file = nmos::fields::endpoint_transportfile(connection_sender.data); + if (!transport_file.is_null() && !transport_file.as_object().empty()) { constrained = constrained && match_transport_file_constraint_sets(nmos::details::get_transport_type_data(transport_file), constraint_sets); diff --git a/Development/nmos/streamcompatibility_validation.h b/Development/nmos/streamcompatibility_validation.h index a3437e748..7824c2380 100644 --- a/Development/nmos/streamcompatibility_validation.h +++ b/Development/nmos/streamcompatibility_validation.h @@ -28,7 +28,7 @@ namespace nmos namespace details { // returns Sender's "state" and "debug" values - typedef std::function(const web::json::value& transport_file, const nmos::resource& sender, const nmos::resource& flow, const nmos::resource& source, const web::json::array& constraint_sets)> streamcompatibility_sender_validator; + typedef std::function(const nmos::resource& source, const nmos::resource& flow, const nmos::resource& sender, const nmos::resource& connection_sender, const web::json::array& constraint_sets)> streamcompatibility_sender_validator; // returns Receiver's "state" and "debug" values typedef std::function(const web::json::value& transport_file, const nmos::resource& receiver, const nmos::resource& connection_receiver)> streamcompatibility_receiver_validator; From e1f707c6375292319a5550e9ef32c47994bea11e Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 28 Apr 2023 19:15:04 +0400 Subject: [PATCH 078/109] Remove "merge" from get_constraint_set_intersection --- .../nmos-cpp-node/node_implementation.cpp | 2 +- Development/nmos/constraints.cpp | 75 ++++++++--------- Development/nmos/constraints.h | 2 +- Development/nmos/test/constraints_test.cpp | 81 ++++++++++++++++++- 4 files changed, 119 insertions(+), 41 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 8244f629d..70e85de77 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1558,7 +1558,7 @@ nmos::experimental::details::streamcompatibility_active_constraints_handler make if (!nmos::caps::meta::enabled(constraint_set)) continue; for (const auto& sender_caps_constraint_set : sender_capabilities) { - const auto intersection = nmos::experimental::get_constraint_set_intersection(sender_caps_constraint_set, constraint_set, true); + const auto intersection = nmos::experimental::get_constraint_set_intersection(sender_caps_constraint_set, constraint_set); if (!intersection.is_null()) { *iter++ = std::move(intersection); diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index 286661fc3..83ea8016d 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -129,54 +129,57 @@ namespace nmos return result; } - web::json::value get_constraint_set_intersection(const web::json::value& lhs, const web::json::value& rhs, bool merge_left_to_right) + web::json::value get_constraint_set_intersection(const web::json::value& lhs_, const web::json::value& rhs_) { using web::json::value; using web::json::value_from_elements; - const auto& lhs_param_constraints = lhs.as_object(); - auto rhs_param_constraints = rhs.as_object(); + const auto& lhs = lhs_.as_object(); + const auto& rhs = rhs_.as_object(); - if (merge_left_to_right) - { - rhs_param_constraints = lhs_param_constraints; - std::for_each(rhs.as_object().begin(), rhs.as_object().end(), [&rhs_param_constraints](const std::pair& rhs_constraint) - { - rhs_param_constraints[rhs_constraint.first] = rhs_constraint.second; - }); - } - - const auto common_param_constraints = get_intersection( - value_from_elements(lhs_param_constraints | boost::adaptors::filtered([](const std::pair& constraint) { return !boost::algorithm::starts_with(constraint.first, U("urn:x-nmos:cap:meta:")); }) | boost::adaptors::transformed([](const std::pair& constraint) { return constraint.first; })).as_array(), - value_from_elements(rhs_param_constraints | boost::adaptors::filtered([](const std::pair& constraint) { return !boost::algorithm::starts_with(constraint.first, U("urn:x-nmos:cap:meta:")); }) | boost::adaptors::transformed([](const std::pair& constraint) { return constraint.first; })).as_array() - ).as_array(); + auto result = value::object(); - if (0 == common_param_constraints.size()) - { - return value::null(); - } + auto lhs_iter = lhs.begin(); + auto rhs_iter = rhs.begin(); - try + while (lhs_iter != lhs.end() || rhs_iter != rhs.end()) { - value result; - - std::for_each(common_param_constraints.begin(), common_param_constraints.end(), [&result, &lhs_param_constraints, &rhs_param_constraints](const web::json::value& constraint_name_) + if (lhs_iter != lhs.end() && rhs_iter != rhs.end()) { - const auto& constraint_name = constraint_name_.as_string(); - const web::json::value intersection = get_constraint_intersection(lhs_param_constraints.at(constraint_name), rhs_param_constraints.at(constraint_name)); - if (intersection.is_null()) + if (lhs_iter->first < rhs_iter->first) { - throw std::runtime_error(utility::us2s(constraint_name) + " gives empty intersection"); + result[lhs_iter->first] = lhs_iter->second; + lhs_iter++; } - result[constraint_name] = intersection; - }); - - return result; - } - catch (const std::runtime_error& e) - { - return value::null(); + else if (lhs_iter->first > rhs_iter->first) + { + result[rhs_iter->first] = rhs_iter->second; + rhs_iter++; + } + else + { + const value intersection = get_constraint_intersection(lhs_iter->second, rhs_iter->second); + if (intersection.is_null()) + { + return value::null(); + } + result[lhs_iter->first] = intersection; + lhs_iter++; rhs_iter++; + } + } + else if (lhs_iter == lhs.end()) + { + result[rhs_iter->first] = rhs_iter->second; + rhs_iter++; + } + else if (rhs_iter == rhs.end()) + { + result[lhs_iter->first] = lhs_iter->second; + lhs_iter++; + } } + + return result; } // Constraint B is a subconstraint of Constraint A if: diff --git a/Development/nmos/constraints.h b/Development/nmos/constraints.h index 6d4d61ec4..fd93f3c55 100644 --- a/Development/nmos/constraints.h +++ b/Development/nmos/constraints.h @@ -19,7 +19,7 @@ namespace nmos } web::json::value get_constraint_intersection(const web::json::value& lhs, const web::json::value& rhs); - web::json::value get_constraint_set_intersection(const web::json::value& lhs, const web::json::value& rhs, bool merge_left_to_right = false); + web::json::value get_constraint_set_intersection(const web::json::value& lhs, const web::json::value& rhs); bool is_subconstraint(const web::json::value& constraint, const web::json::value& subconstraint); bool is_constraint_subset(const web::json::value& constraint_set, const web::json::value& constraint_subset); diff --git a/Development/nmos/test/constraints_test.cpp b/Development/nmos/test/constraints_test.cpp index 976e52970..f1db209a2 100644 --- a/Development/nmos/test/constraints_test.cpp +++ b/Development/nmos/test/constraints_test.cpp @@ -215,7 +215,7 @@ BST_TEST_CASE(testEnumRationalConstraintIntersection) } //////////////////////////////////////////////////////////////////////////////////////////// -BST_TEST_CASE(testConstraintSetIntersection) +BST_TEST_CASE(testConstraintSetIntersectionSameParamConstraints) { { using web::json::value_of; @@ -265,7 +265,7 @@ BST_TEST_CASE(testConstraintSetIntersection) } //////////////////////////////////////////////////////////////////////////////////////////// -BST_TEST_CASE(testConstraintSetIntersectionMerge) +BST_TEST_CASE(testConstraintSetIntersection) { { using web::json::value_of; @@ -305,6 +305,81 @@ BST_TEST_CASE(testConstraintSetIntersectionMerge) { nmos::caps::transport::st2110_21_sender_type, nmos::make_caps_string_constraint({ sdp::type_parameters::type_N.name }) } }); - BST_REQUIRE_EQUAL(get_constraint_set_intersection(a, b, true), c); + BST_REQUIRE_EQUAL(get_constraint_set_intersection(a, b), c); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testConstraintSetIntersectionUniqueParamConstraints) +{ + { + using web::json::value_of; + using nmos::experimental::get_constraint_set_intersection; + + const auto a = value_of({ + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, + { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ sdp::samplings::YCbCr_4_2_2.name }) }, + { nmos::caps::format::colorspace, nmos::make_caps_string_constraint({ sdp::colorimetries::BT2020.name, sdp::colorimetries::BT709.name }) }, + { nmos::caps::format::transfer_characteristic, nmos::make_caps_string_constraint({ sdp::transfer_characteristic_systems::SDR.name }) }, + { nmos::caps::transport::st2110_21_sender_type, nmos::make_caps_string_constraint({ sdp::type_parameters::type_N.name }) } + }); + + const auto b = value_of({ + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25 }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({ 10 }) } + }); + + const auto c = value_of({ + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::video_raw.name }) }, + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25 }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, + { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ sdp::samplings::YCbCr_4_2_2.name }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, + { nmos::caps::format::colorspace, nmos::make_caps_string_constraint({ sdp::colorimetries::BT2020.name, sdp::colorimetries::BT709.name }) }, + { nmos::caps::format::transfer_characteristic, nmos::make_caps_string_constraint({ sdp::transfer_characteristic_systems::SDR.name }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({ 10 }) }, + { nmos::caps::transport::st2110_21_sender_type, nmos::make_caps_string_constraint({ sdp::type_parameters::type_N.name }) } + }); + + BST_REQUIRE_EQUAL(get_constraint_set_intersection(a, b), c); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testConstraintSetIntersectionOfEmpties) +{ + { + using web::json::value_of; + using nmos::experimental::get_constraint_set_intersection; + + const auto a = web::json::value::object(); + + BST_REQUIRE_EQUAL(get_constraint_set_intersection(a, a), a); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testConstraintSetIntersectionOfEmptyAndNonEmpty) +{ + { + using web::json::value_of; + using nmos::experimental::get_constraint_set_intersection; + + const auto a = web::json::value::object(); + + const auto b = value_of({ + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25 }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({ 10 }) } + }); + + BST_REQUIRE_EQUAL(get_constraint_set_intersection(a, b), b); + BST_REQUIRE_EQUAL(get_constraint_set_intersection(b, a), b); } } From c0c479fbc2e3f06dfa6d94c878b2224fc3bdfdcc Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 28 Apr 2023 21:01:09 +0400 Subject: [PATCH 079/109] Replace make_streamcompatibility_receiver_validator's transport_file_parser arg with transport_file_validator --- .../nmos-cpp-node/node_implementation.cpp | 41 +++++++++++-------- Development/nmos/node_server.h | 1 + .../nmos/streamcompatibility_behaviour.cpp | 2 +- .../nmos/streamcompatibility_validation.cpp | 28 +++++++++++-- .../nmos/streamcompatibility_validation.h | 13 +++++- Development/nmos/video_jxsv.cpp | 25 +++++------ 6 files changed, 73 insertions(+), 37 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 70e85de77..22a95e259 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -190,6 +190,19 @@ namespace impl const auto temperature_Celsius = nmos::event_types::measurement(U("temperature"), U("C")); const auto temperature_wildcard = nmos::event_types::measurement(U("temperature"), nmos::event_types::wildcard); const auto catcall = nmos::event_types::named_enum(nmos::event_types::number, U("caterwaul")); + + const auto validate_sdp_parameters = [](const web::json::value& receiver, const nmos::sdp_parameters& sdp_params) + { + if (nmos::media_types::video_jxsv == nmos::get_media_type(sdp_params)) + { + nmos::validate_video_jxsv_sdp_parameters(receiver, sdp_params); + } + else + { + // validate core media types, i.e., "video/raw", "audio/L", "video/smpte291" and "video/SMPTE2022-6" + nmos::validate_sdp_parameters(receiver, sdp_params); + } + }; } // forward declarations for node_implementation_thread @@ -1146,19 +1159,7 @@ nmos::transport_file_parser make_node_implementation_transport_file_parser() // (if this callback is specified, an 'empty' std::function is not allowed) return [](const nmos::resource& receiver, const nmos::resource& connection_receiver, const utility::string_t& transport_file_type, const utility::string_t& transport_file_data, slog::base_gate& gate) { - const auto validate_sdp_parameters = [](const web::json::value& receiver, const nmos::sdp_parameters& sdp_params) - { - if (nmos::media_types::video_jxsv == nmos::get_media_type(sdp_params)) - { - nmos::validate_video_jxsv_sdp_parameters(receiver, sdp_params); - } - else - { - // validate core media types, i.e., "video/raw", "audio/L", "video/smpte291" and "video/SMPTE2022-6" - nmos::validate_sdp_parameters(receiver, sdp_params); - } - }; - return nmos::details::parse_rtp_transport_file(validate_sdp_parameters, receiver, connection_receiver, transport_file_type, transport_file_data, gate); + return nmos::details::parse_rtp_transport_file(impl::validate_sdp_parameters, receiver, connection_receiver, transport_file_type, transport_file_data, gate); }; } @@ -1603,9 +1604,17 @@ nmos::experimental::details::streamcompatibility_sender_validator make_node_impl }; } -nmos::experimental::details::streamcompatibility_receiver_validator make_node_implementation_streamcompatibility_receiver_validator(slog::base_gate& gate) +nmos::experimental::details::streamcompatibility_receiver_validator make_node_implementation_streamcompatibility_receiver_validator() { - return nmos::experimental::make_streamcompatibility_receiver_validator(make_node_implementation_transport_file_parser(), gate); + // this example uses a custom transport file validator to handle video/jxsv in addition to the core media types + const auto transport_file_validator = std::bind( + nmos::experimental::details::validate_rtp_transport_file, + impl::validate_sdp_parameters, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3 + ); + return nmos::experimental::make_streamcompatibility_receiver_validator(transport_file_validator); } namespace impl @@ -1770,5 +1779,5 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode .on_set_effective_edid(set_effective_edid) // may be omitted if not required .on_active_constraints_changed(make_node_implementation_streamcompatibility_active_constraints_handler(model, gate)) .on_validate_sender_resources_against_active_constraints(make_node_implementation_streamcompatibility_sender_validator()) // may be omitted if the default is sufficient - .on_validate_receiver_against_transport_file(make_node_implementation_streamcompatibility_receiver_validator(gate)); + .on_validate_receiver_against_transport_file(make_node_implementation_streamcompatibility_receiver_validator()); } diff --git a/Development/nmos/node_server.h b/Development/nmos/node_server.h index d390185d6..22889009b 100644 --- a/Development/nmos/node_server.h +++ b/Development/nmos/node_server.h @@ -46,6 +46,7 @@ namespace nmos node_implementation() : parse_transport_file(&nmos::parse_rtp_transport_file) , validate_sender_resources(make_streamcompatibility_sender_resources_validator(&nmos::experimental::match_resource_parameters_constraint_set, make_streamcompatibility_sdp_constraint_sets_matcher(&nmos::match_sdp_parameters_constraint_sets))) + , validate_receiver(make_streamcompatibility_receiver_validator(&nmos::experimental::validate_rtp_transport_file)) {} node_implementation& on_load_server_certificates(nmos::load_server_certificates_handler load_server_certificates) { this->load_server_certificates = std::move(load_server_certificates); return *this; } diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index a0e6811db..746d51027 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -178,7 +178,7 @@ namespace nmos if (validate_receiver) { - std::tie(receiver_state, receiver_state_debug) = validate_receiver(transport_file, *receiver, *connection_receiver); + std::tie(receiver_state, receiver_state_debug) = validate_receiver(*receiver, transport_file); } if (nmos::fields::state(nmos::fields::status(streamcompatibility_receiver->data)) != receiver_state.name) diff --git a/Development/nmos/streamcompatibility_validation.cpp b/Development/nmos/streamcompatibility_validation.cpp index bf7dee354..810ed3ba6 100644 --- a/Development/nmos/streamcompatibility_validation.cpp +++ b/Development/nmos/streamcompatibility_validation.cpp @@ -1,5 +1,6 @@ #include "nmos/streamcompatibility_validation.h" +#include "nmos/connection_api.h" #include "nmos/json_fields.h" #include "nmos/model.h" #include "nmos/resource.h" @@ -28,6 +29,26 @@ namespace nmos } + void validate_rtp_transport_file(const nmos::resource& receiver, const utility::string_t& transport_file_type, const utility::string_t& transport_file_data) + { + return details::validate_rtp_transport_file(&validate_sdp_parameters, receiver, transport_file_type, transport_file_data); + } + + void details::validate_rtp_transport_file(nmos::details::sdp_parameters_validator validate_sdp_parameters, const nmos::resource& receiver, const utility::string_t& transport_file_type, const utility::string_t& transport_file_data) + { + if (transport_file_type != nmos::media_types::application_sdp.name) + { + throw std::runtime_error("unexpected type: " + utility::us2s(transport_file_type)); + } + + const auto session_description = sdp::parse_session_description(utility::us2s(transport_file_data)); + auto sdp_transport_params = nmos::parse_session_description(session_description); + + // Validate transport file according to the IS-04 receiver + + validate_sdp_parameters(receiver.data, sdp_transport_params.first); + } + // "At any time if State of an active Sender becomes active_constraints_violation, the Sender MUST become inactive. // An inactive Sender in this state MUST NOT allow activations. // At any time if State of an active Receiver becomes non_compliant_stream, the Receiver SHOULD become inactive. @@ -122,9 +143,9 @@ namespace nmos }; } - details::streamcompatibility_receiver_validator make_streamcompatibility_receiver_validator(const nmos::transport_file_parser& parse_and_validate_transport_file, slog::base_gate& gate) + details::streamcompatibility_receiver_validator make_streamcompatibility_receiver_validator(const details::transport_file_validator& validate_transport_file) { - return [parse_and_validate_transport_file, &gate](const web::json::value& transport_file_, const nmos::resource& receiver, const nmos::resource& connection_receiver) -> std::pair + return [validate_transport_file](const nmos::resource& receiver, const web::json::value& transport_file_) -> std::pair { nmos::receiver_state receiver_state = nmos::receiver_states::unknown; utility::string_t receiver_state_debug; @@ -138,11 +159,10 @@ namespace nmos try { - parse_and_validate_transport_file(receiver, connection_receiver, transport_file.first, transport_file.second, gate); + validate_transport_file(receiver, transport_file.first, transport_file.second); } catch (const std::runtime_error& e) { - slog::log(gate, SLOG_FLF) << "Validation of receiver " << receiver.id << " failed with " << e.what(); receiver_state = nmos::receiver_states::non_compliant_stream; receiver_state_debug = utility::conversions::to_string_t(e.what()); } diff --git a/Development/nmos/streamcompatibility_validation.h b/Development/nmos/streamcompatibility_validation.h index 7824c2380..26f2cdbb2 100644 --- a/Development/nmos/streamcompatibility_validation.h +++ b/Development/nmos/streamcompatibility_validation.h @@ -30,13 +30,22 @@ namespace nmos // returns Sender's "state" and "debug" values typedef std::function(const nmos::resource& source, const nmos::resource& flow, const nmos::resource& sender, const nmos::resource& connection_sender, const web::json::array& constraint_sets)> streamcompatibility_sender_validator; // returns Receiver's "state" and "debug" values - typedef std::function(const web::json::value& transport_file, const nmos::resource& receiver, const nmos::resource& connection_receiver)> streamcompatibility_receiver_validator; + typedef std::function(const nmos::resource& receiver, const web::json::value& transport_file)> streamcompatibility_receiver_validator; + + typedef std::function transport_file_validator; typedef std::function resource_constraints_matcher; typedef std::function& transport_file, const web::json::array& constraint_sets)> transport_file_constraint_sets_matcher; typedef std::function sdp_constraint_sets_matcher; + + // Validate the specified transport file for the specified receiver using the specified validator + void validate_rtp_transport_file(nmos::details::sdp_parameters_validator validate_sdp_parameters, const nmos::resource& receiver, const utility::string_t& transport_file_type, const utility::string_t& transport_file_data); } + // Validate the specified transport file for the specified receiver using the default validator + // (this is the default transport file validator) + void validate_rtp_transport_file(const nmos::resource& receiver, const utility::string_t& transport_file_type, const utility::string_t& transport_file_data); + typedef std::map> parameter_constraints; // NMOS Parameter Registers - Capabilities register @@ -89,7 +98,7 @@ namespace nmos nmos::details::connection_resource_patch_validator make_connection_streamcompatibility_validator(nmos::node_model& model); details::streamcompatibility_sender_validator make_streamcompatibility_sender_resources_validator(const details::resource_constraints_matcher& resource_matcher, const details::transport_file_constraint_sets_matcher& transport_file_matcher); - details::streamcompatibility_receiver_validator make_streamcompatibility_receiver_validator(const nmos::transport_file_parser& parse_and_validate_transport_file, slog::base_gate& gate); + details::streamcompatibility_receiver_validator make_streamcompatibility_receiver_validator(const details::transport_file_validator& validate_transport_file); bool match_resource_parameters_constraint_set(const nmos::resource& resource, const web::json::value& constraint_set); details::transport_file_constraint_sets_matcher make_streamcompatibility_sdp_constraint_sets_matcher(const details::sdp_constraint_sets_matcher& match_sdp_parameters_constraint_sets); } diff --git a/Development/nmos/video_jxsv.cpp b/Development/nmos/video_jxsv.cpp index 8e7450b19..dc1ba4e65 100644 --- a/Development/nmos/video_jxsv.cpp +++ b/Development/nmos/video_jxsv.cpp @@ -344,24 +344,21 @@ namespace nmos return constraint_sets.end() != found; } - namespace experimental + bool experimental::match_video_jxsv_resource_parameters_constraint_set(const nmos::resource& resource, const web::json::value& constraint_set) { - bool match_video_jxsv_resource_parameters_constraint_set(const nmos::resource& resource, const web::json::value& constraint_set) + const std::map resource_parameter_constraints { - const std::map resource_parameter_constraints - { - { nmos::types::source, nmos::experimental::source_parameter_constraints }, - { nmos::types::flow, nmos::experimental::video_jxsv_flow_parameter_constraints }, - { nmos::types::sender, nmos::experimental::video_jxsv_sender_parameter_constraints } - }; + { nmos::types::source, nmos::experimental::source_parameter_constraints }, + { nmos::types::flow, nmos::experimental::video_jxsv_flow_parameter_constraints }, + { nmos::types::sender, nmos::experimental::video_jxsv_sender_parameter_constraints } + }; - if (0 == resource_parameter_constraints.count(resource.type)) - { - throw std::logic_error("wrong resource type"); - } + if (0 == resource_parameter_constraints.count(resource.type)) + { + throw std::logic_error("wrong resource type"); + } - return nmos::experimental::detail::match_resource_parameters_constraint_set(resource_parameter_constraints.at(resource.type), resource.data, constraint_set); - }; + return nmos::experimental::detail::match_resource_parameters_constraint_set(resource_parameter_constraints.at(resource.type), resource.data, constraint_set); } // See https://specs.amwa.tv/bcp-006-01/branches/v1.0-dev/docs/NMOS_With_JPEG_XS.html#flows From 4edcf0c876c8810bf6237069f33f08f5d03c0786 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 5 May 2023 17:08:47 +0400 Subject: [PATCH 080/109] Simplify make_node_implementation_streamcompatibility_active_constraints_handler --- Development/nmos-cpp-node/node_implementation.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 22a95e259..18a5c6064 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1551,8 +1551,7 @@ nmos::experimental::details::streamcompatibility_active_constraints_handler make const auto& sender_capabilities_ = video_found ? video_sender_capabilities : audio_found ? audio_sender_capabilities : throw std::logic_error("No Sender Capabilities found"); const auto& sender_capabilities = sender_capabilities_.as_array(); - std::vector v(constraint_sets.size() * sender_capabilities.size()); - auto iter = v.begin(); + std::vector v; for (const auto& constraint_set : constraint_sets) { @@ -1562,12 +1561,11 @@ nmos::experimental::details::streamcompatibility_active_constraints_handler make const auto intersection = nmos::experimental::get_constraint_set_intersection(sender_caps_constraint_set, constraint_set); if (!intersection.is_null()) { - *iter++ = std::move(intersection); + v.push_back(intersection); } } } - v.resize(iter - v.begin()); if (v.empty()) { slog::log(gate, SLOG_FLF) << "Sender " << sender_id << " doesn't support proposed Active Constraints"; From 4a37cd8a8f4c1f8cbec1dccdd06b63370f69d84c Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 5 May 2023 17:10:04 +0400 Subject: [PATCH 081/109] constraint_value_less: detail -> details --- Development/nmos/constraints.cpp | 18 +++++++++--------- Development/nmos/constraints.h | 2 +- Development/nmos/test/constraints_test.cpp | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index 83ea8016d..4e2d32679 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -15,7 +15,7 @@ namespace nmos { namespace experimental { - namespace detail + namespace details { bool constraint_value_less(const web::json::value& lhs, const web::json::value& rhs) { @@ -33,12 +33,12 @@ namespace nmos web::json::value get_intersection(const web::json::array& lhs_, const web::json::array& rhs_) { - std::set lhs(lhs_.begin(), lhs_.end(), &detail::constraint_value_less); - std::set rhs(rhs_.begin(), rhs_.end(), &detail::constraint_value_less); + std::set lhs(lhs_.begin(), lhs_.end(), &details::constraint_value_less); + std::set rhs(rhs_.begin(), rhs_.end(), &details::constraint_value_less); std::vector v(std::min(lhs.size(), rhs.size())); - const auto it = std::set_intersection(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), v.begin(), detail::constraint_value_less); + const auto it = std::set_intersection(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), v.begin(), details::constraint_value_less); v.resize(it - v.begin()); return web::json::value_from_elements(v); @@ -73,7 +73,7 @@ namespace nmos // "minimum" if (lhs.has_field(nmos::fields::constraint_minimum) && rhs.has_field(nmos::fields::constraint_minimum)) { - result[nmos::fields::constraint_minimum] = std::max(nmos::fields::constraint_minimum(lhs), nmos::fields::constraint_minimum(rhs), detail::constraint_value_less); + result[nmos::fields::constraint_minimum] = std::max(nmos::fields::constraint_minimum(lhs), nmos::fields::constraint_minimum(rhs), details::constraint_value_less); } else if (lhs.has_field(nmos::fields::constraint_minimum)) { @@ -87,7 +87,7 @@ namespace nmos // "maximum" if (lhs.has_field(nmos::fields::constraint_maximum) && rhs.has_field(nmos::fields::constraint_maximum)) { - result[nmos::fields::constraint_maximum] = std::min(nmos::fields::constraint_maximum(lhs), nmos::fields::constraint_maximum(rhs), detail::constraint_value_less); + result[nmos::fields::constraint_maximum] = std::min(nmos::fields::constraint_maximum(lhs), nmos::fields::constraint_maximum(rhs), details::constraint_value_less); } else if (lhs.has_field(nmos::fields::constraint_maximum)) { @@ -101,7 +101,7 @@ namespace nmos // "min" > "max" if (result.has_field(nmos::fields::constraint_minimum) && result.has_field(nmos::fields::constraint_maximum)) { - if (detail::constraint_value_less(nmos::fields::constraint_maximum(result), nmos::fields::constraint_minimum(result))) + if (details::constraint_value_less(nmos::fields::constraint_maximum(result), nmos::fields::constraint_minimum(result))) { return web::json::value::null(); } @@ -208,7 +208,7 @@ namespace nmos const auto& constraint_min = nmos::fields::constraint_minimum(constraint); const auto& subconstraint_min = nmos::fields::constraint_minimum(subconstraint); - if (detail::constraint_value_less(subconstraint_min, constraint_min)) + if (details::constraint_value_less(subconstraint_min, constraint_min)) { return false; } @@ -218,7 +218,7 @@ namespace nmos const auto& constraint_max = nmos::fields::constraint_maximum(constraint); const auto& subconstraint_max = nmos::fields::constraint_maximum(subconstraint); - if (detail::constraint_value_less(constraint_max, subconstraint_max)) + if (details::constraint_value_less(constraint_max, subconstraint_max)) { return false; } diff --git a/Development/nmos/constraints.h b/Development/nmos/constraints.h index fd93f3c55..9aa7aa52f 100644 --- a/Development/nmos/constraints.h +++ b/Development/nmos/constraints.h @@ -13,7 +13,7 @@ namespace nmos { namespace experimental { - namespace detail + namespace details { bool constraint_value_less(const web::json::value& lhs, const web::json::value& rhs); } diff --git a/Development/nmos/test/constraints_test.cpp b/Development/nmos/test/constraints_test.cpp index f1db209a2..2927591c0 100644 --- a/Development/nmos/test/constraints_test.cpp +++ b/Development/nmos/test/constraints_test.cpp @@ -11,7 +11,7 @@ BST_TEST_CASE(testJsonComparator) { { - using nmos::experimental::detail::constraint_value_less; + using nmos::experimental::details::constraint_value_less; const auto a = nmos::make_caps_rational_constraint({}, nmos::rates::rate25, nmos::rates::rate30); const auto b = nmos::make_caps_rational_constraint({}, nmos::rates::rate25, nmos::rates::rate29_97); From bd128a345c222e788757e852612a1c76a5661627 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 5 May 2023 17:20:47 +0400 Subject: [PATCH 082/109] Restore filtering out "meta" in get_constraint_set_intersection --- Development/nmos/constraints.cpp | 30 ++++++++++------- Development/nmos/test/constraints_test.cpp | 38 ++++++++++++++++++++++ 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index 4e2d32679..60978b27f 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -142,40 +142,46 @@ namespace nmos auto lhs_iter = lhs.begin(); auto rhs_iter = rhs.begin(); + const auto insert_if_not_meta = [](value& j, web::json::object::const_iterator property) { + if (!boost::algorithm::starts_with(property->first, U("urn:x-nmos:cap:meta:"))) + { + j[property->first] = property->second; + } + }; + while (lhs_iter != lhs.end() || rhs_iter != rhs.end()) { if (lhs_iter != lhs.end() && rhs_iter != rhs.end()) { if (lhs_iter->first < rhs_iter->first) { - result[lhs_iter->first] = lhs_iter->second; - lhs_iter++; + insert_if_not_meta(result, lhs_iter++); } else if (lhs_iter->first > rhs_iter->first) { - result[rhs_iter->first] = rhs_iter->second; - rhs_iter++; + insert_if_not_meta(result, rhs_iter++); } else { - const value intersection = get_constraint_intersection(lhs_iter->second, rhs_iter->second); - if (intersection.is_null()) + if (!boost::algorithm::starts_with(lhs_iter->first, U("urn:x-nmos:cap:meta:"))) { - return value::null(); + const value intersection = get_constraint_intersection(lhs_iter->second, rhs_iter->second); + if (intersection.is_null()) + { + return value::null(); + } + result[lhs_iter->first] = intersection; } - result[lhs_iter->first] = intersection; lhs_iter++; rhs_iter++; } } else if (lhs_iter == lhs.end()) { - result[rhs_iter->first] = rhs_iter->second; - rhs_iter++; + insert_if_not_meta(result, rhs_iter++); } else if (rhs_iter == rhs.end()) { - result[lhs_iter->first] = lhs_iter->second; - lhs_iter++; + insert_if_not_meta(result, lhs_iter++); } } diff --git a/Development/nmos/test/constraints_test.cpp b/Development/nmos/test/constraints_test.cpp index 2927591c0..334b8e6d7 100644 --- a/Development/nmos/test/constraints_test.cpp +++ b/Development/nmos/test/constraints_test.cpp @@ -383,3 +383,41 @@ BST_TEST_CASE(testConstraintSetIntersectionOfEmptyAndNonEmpty) BST_REQUIRE_EQUAL(get_constraint_set_intersection(b, a), b); } } + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testConstraintSetIntersectionMeta) +{ + { + using web::json::value_of; + using nmos::experimental::get_constraint_set_intersection; + + const auto a = value_of({ + { nmos::caps::meta::label, U("test1") }, + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25 }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_psf.name, nmos::interlace_modes::interlaced_tff.name }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({ 10 }) } + }); + + const auto b = value_of({ + { nmos::caps::meta::label, U("test2") }, + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25 }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({ 10 }) } + }); + + const auto c = value_of({ + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ nmos::rates::rate25 }) }, + { nmos::caps::format::frame_width, nmos::make_caps_integer_constraint({ 1920 }) }, + { nmos::caps::format::frame_height, nmos::make_caps_integer_constraint({ 1080 }) }, + { nmos::caps::format::interlace_mode, nmos::make_caps_string_constraint({ nmos::interlace_modes::interlaced_psf.name, nmos::interlace_modes::interlaced_tff.name }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({ 10 }) } + }); + + BST_REQUIRE_EQUAL(get_constraint_set_intersection(a, b), c); + BST_REQUIRE_EQUAL(get_constraint_set_intersection(b, a), c); + } +} From ca0b38ac5f07cd9109b8c7b750e62dfc191bbadc Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Mon, 22 May 2023 13:00:09 +0400 Subject: [PATCH 083/109] Fix '"enum" matches "min"/"max"' case --- Development/nmos/constraints.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Development/nmos/constraints.cpp b/Development/nmos/constraints.cpp index 60978b27f..975c52bd7 100644 --- a/Development/nmos/constraints.cpp +++ b/Development/nmos/constraints.cpp @@ -110,14 +110,23 @@ namespace nmos // "enum" matches "min"/"max" if (result.has_field(nmos::fields::constraint_enum) && (result.has_field(nmos::fields::constraint_minimum) || result.has_field(nmos::fields::constraint_maximum))) { - result[nmos::fields::constraint_enum] = get_intersection(nmos::fields::constraint_enum(result).as_array(), result); - for (const auto& keyword : { nmos::fields::constraint_minimum, nmos::fields::constraint_maximum }) + const auto remove_keywords = [](web::json::value constraint, const std::vector& keywords) { - if (result.has_field(keyword)) + for (const auto& keyword : keywords) { - result.erase(keyword); + if (constraint.has_field(keyword)) + { + constraint.erase(keyword); + } } - } + return constraint; + }; + + result[nmos::fields::constraint_enum] = get_intersection(nmos::fields::constraint_enum(result).as_array(), remove_keywords(result, { nmos::fields::constraint_enum })); + + // "The Parameter Constraint is satisfied if all of the constraints expressed by the Constraint Keywords are satisfied." + // After "enum" lost any values out of [min, max], the Parameter Constraint can be simplified by removing "min" and "max" + result = remove_keywords(result, { nmos::fields::constraint_minimum, nmos::fields::constraint_maximum }); } // "enum" is empty From c17013fc11e5daa808fd859083f9fd3b6db6e9d5 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Tue, 23 May 2023 02:18:15 +0400 Subject: [PATCH 084/109] Add settings for IS-11 implementation control --- Development/nmos-cpp-node/config.json | 6 ++ .../nmos-cpp-node/node_implementation.cpp | 53 +++++++---- .../nmos/streamcompatibility_behaviour.cpp | 89 +++++++++++-------- 3 files changed, 90 insertions(+), 58 deletions(-) diff --git a/Development/nmos-cpp-node/config.json b/Development/nmos-cpp-node/config.json index 4d794e20b..189a1bb7e 100644 --- a/Development/nmos-cpp-node/config.json +++ b/Development/nmos-cpp-node/config.json @@ -63,6 +63,12 @@ // edid_support: controls whether inputs and output have EDID support //"edid_support": false, + // streamcompatibility_index: specifies index of video/audio sender/receiver for marking as IS-11 compatible + //"streamcompatibility_index": 1, + + // volatile_status_of_sender: specifies whether Status of Sender voluntarily changes (for demo purposes) + //"volatile_status_of_sender": false, + // Configuration settings and defaults for logging // error_log [registry, node]: filename for the error log or an empty string to write to stderr diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 18a5c6064..61dd48b45 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -122,6 +122,12 @@ namespace impl // edid_support: controls whether inputs and output have EDID support const web::json::field_as_bool_or edid_support{ U("edid_support"), false }; + + // streamcompatibility_index: specifies index of video/audio sender/receiver for marking as IS-11 compatible + const web::json::field_as_integer_or streamcompatibility_index{ U("streamcompatibility_index"), 1 }; + + // volatile_status_of_sender: specifies whether Status of Sender voluntarily changes (for demo purposes) + const web::json::field_as_bool_or volatile_status_of_sender{ U("volatile_status_of_sender"), false }; } nmos::interlace_mode get_interlace_mode(const nmos::settings& settings); @@ -282,6 +288,7 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) const auto channel_count = impl::fields::channel_count(model.settings); const auto smpte2022_7 = impl::fields::smpte2022_7(model.settings); const auto edid_support = impl::fields::edid_support(model.settings); + const auto streamcompatibility_index = impl::fields::streamcompatibility_index(model.settings); // for now, some typical values for video/jxsv, based on VSF TR-08:2022 // see https://vsf.tv/download/technical_recommendations/VSF_TR-08_2022-04-20.pdf @@ -939,14 +946,7 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) utility::string_t edid(edid_bytes, edid_bytes + sizeof(edid_bytes)); const auto input_id = impl::make_id(seed_id, nmos::types::input); - - // Associate the last created video and audio Senders with this Input - std::vector sender_ids; - int index = how_many - 1; - for (const auto& port : { impl::ports::video, impl::ports::audio }) - { - sender_ids.push_back(impl::make_id(seed_id, nmos::types::sender, port, index)); - } + const auto sender_ids = impl::make_ids(seed_id, nmos::types::sender, { impl::ports::video, impl::ports::audio }, streamcompatibility_index); auto input = edid_support ? nmos::experimental::make_streamcompatibility_input(input_id, true, true, edid, bst::nullopt, sender_ids, model.settings) @@ -980,7 +980,7 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) for (const auto& port : { impl::ports::video, impl::ports::audio }) { - const auto sender_id = impl::make_id(seed_id, nmos::types::sender, port, index); + const auto sender_id = impl::make_id(seed_id, nmos::types::sender, port, streamcompatibility_index); const auto& supported_param_constraints = port == impl::ports::video ? video_parameter_constraints : audio_parameter_constraints; auto streamcompatibility_sender = nmos::experimental::make_streamcompatibility_sender(sender_id, { input_id }, supported_param_constraints); if (!insert_resource_after(delay_millis, model.streamcompatibility_resources, std::move(streamcompatibility_sender), gate)) return; @@ -1010,14 +1010,7 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) utility::string_t edid(edid_bytes, edid_bytes + sizeof(edid_bytes)); const auto output_id = impl::make_id(seed_id, nmos::types::output); - - // Associate the last created video and audio Receivers with this Output - std::vector receiver_ids; - int index = how_many - 1; - for (const auto& port : { impl::ports::video, impl::ports::audio }) - { - receiver_ids.push_back(impl::make_id(seed_id, nmos::types::receiver, port, index)); - } + const auto receiver_ids = impl::make_ids(seed_id, nmos::types::receiver, { impl::ports::video, impl::ports::audio }, streamcompatibility_index); auto output = edid_support ? nmos::experimental::make_streamcompatibility_output(output_id, true, boost::variant(edid), bst::nullopt, receiver_ids, model.settings) @@ -1042,6 +1035,9 @@ void node_implementation_run(nmos::node_model& model, slog::base_gate& gate) const auto sender_ports = impl::parse_ports(impl::fields::senders(model.settings)); const auto ws_sender_ports = boost::copy_range>(sender_ports | boost::adaptors::filtered(impl::is_ws_port)); + const auto streamcompatibility_index = impl::fields::streamcompatibility_index(model.settings); + const auto update_sender = impl::fields::volatile_status_of_sender(model.settings); + // start background tasks to intermittently update the state of the event sources, to cause events to be emitted to connected receivers nmos::details::seed_generator events_seeder; @@ -1049,10 +1045,10 @@ void node_implementation_run(nmos::node_model& model, slog::base_gate& gate) auto cancellation_source = pplx::cancellation_token_source(); auto token = cancellation_source.get_token(); - auto events = pplx::do_while([&model, seed_id, how_many, ws_sender_ports, events_engine, &gate, token] + auto events = pplx::do_while([&model, seed_id, how_many, ws_sender_ports, streamcompatibility_index, update_sender, events_engine, &gate, token] { const auto event_interval = std::uniform_real_distribution<>(0.5, 5.0)(*events_engine); - return pplx::complete_after(std::chrono::milliseconds(std::chrono::milliseconds::rep(1000 * event_interval)), token).then([&model, seed_id, how_many, ws_sender_ports, events_engine, &gate] + return pplx::complete_after(std::chrono::milliseconds(std::chrono::milliseconds::rep(1000 * event_interval)), token).then([&model, seed_id, how_many, ws_sender_ports, streamcompatibility_index, update_sender, events_engine, &gate] { auto lock = model.write_lock(); @@ -1095,6 +1091,25 @@ void node_implementation_run(nmos::node_model& model, slog::base_gate& gate) slog::log(gate, SLOG_FLF) << "Temperature updated: " << temp.scaled_value() << " (" << impl::temperature_Celsius.name << ")"; + if (update_sender) + { + const auto streamcompatibility_video_sender_id = impl::make_id(seed_id, nmos::types::sender, impl::ports::video, streamcompatibility_index); + const auto states_of_sender = { U("no_essence"), U("awaiting_essence"), U("constrained") }; + const auto& state_of_sender = *(states_of_sender.begin() + (std::min)(std::geometric_distribution()(*events_engine), states_of_sender.size() - 1)); + + modify_resource(model.streamcompatibility_resources, streamcompatibility_video_sender_id, [&](nmos::resource& resource) + { + resource.data[nmos::fields::status] = web::json::value_of({ { nmos::fields::state, state_of_sender } }); + }); + + modify_resource(model.node_resources, streamcompatibility_video_sender_id, [&](nmos::resource& resource) + { + resource.data[nmos::fields::version] = web::json::value::string(nmos::make_version()); + }); + + slog::log(gate, SLOG_FLF) << "Status of Sender " << streamcompatibility_video_sender_id << " updated: " << state_of_sender; + } + model.notify(); return true; diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index 746d51027..31ff456fc 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -33,6 +33,52 @@ namespace nmos })); } + nmos::receiver_state update_status_of_receiver(nmos::node_model& model, const nmos::id& receiver_id, const details::streamcompatibility_receiver_validator& validate_receiver, slog::base_gate& gate) + { + using web::json::value; + + auto& node_resources = model.node_resources; + auto& connection_resources = model.connection_resources; + auto& streamcompatibility_resources = model.streamcompatibility_resources; + + const std::pair receiver_id_type{ receiver_id, nmos::types::receiver }; + + auto node_receiver = find_resource(node_resources, receiver_id_type); + if (node_resources.end() == node_receiver) throw std::logic_error("Matching IS-04 receiver not found"); + + auto connection_receiver = find_resource(connection_resources, receiver_id_type); + if (connection_resources.end() == connection_receiver) throw std::logic_error("Matching IS-05 receiver not found"); + + auto streamcompatibility_receiver = find_resource(streamcompatibility_resources, receiver_id_type); + if (streamcompatibility_resources.end() == streamcompatibility_receiver) throw std::logic_error("Matching IS-11 receiver not found"); + + nmos::receiver_state receiver_state(nmos::receiver_states::unknown); + utility::string_t receiver_state_debug; + + const auto& transport_file = nmos::fields::transport_file(nmos::fields::endpoint_active(connection_receiver->data)); + + if (validate_receiver) + { + std::tie(receiver_state, receiver_state_debug) = validate_receiver(*node_receiver, transport_file); + } + + if (nmos::fields::state(nmos::fields::status(streamcompatibility_receiver->data)) != receiver_state.name) + { + modify_resource(streamcompatibility_resources, receiver_id, [&receiver_state, &receiver_state_debug, &gate](nmos::resource& receiver) + { + nmos::fields::status(receiver.data)[nmos::fields::state] = value::string(receiver_state.name); + if (!receiver_state_debug.empty()) + { + nmos::fields::status(receiver.data)[nmos::fields::debug] = value::string(receiver_state_debug); + } + }); + + update_version(node_resources, receiver_id, nmos::make_version()); + } + + return receiver_state; + } + void streamcompatibility_behaviour_thread(nmos::node_model& model, details::streamcompatibility_sender_validator validate_sender_resources, details::streamcompatibility_receiver_validator validate_receiver, slog::base_gate& gate) { using web::json::value; @@ -157,48 +203,13 @@ namespace nmos auto receiver = find_resource(node_resources, receiver_id_type); if (node_resources.end() == receiver) throw std::logic_error("Matching IS-04 receiver not found"); - updated = most_recent_update < nmos::fields::version(nmos::fields::caps(receiver->data)); + updated = most_recent_update < nmos::fields::version(receiver->data); if (updated) { - slog::log(gate, SLOG_FLF) << "Receiver " << receiver_id << " has been updated recently and Receiver State is being updated as well"; - - const std::pair streamcompatibility_receiver_id_type{ receiver_id, nmos::types::receiver }; - auto streamcompatibility_receiver = find_resource(streamcompatibility_resources, streamcompatibility_receiver_id_type); - if (streamcompatibility_resources.end() == streamcompatibility_receiver) throw std::logic_error("Matching IS-11 receiver not found"); - - nmos::receiver_state receiver_state(nmos::receiver_states::unknown); - utility::string_t receiver_state_debug; - - const std::pair connection_receiver_id_type{ receiver_id, nmos::types::receiver }; - auto connection_receiver = find_resource(connection_resources, connection_receiver_id_type); - if (connection_resources.end() == connection_receiver) throw std::logic_error("Matching IS-05 receiver not found"); + slog::log(gate, SLOG_FLF) << "Receiver " << receiver_id << " has been updated recently and Status of Receiver is being updated as well"; - const auto& transport_file = nmos::fields::transport_file(nmos::fields::endpoint_active(connection_receiver->data)); - - if (validate_receiver) - { - std::tie(receiver_state, receiver_state_debug) = validate_receiver(*receiver, transport_file); - } - - if (nmos::fields::state(nmos::fields::status(streamcompatibility_receiver->data)) != receiver_state.name) - { - utility::string_t updated_timestamp; - - modify_resource(streamcompatibility_resources, receiver_id, [&receiver_state, &receiver_state_debug, &updated_timestamp, &gate](nmos::resource& receiver) - { - nmos::fields::status(receiver.data)[nmos::fields::state] = web::json::value::string(receiver_state.name); - if (!receiver_state_debug.empty()) - { - nmos::fields::status(receiver.data)[nmos::fields::debug] = web::json::value::string(receiver_state_debug); - } - - updated_timestamp = nmos::make_version(); - receiver.data[nmos::fields::version] = web::json::value::string(updated_timestamp); - }); - - update_version(node_resources, receiver_id, updated_timestamp); - } + nmos::receiver_state receiver_state = update_status_of_receiver(model, receiver_id, validate_receiver, gate); if (receiver_state == nmos::receiver_states::non_compliant_stream) { @@ -221,7 +232,7 @@ namespace nmos } catch (const std::exception& e) { - slog::log(gate, SLOG_FLF) << "Updating receiver status for " << receiver_id << " raised exception: " << e.what(); + slog::log(gate, SLOG_FLF) << "Updating Status of Receiver for " << receiver_id << " raised exception: " << e.what(); continue; } } From 04f1fc9a497df9337a45fb28c6760665b6c8061b Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Tue, 23 May 2023 03:28:10 +0400 Subject: [PATCH 085/109] CI: print test results for failed suites --- Sandbox/run_nmos_testing.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/Sandbox/run_nmos_testing.sh b/Sandbox/run_nmos_testing.sh index e0118a645..82897fe7e 100755 --- a/Sandbox/run_nmos_testing.sh +++ b/Sandbox/run_nmos_testing.sh @@ -169,6 +169,7 @@ function do_run_test() { ;; *) echo "Fail" | tee ${badges_dir}/${suite}.txt echo "${suite} :x:" >> ${summary_path} + cat ${output_file} ;; esac } From bda591e362c3038744e0d23185b71c3c9dbd9036 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Tue, 13 Jun 2023 19:04:10 +0400 Subject: [PATCH 086/109] Fix streamcompatibility_index issue --- Development/nmos-cpp-node/config.json | 2 +- Development/nmos-cpp-node/node_implementation.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Development/nmos-cpp-node/config.json b/Development/nmos-cpp-node/config.json index 189a1bb7e..14f48ff08 100644 --- a/Development/nmos-cpp-node/config.json +++ b/Development/nmos-cpp-node/config.json @@ -64,7 +64,7 @@ //"edid_support": false, // streamcompatibility_index: specifies index of video/audio sender/receiver for marking as IS-11 compatible - //"streamcompatibility_index": 1, + //"streamcompatibility_index": 0, // volatile_status_of_sender: specifies whether Status of Sender voluntarily changes (for demo purposes) //"volatile_status_of_sender": false, diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 61dd48b45..fa2eee9e6 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -124,7 +124,7 @@ namespace impl const web::json::field_as_bool_or edid_support{ U("edid_support"), false }; // streamcompatibility_index: specifies index of video/audio sender/receiver for marking as IS-11 compatible - const web::json::field_as_integer_or streamcompatibility_index{ U("streamcompatibility_index"), 1 }; + const web::json::field_as_integer_or streamcompatibility_index{ U("streamcompatibility_index"), 0 }; // volatile_status_of_sender: specifies whether Status of Sender voluntarily changes (for demo purposes) const web::json::field_as_bool_or volatile_status_of_sender{ U("volatile_status_of_sender"), false }; @@ -946,7 +946,7 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) utility::string_t edid(edid_bytes, edid_bytes + sizeof(edid_bytes)); const auto input_id = impl::make_id(seed_id, nmos::types::input); - const auto sender_ids = impl::make_ids(seed_id, nmos::types::sender, { impl::ports::video, impl::ports::audio }, streamcompatibility_index); + const auto sender_ids = impl::make_ids(seed_id, nmos::types::sender, { impl::ports::video, impl::ports::audio }); auto input = edid_support ? nmos::experimental::make_streamcompatibility_input(input_id, true, true, edid, bst::nullopt, sender_ids, model.settings) @@ -1010,7 +1010,7 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) utility::string_t edid(edid_bytes, edid_bytes + sizeof(edid_bytes)); const auto output_id = impl::make_id(seed_id, nmos::types::output); - const auto receiver_ids = impl::make_ids(seed_id, nmos::types::receiver, { impl::ports::video, impl::ports::audio }, streamcompatibility_index); + const auto receiver_ids = impl::make_ids(seed_id, nmos::types::receiver, { impl::ports::video, impl::ports::audio }); auto output = edid_support ? nmos::experimental::make_streamcompatibility_output(output_id, true, boost::variant(edid), bst::nullopt, receiver_ids, model.settings) From dbfc11d866271ddadfe4033230bed58c4bc3e0e3 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Mon, 13 Feb 2023 13:36:42 +0400 Subject: [PATCH 087/109] Add "device_id" to IS-11 Input/Output --- .../nmos-cpp-node/node_implementation.cpp | 8 ++++---- .../nmos/streamcompatibility_resources.cpp | 19 ++++++++++--------- .../nmos/streamcompatibility_resources.h | 8 ++++---- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index fa2eee9e6..67ca97cb2 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -949,8 +949,8 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) const auto sender_ids = impl::make_ids(seed_id, nmos::types::sender, { impl::ports::video, impl::ports::audio }); auto input = edid_support - ? nmos::experimental::make_streamcompatibility_input(input_id, true, true, edid, bst::nullopt, sender_ids, model.settings) - : nmos::experimental::make_streamcompatibility_input(input_id, true, sender_ids, model.settings); + ? nmos::experimental::make_streamcompatibility_input(input_id, device_id, true, true, edid, bst::nullopt, sender_ids, model.settings) + : nmos::experimental::make_streamcompatibility_input(input_id, device_id, true, sender_ids, model.settings); impl::set_label_description(input, impl::ports::mux, 0); // The single Input consumes both video and audio signals if (!insert_resource_after(delay_millis, model.streamcompatibility_resources, std::move(input), gate)) return; @@ -1013,8 +1013,8 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) const auto receiver_ids = impl::make_ids(seed_id, nmos::types::receiver, { impl::ports::video, impl::ports::audio }); auto output = edid_support - ? nmos::experimental::make_streamcompatibility_output(output_id, true, boost::variant(edid), bst::nullopt, receiver_ids, model.settings) - : nmos::experimental::make_streamcompatibility_output(output_id, true, receiver_ids, model.settings); + ? nmos::experimental::make_streamcompatibility_output(output_id, device_id, true, boost::variant(edid), bst::nullopt, receiver_ids, model.settings) + : nmos::experimental::make_streamcompatibility_output(output_id, device_id, true, receiver_ids, model.settings); impl::set_label_description(output, impl::ports::mux, 0); // The single Output produces both video and audio signals if (!insert_resource_after(delay_millis, model.streamcompatibility_resources, std::move(output), gate)) return; diff --git a/Development/nmos/streamcompatibility_resources.cpp b/Development/nmos/streamcompatibility_resources.cpp index d54ab253c..64b5c297b 100644 --- a/Development/nmos/streamcompatibility_resources.cpp +++ b/Development/nmos/streamcompatibility_resources.cpp @@ -91,36 +91,37 @@ namespace nmos }); } - web::json::value make_streamcompatibility_input_output_base(const nmos::id& id, bool connected, bool edid_support, const nmos::settings& settings) + web::json::value make_streamcompatibility_input_output_base(const nmos::id& id, const nmos::id& device_id, bool connected, bool edid_support, const nmos::settings& settings) { using web::json::value; auto data = details::make_resource_core(id, settings); data[nmos::fields::connected] = value::boolean(connected); + data[nmos::fields::device_id] = value::string(device_id); data[nmos::fields::edid_support] = value::boolean(edid_support); return data; } - nmos::resource make_streamcompatibility_input(const nmos::id& id, bool connected, const std::vector& senders, const nmos::settings& settings) + nmos::resource make_streamcompatibility_input(const nmos::id& id, const nmos::id& device_id, bool connected, const std::vector& senders, const nmos::settings& settings) { using web::json::value_from_elements; using web::json::value_of; - auto data = make_streamcompatibility_input_output_base(id, connected, false, settings); + auto data = make_streamcompatibility_input_output_base(id, device_id, connected, false, settings); data[nmos::fields::senders] = value_from_elements(senders); data[nmos::fields::status] = value_of({ { nmos::fields::state, nmos::input_states::signal_present.name } }); return{ is11_versions::v1_0, types::input, std::move(data), id, false }; } - nmos::resource make_streamcompatibility_input(const nmos::id& id, bool connected, bool base_edid_changeable, const boost::variant& effective_edid, const bst::optional& effective_edid_properties, const std::vector& senders, const nmos::settings& settings) + nmos::resource make_streamcompatibility_input(const nmos::id& id, const nmos::id& device_id, bool connected, bool base_edid_changeable, const boost::variant& effective_edid, const bst::optional& effective_edid_properties, const std::vector& senders, const nmos::settings& settings) { using web::json::value_from_elements; using web::json::value_of; - auto data = make_streamcompatibility_input_output_base(id, connected, true, settings); + auto data = make_streamcompatibility_input_output_base(id, device_id, connected, true, settings); if (base_edid_changeable) { @@ -140,24 +141,24 @@ namespace nmos return{ is11_versions::v1_0, types::input, std::move(data), id, false }; } - nmos::resource make_streamcompatibility_output(const nmos::id& id, bool connected, const std::vector& receivers, const nmos::settings& settings) + nmos::resource make_streamcompatibility_output(const nmos::id& id, const nmos::id& device_id, bool connected, const std::vector& receivers, const nmos::settings& settings) { using web::json::value_from_elements; using web::json::value_of; - auto data = make_streamcompatibility_input_output_base(id, connected, false, settings); + auto data = make_streamcompatibility_input_output_base(id, device_id, connected, false, settings); data[nmos::fields::receivers] = value_from_elements(receivers); data[nmos::fields::status] = value_of({ { nmos::fields::state, nmos::output_states::signal_present.name } }); return{ is11_versions::v1_0, types::output, std::move(data), id, false }; } - nmos::resource make_streamcompatibility_output(const nmos::id& id, bool connected, const bst::optional>& edid, const bst::optional& edid_properties, const std::vector& receivers, const nmos::settings& settings) + nmos::resource make_streamcompatibility_output(const nmos::id& id, const nmos::id& device_id, bool connected, const bst::optional>& edid, const bst::optional& edid_properties, const std::vector& receivers, const nmos::settings& settings) { using web::json::value_from_elements; using web::json::value_of; - auto data = make_streamcompatibility_input_output_base(id, connected, true, settings); + auto data = make_streamcompatibility_input_output_base(id, device_id, connected, true, settings); data[nmos::fields::receivers] = value_from_elements(receivers); data[nmos::fields::status] = value_of({ { nmos::fields::state, nmos::output_states::signal_present.name } }); diff --git a/Development/nmos/streamcompatibility_resources.h b/Development/nmos/streamcompatibility_resources.h index 77b4158ba..e9cf15c37 100644 --- a/Development/nmos/streamcompatibility_resources.h +++ b/Development/nmos/streamcompatibility_resources.h @@ -26,15 +26,15 @@ namespace nmos // See https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/input.html // Makes an input without EDID support - nmos::resource make_streamcompatibility_input(const nmos::id& id, bool connected, const std::vector& senders, const nmos::settings& settings); + nmos::resource make_streamcompatibility_input(const nmos::id& id, const nmos::id& device_id, bool connected, const std::vector& senders, const nmos::settings& settings); // Makes an input with EDID support - nmos::resource make_streamcompatibility_input(const nmos::id& id, bool connected, bool base_edid_changeable, const boost::variant& effective_edid, const bst::optional& effective_edid_properties, const std::vector& senders, const nmos::settings& settings); + nmos::resource make_streamcompatibility_input(const nmos::id& id, const nmos::id& device_id, bool connected, bool base_edid_changeable, const boost::variant& effective_edid, const bst::optional& effective_edid_properties, const std::vector& senders, const nmos::settings& settings); // See https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/output.html // Makes an output without EDID support - nmos::resource make_streamcompatibility_output(const nmos::id& id, bool connected, const std::vector& receivers, const nmos::settings& settings); + nmos::resource make_streamcompatibility_output(const nmos::id& id, const nmos::id& device_id, bool connected, const std::vector& receivers, const nmos::settings& settings); // Makes an output with EDID support - nmos::resource make_streamcompatibility_output(const nmos::id& id, bool connected, const bst::optional>& edid, const bst::optional& edid_properties, const std::vector& receivers, const nmos::settings& settings); + nmos::resource make_streamcompatibility_output(const nmos::id& id, const nmos::id& device_id, bool connected, const bst::optional>& edid, const bst::optional& edid_properties, const std::vector& receivers, const nmos::settings& settings); struct edid_file_visitor : public boost::static_visitor { From 60fa835d3ae7cf75381901eebeb73e47d218a37c Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Wed, 15 Feb 2023 22:56:35 +0400 Subject: [PATCH 088/109] Add "base_edid_changeable" to Input --- Development/nmos/json_fields.h | 1 + Development/nmos/streamcompatibility_resources.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Development/nmos/json_fields.h b/Development/nmos/json_fields.h index 9c523960d..c3a07146c 100644 --- a/Development/nmos/json_fields.h +++ b/Development/nmos/json_fields.h @@ -240,6 +240,7 @@ namespace nmos // for properties const web::json::field_as_bool connected{ U("connected") }; const web::json::field_as_bool edid_support{ U("edid_support") }; + const web::json::field_as_bool base_edid_changeable{ U("base_edid_changeable") }; const web::json::field_as_object base_edid_properties{ U("base_edid") }; const web::json::field_as_object effective_edid_properties{ U("effective_edid") }; const web::json::field_as_object edid_properties{ U("edid") }; diff --git a/Development/nmos/streamcompatibility_resources.cpp b/Development/nmos/streamcompatibility_resources.cpp index 64b5c297b..e0fa75a16 100644 --- a/Development/nmos/streamcompatibility_resources.cpp +++ b/Development/nmos/streamcompatibility_resources.cpp @@ -106,10 +106,12 @@ namespace nmos nmos::resource make_streamcompatibility_input(const nmos::id& id, const nmos::id& device_id, bool connected, const std::vector& senders, const nmos::settings& settings) { + using web::json::value; using web::json::value_from_elements; using web::json::value_of; auto data = make_streamcompatibility_input_output_base(id, device_id, connected, false, settings); + data[nmos::fields::base_edid_changeable] = value::boolean(false); data[nmos::fields::senders] = value_from_elements(senders); data[nmos::fields::status] = value_of({ { nmos::fields::state, nmos::input_states::signal_present.name } }); @@ -118,6 +120,7 @@ namespace nmos nmos::resource make_streamcompatibility_input(const nmos::id& id, const nmos::id& device_id, bool connected, bool base_edid_changeable, const boost::variant& effective_edid, const bst::optional& effective_edid_properties, const std::vector& senders, const nmos::settings& settings) { + using web::json::value; using web::json::value_from_elements; using web::json::value_of; @@ -135,6 +138,7 @@ namespace nmos data[nmos::fields::effective_edid_properties] = *effective_edid_properties; } + data[nmos::fields::base_edid_changeable] = value::boolean(base_edid_changeable); data[nmos::fields::senders] = value_from_elements(senders); data[nmos::fields::status] = value_of({ { nmos::fields::state, nmos::input_states::signal_present.name } }); From 3c01117c158e2553310671954ed2a34f26082fbc Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sat, 1 Jul 2023 20:48:22 +0400 Subject: [PATCH 089/109] "base_edid_changeable" -> "base_edid_support" --- Development/nmos/json_fields.h | 2 +- Development/nmos/streamcompatibility_resources.cpp | 8 ++++---- Development/nmos/streamcompatibility_resources.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Development/nmos/json_fields.h b/Development/nmos/json_fields.h index c3a07146c..14d78ea4e 100644 --- a/Development/nmos/json_fields.h +++ b/Development/nmos/json_fields.h @@ -240,7 +240,7 @@ namespace nmos // for properties const web::json::field_as_bool connected{ U("connected") }; const web::json::field_as_bool edid_support{ U("edid_support") }; - const web::json::field_as_bool base_edid_changeable{ U("base_edid_changeable") }; + const web::json::field_as_bool base_edid_support{ U("base_edid_support") }; const web::json::field_as_object base_edid_properties{ U("base_edid") }; const web::json::field_as_object effective_edid_properties{ U("effective_edid") }; const web::json::field_as_object edid_properties{ U("edid") }; diff --git a/Development/nmos/streamcompatibility_resources.cpp b/Development/nmos/streamcompatibility_resources.cpp index e0fa75a16..0a3e50e1f 100644 --- a/Development/nmos/streamcompatibility_resources.cpp +++ b/Development/nmos/streamcompatibility_resources.cpp @@ -111,14 +111,14 @@ namespace nmos using web::json::value_of; auto data = make_streamcompatibility_input_output_base(id, device_id, connected, false, settings); - data[nmos::fields::base_edid_changeable] = value::boolean(false); + data[nmos::fields::base_edid_support] = value::boolean(false); data[nmos::fields::senders] = value_from_elements(senders); data[nmos::fields::status] = value_of({ { nmos::fields::state, nmos::input_states::signal_present.name } }); return{ is11_versions::v1_0, types::input, std::move(data), id, false }; } - nmos::resource make_streamcompatibility_input(const nmos::id& id, const nmos::id& device_id, bool connected, bool base_edid_changeable, const boost::variant& effective_edid, const bst::optional& effective_edid_properties, const std::vector& senders, const nmos::settings& settings) + nmos::resource make_streamcompatibility_input(const nmos::id& id, const nmos::id& device_id, bool connected, bool base_edid_support, const boost::variant& effective_edid, const bst::optional& effective_edid_properties, const std::vector& senders, const nmos::settings& settings) { using web::json::value; using web::json::value_from_elements; @@ -126,7 +126,7 @@ namespace nmos auto data = make_streamcompatibility_input_output_base(id, device_id, connected, true, settings); - if (base_edid_changeable) + if (base_edid_support) { data[nmos::fields::endpoint_base_edid] = make_streamcompatibility_dummy_edid_endpoint(false); } @@ -138,7 +138,7 @@ namespace nmos data[nmos::fields::effective_edid_properties] = *effective_edid_properties; } - data[nmos::fields::base_edid_changeable] = value::boolean(base_edid_changeable); + data[nmos::fields::base_edid_support] = value::boolean(base_edid_support); data[nmos::fields::senders] = value_from_elements(senders); data[nmos::fields::status] = value_of({ { nmos::fields::state, nmos::input_states::signal_present.name } }); diff --git a/Development/nmos/streamcompatibility_resources.h b/Development/nmos/streamcompatibility_resources.h index e9cf15c37..c4ac6faf8 100644 --- a/Development/nmos/streamcompatibility_resources.h +++ b/Development/nmos/streamcompatibility_resources.h @@ -28,7 +28,7 @@ namespace nmos // Makes an input without EDID support nmos::resource make_streamcompatibility_input(const nmos::id& id, const nmos::id& device_id, bool connected, const std::vector& senders, const nmos::settings& settings); // Makes an input with EDID support - nmos::resource make_streamcompatibility_input(const nmos::id& id, const nmos::id& device_id, bool connected, bool base_edid_changeable, const boost::variant& effective_edid, const bst::optional& effective_edid_properties, const std::vector& senders, const nmos::settings& settings); + nmos::resource make_streamcompatibility_input(const nmos::id& id, const nmos::id& device_id, bool connected, bool base_edid_support, const boost::variant& effective_edid, const bst::optional& effective_edid_properties, const std::vector& senders, const nmos::settings& settings); // See https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/output.html // Makes an output without EDID support From 17f97216a969295dc316bcb396794938e36808fa Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sat, 1 Jul 2023 20:50:11 +0400 Subject: [PATCH 090/109] Add "default_signal" --- Development/nmos/streamcompatibility_state.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Development/nmos/streamcompatibility_state.h b/Development/nmos/streamcompatibility_state.h index 4fd36a547..5f567a896 100644 --- a/Development/nmos/streamcompatibility_state.h +++ b/Development/nmos/streamcompatibility_state.h @@ -23,6 +23,7 @@ namespace nmos namespace output_states { const output_state no_signal{ U("no_signal") }; + const output_state default_signal{ U("default_signal") }; const output_state signal_present{ U("signal_present") }; } From 28241d0c988840b1ee2654656e901d99b6d36f62 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sat, 1 Jul 2023 20:58:09 +0400 Subject: [PATCH 091/109] Update JSON Schemas --- Development/cmake/NmosCppLibraries.cmake | 2 - Development/nmos/is11_schemas/is11_schemas.h | 2 - .../is-11/v1.0.x/APIs/schemas/edid.json | 83 ------------------- .../v1.0.x/APIs/schemas/edid_timing.json | 46 ---------- .../is-11/v1.0.x/APIs/schemas/input.json | 36 +++++++- .../is-11/v1.0.x/APIs/schemas/output.json | 30 ++++++- .../v1.0.x/APIs/schemas/receiver-status.json | 4 + .../v1.0.x/APIs/schemas/sender-status.json | 8 +- 8 files changed, 71 insertions(+), 140 deletions(-) delete mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/edid.json delete mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/edid_timing.json diff --git a/Development/cmake/NmosCppLibraries.cmake b/Development/cmake/NmosCppLibraries.cmake index d381c335c..b3a9775a5 100644 --- a/Development/cmake/NmosCppLibraries.cmake +++ b/Development/cmake/NmosCppLibraries.cmake @@ -698,8 +698,6 @@ set(NMOS_IS11_V1_0_SCHEMAS_JSON third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/constraints_active.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/constraints-base.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/constraints_supported.json - third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/edid.json - third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/edid_timing.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/empty_constraints_active.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/error.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/input-edid-base.json diff --git a/Development/nmos/is11_schemas/is11_schemas.h b/Development/nmos/is11_schemas/is11_schemas.h index 40f554ef7..7e8174b19 100644 --- a/Development/nmos/is11_schemas/is11_schemas.h +++ b/Development/nmos/is11_schemas/is11_schemas.h @@ -12,8 +12,6 @@ namespace nmos extern const char* constraints_active; extern const char* constraints_base; extern const char* constraints_supported; - extern const char* edid; - extern const char* edid_timing; extern const char* empty_constraints_active; extern const char* error; extern const char* streamcompatibility_api_base; diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/edid.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/edid.json deleted file mode 100644 index dad38ccb9..000000000 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/edid.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema", - "type": "object", - "required": [ - "manufacturer", - "manufacture_week", - "manufacture_year", - "screen_size", - "gamma", - "color_samplings", - "established_timings" - ], - "properties": { - "manufacturer": { - "type": "string" - }, - "manufacture_week": { - "type": "integer" - }, - "manufacture_year": { - "type": "integer" - }, - "screen_size": { - "type": "object", - "required": [ - "width", - "height" - ], - "properties": { - "width": { - "description": "Horizontal screen size, in centimetres", - "type": "integer" - }, - "height": { - "description": "Vertical screen size, in centimetres", - "type": "integer" - } - } - }, - "gamma": { - "type": "number" - }, - "color_samplings": { - "description": "Digital display type in terms of supported subsampling modes", - "type": "array", - "minItems": 1, - "maxItems": 3, - "uniqueItems": true, - "items": { - "type": "string", - "enum": [ - "RGB", - "YCbCr-4:4:4", - "YCbCr-4:2:2" - ] - } - }, - "established_timings": { - "type": "array", - "items": { - "$ref": "edid_timing.json" - } - }, - "standard_timings": { - "type": "array", - "items": { - "$ref": "edid_timing.json" - } - }, - "detailed_timings": { - "type": "array", - "items": { - "$ref": "edid_timing.json" - } - }, - "cta_861_timings": { - "type": "array", - "items": { - "$ref": "edid_timing.json" - } - } - } -} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/edid_timing.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/edid_timing.json deleted file mode 100644 index c2a1d4f4e..000000000 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/edid_timing.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema", - "type": "object", - "required": [ - "frame_width", - "frame_height", - "grain_rate" - ], - "properties": { - "frame_width": { - "type": "integer" - }, - "frame_height": { - "type": "integer" - }, - "grain_rate": { - "description": "Frame rate in Hz", - "type": "object", - "required" : [ - "numerator" - ], - "properties" : { - "numerator" : { - "description" : "Numerator", - "type" : "integer" - }, - "denominator" : { - "description" : "Denominator", - "type" : "integer", - "default" : 1 - } - } - }, - "interlace_mode" : { - "description" : "Interlaced video mode for frames", - "type" : "string", - "default": "progressive", - "enum" : [ - "progressive", - "interlaced_tff", - "interlaced_bff", - "interlaced_psf" - ] - } - } -} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/input.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/input.json index d50efbf2c..d49dda6f3 100644 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/input.json +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/input.json @@ -8,11 +8,17 @@ { "type": "object", "required": [ + "base_edid_support", "connected", - "edid_support" + "edid_support", + "status", + "device_id" ], "properties": { - "base_edid": { "$ref": "edid.json" }, + "base_edid_support": { + "description": "Whether the Input supports Base EDID", + "type": "boolean" + }, "connected": { "description": "Whether the upstream counterpart of this Input is connected", "type": "boolean" @@ -21,7 +27,31 @@ "description": "Whether the Input supports EDID", "type": "boolean" }, - "effective_edid": { "$ref": "edid.json" } + "status": { + "description": "Status of Input", + "required": [ + "state" + ], + "properties": { + "state": { + "type": "string", + "enum": [ + "no_signal", + "awaiting_signal", + "signal_present" + ] + }, + "debug": { + "description": "Debug information which may give additional information on the state", + "type": "string" + } + } + }, + "device_id": { + "description": "Device ID which this Input forms part of", + "type": "string", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + } } } ] diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/output.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/output.json index a3d1ec61d..47d1feab5 100644 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/output.json +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/output.json @@ -9,17 +9,43 @@ "type": "object", "required": [ "connected", - "edid_support" + "edid_support", + "status", + "device_id" ], "properties": { "connected": { "description": "Whether the downstream counterpart of this Output is connected", "type": "boolean" }, - "edid": { "$ref": "edid.json" }, "edid_support": { "description": "Whether the Output supports EDID", "type": "boolean" + }, + "status": { + "description": "Status of Output", + "required": [ + "state" + ], + "properties": { + "state": { + "type": "string", + "enum": [ + "no_signal", + "default_signal", + "signal_present" + ] + }, + "debug": { + "description": "Debug information which may give additional information on the state", + "type": "string" + } + } + }, + "device_id": { + "description": "Device ID which this Output forms part of", + "type": "string", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" } } } diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-status.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-status.json index 8644a65fb..c14df6476 100644 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-status.json +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/receiver-status.json @@ -13,6 +13,10 @@ "compliant_stream", "non_compliant_stream" ] + }, + "debug": { + "description": "Debug information which may give additional information on the state", + "type": "string" } } } diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/sender-status.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/sender-status.json index 9eb98218e..992c4da48 100644 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/sender-status.json +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/sender-status.json @@ -12,9 +12,13 @@ "unconstrained", "constrained", "active_constraints_violation", - "no_signal", - "awaiting_signal" + "no_essence", + "awaiting_essence" ] + }, + "debug": { + "description": "Debug information which may give additional information on the state", + "type": "string" } } } From b24f84d9fc68a393ff5ea2397143412f6bf9bbbb Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Mon, 3 Jul 2023 16:57:17 +0400 Subject: [PATCH 092/109] Add "adjust_to_caps" --- Development/nmos/json_fields.h | 3 ++- Development/nmos/streamcompatibility_api.cpp | 18 +++++++++++++++++- .../nmos/streamcompatibility_resources.cpp | 4 ++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Development/nmos/json_fields.h b/Development/nmos/json_fields.h index 14d78ea4e..1705dc0cf 100644 --- a/Development/nmos/json_fields.h +++ b/Development/nmos/json_fields.h @@ -237,13 +237,14 @@ namespace nmos const web::json::field_as_array outputs{ U("outputs") }; const web::json::field_as_bool temporarily_locked{ U("temporarily_locked") }; - // for properties + // for input/output properties const web::json::field_as_bool connected{ U("connected") }; const web::json::field_as_bool edid_support{ U("edid_support") }; const web::json::field_as_bool base_edid_support{ U("base_edid_support") }; const web::json::field_as_object base_edid_properties{ U("base_edid") }; const web::json::field_as_object effective_edid_properties{ U("effective_edid") }; const web::json::field_as_object edid_properties{ U("edid") }; + const web::json::field_as_bool adjust_to_caps{ U("adjust_to_caps") }; // for sender const web::json::field_as_value endpoint_active_constraints{ U("endpoint_active_constraints") }; // object diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index f0f3d3475..464c5c810 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -606,6 +606,21 @@ namespace nmos const std::pair id_type{ resourceId, nmos::types::input }; auto resource = find_resource(resources, id_type); + + // Extract "adjust_to_caps" query parameter + + bool adjust_to_caps = false; + + const auto query_params = web::json::value_from_query((req.request_uri().query())); + if (query_params.has_field(nmos::fields::adjust_to_caps)) + { + const auto& query_adjust_to_caps = query_params.at(nmos::fields::adjust_to_caps).as_string(); + if (query_adjust_to_caps == "true" || query_adjust_to_caps == "1") + { + adjust_to_caps = true; + } + } + if (resources.end() != resource) { auto& endpoint_base_edid = nmos::fields::endpoint_base_edid(resource->data); @@ -638,7 +653,7 @@ namespace nmos utility::string_t updated_timestamp; // Update Base EDID in streamcompatibility_resources - modify_resource(resources, resourceId, [&base_edid, &base_edid_properties, &updated_timestamp](nmos::resource& input) + modify_resource(resources, resourceId, [&base_edid, &base_edid_properties, &updated_timestamp, adjust_to_caps](nmos::resource& input) { if (!base_edid_properties.is_null()) { @@ -646,6 +661,7 @@ namespace nmos } input.data[nmos::fields::endpoint_base_edid] = make_streamcompatibility_edid_endpoint(base_edid); + input.data[nmos::fields::adjust_to_caps] = value::boolean(adjust_to_caps); updated_timestamp = nmos::make_version(); input.data[nmos::fields::version] = web::json::value::string(updated_timestamp); diff --git a/Development/nmos/streamcompatibility_resources.cpp b/Development/nmos/streamcompatibility_resources.cpp index 0a3e50e1f..4189c76b4 100644 --- a/Development/nmos/streamcompatibility_resources.cpp +++ b/Development/nmos/streamcompatibility_resources.cpp @@ -139,6 +139,10 @@ namespace nmos } data[nmos::fields::base_edid_support] = value::boolean(base_edid_support); + if (base_edid_support) + { + data[nmos::fields::adjust_to_caps] = value::boolean(false); + } data[nmos::fields::senders] = value_from_elements(senders); data[nmos::fields::status] = value_of({ { nmos::fields::state, nmos::input_states::signal_present.name } }); From b98dab7dfc8e3e054a3c4c3e29bf4eac220a3ea4 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 21 Jul 2023 01:18:45 +0400 Subject: [PATCH 093/109] Update JSON Schemas --- Development/cmake/NmosCppLibraries.cmake | 8 ++++ Development/nmos/is11_schemas/is11_schemas.h | 10 ++++- Development/nmos/json_schema.cpp | 9 +++- .../is-11/v1.0.x/APIs/schemas/README.md | 16 +++++++ .../v1.0.x/APIs/schemas/constraint_set.json | 30 +++++++++++++ .../APIs/schemas/constraints_active.json | 2 +- .../schemas/empty_constraints_active.json | 2 +- .../is-11/v1.0.x/APIs/schemas/input.json | 2 +- .../is-11/v1.0.x/APIs/schemas/output.json | 2 +- .../v1.0.x/APIs/schemas/param_constraint.json | 13 ++++++ .../schemas/param_constraint_boolean.json | 15 +++++++ .../schemas/param_constraint_integer.json | 21 +++++++++ .../APIs/schemas/param_constraint_number.json | 21 +++++++++ .../schemas/param_constraint_rational.json | 39 ++++++++++++++++ .../APIs/schemas/param_constraint_string.json | 15 +++++++ .../v1.0.x/APIs/schemas/resource_core.json | 45 +++++++++++++++++++ 16 files changed, 244 insertions(+), 6 deletions(-) create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/README.md create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/constraint_set.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_boolean.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_integer.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_number.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_rational.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_string.json create mode 100644 Development/third_party/is-11/v1.0.x/APIs/schemas/resource_core.json diff --git a/Development/cmake/NmosCppLibraries.cmake b/Development/cmake/NmosCppLibraries.cmake index b3a9775a5..1b4133fef 100644 --- a/Development/cmake/NmosCppLibraries.cmake +++ b/Development/cmake/NmosCppLibraries.cmake @@ -697,6 +697,7 @@ set(NMOS_IS11_V1_0_TAG v1.0.x) set(NMOS_IS11_V1_0_SCHEMAS_JSON third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/constraints_active.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/constraints-base.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/constraint_set.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/constraints_supported.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/empty_constraints_active.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/error.json @@ -704,8 +705,15 @@ set(NMOS_IS11_V1_0_SCHEMAS_JSON third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/input.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/input-output-base.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/output.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/param_constraint_boolean.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/param_constraint_integer.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/param_constraint.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/param_constraint_number.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/param_constraint_rational.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/param_constraint_string.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/receiver-base.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/receiver-status.json + third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/resource_core.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/resource-list.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/sender-base.json third_party/is-11/${NMOS_IS11_V1_0_TAG}/APIs/schemas/sender-status.json diff --git a/Development/nmos/is11_schemas/is11_schemas.h b/Development/nmos/is11_schemas/is11_schemas.h index 7e8174b19..8f2513cb0 100644 --- a/Development/nmos/is11_schemas/is11_schemas.h +++ b/Development/nmos/is11_schemas/is11_schemas.h @@ -11,19 +11,27 @@ namespace nmos { extern const char* constraints_active; extern const char* constraints_base; + extern const char* constraint_set; extern const char* constraints_supported; extern const char* empty_constraints_active; extern const char* error; - extern const char* streamcompatibility_api_base; extern const char* input_edid_base; extern const char* input; extern const char* input_output_base; extern const char* output; + extern const char* param_constraint_boolean; + extern const char* param_constraint_integer; + extern const char* param_constraint; + extern const char* param_constraint_number; + extern const char* param_constraint_rational; + extern const char* param_constraint_string; extern const char* receiver_base; extern const char* receiver_status; + extern const char* resource_core; extern const char* resource_list; extern const char* sender_base; extern const char* sender_status; + extern const char* streamcompatibility_api_base; extern const char* uuid_list; } } diff --git a/Development/nmos/json_schema.cpp b/Development/nmos/json_schema.cpp index f0cc43748..a0f9272f1 100644 --- a/Development/nmos/json_schema.cpp +++ b/Development/nmos/json_schema.cpp @@ -352,7 +352,14 @@ namespace nmos return { // v1.0 - { make_schema_uri(v1_0::tag, _XPLATSTR("constraints_active.json")), make_schema(v1_0::constraints_active) } + { make_schema_uri(v1_0::tag, _XPLATSTR("constraints_active.json")), make_schema(v1_0::constraints_active) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("constraint_set.json")), make_schema(v1_0::constraint_set) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("param_constraint_boolean.json")), make_schema(v1_0::param_constraint_boolean) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("param_constraint_integer.json")), make_schema(v1_0::param_constraint_integer) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("param_constraint.json")), make_schema(v1_0::param_constraint) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("param_constraint_number.json")), make_schema(v1_0::param_constraint_number) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("param_constraint_rational.json")), make_schema(v1_0::param_constraint_rational) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("param_constraint_string.json")), make_schema(v1_0::param_constraint_string) }, }; } diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/README.md b/Development/third_party/is-11/v1.0.x/APIs/schemas/README.md new file mode 100644 index 000000000..e9847502d --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/README.md @@ -0,0 +1,16 @@ +This directory is for JSON Schemas used in documentation. Some of them were copied from other AMWA NMOS specifications. + +[IS-04 v1.3.2][]: +- [resource_core.json](https://github.com/AMWA-TV/is-04/raw/v1.3.2/APIs/schemas/resource_core.json) + +[BCP-004-01 v1.0.0][]: +- [constraint_set.json](https://github.com/AMWA-TV/bcp-004-01/raw/v1.0.0/APIs/schemas/constraint_set.json) +- [param_constraint.json](https://github.com/AMWA-TV/bcp-004-01/raw/v1.0.0/APIs/schemas/param_constraint.json) +- [param_constraint_boolean.json](https://github.com/AMWA-TV/bcp-004-01/raw/v1.0.0/APIs/schemas/param_constraint_boolean.json) +- [param_constraint_integer.json](https://github.com/AMWA-TV/bcp-004-01/raw/v1.0.0/APIs/schemas/param_constraint_integer.json) +- [param_constraint_number.json](https://github.com/AMWA-TV/bcp-004-01/raw/v1.0.0/APIs/schemas/param_constraint_number.json) +- [param_constraint_rational.json](https://github.com/AMWA-TV/bcp-004-01/raw/v1.0.0/APIs/schemas/param_constraint_rational.json) +- [param_constraint_string.json](https://github.com/AMWA-TV/bcp-004-01/raw/v1.0.0/APIs/schemas/param_constraint_string.json) + +[IS-04 v1.3.2]: https://specs.amwa.tv/is-04/releases/v1.3.2/ +[BCP-004-01 v1.0.0]: https://specs.amwa.tv/bcp-004-01/releases/v1.0.0/ diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/constraint_set.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/constraint_set.json new file mode 100644 index 000000000..ab439d378 --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/constraint_set.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes a Constraint Set", + "title": "Constraint Set", + "type": "object", + "minProperties": 1, + "properties": { + "urn:x-nmos:cap:meta:label": { + "description": "Freeform string label for the Constraint Set", + "type": "string" + }, + "urn:x-nmos:cap:meta:preference": { + "description": "This value expresses the relative 'weight' that the Receiver assigns to its preference for the streams satisfied by the associated Constraint Set. The weight is an integer in the range -100 through 100, where -100 is least preferred and 100 is most preferred. When the attribute is omitted, the effective value for the associated Constraint Set is 0.", + "type": "integer", + "default": 0, + "maximum": 100, + "minimum": -100 + }, + "urn:x-nmos:cap:meta:enabled": { + "description": "This value indicates whether a Constraint Set is available to use immediately (true) or whether this is an offline capability which can be activated via some unspecified configuration mechanism (false). When the attribute is omitted its value is assumed to be true.", + "type": "boolean", + "default": true + } + }, + "patternProperties": { + "^urn:x-nmos:cap:(?!meta:)": { + "$ref": "param_constraint.json" + } + } +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints_active.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints_active.json index b9a9253ca..539dbec7d 100644 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints_active.json +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/constraints_active.json @@ -10,7 +10,7 @@ "constraint_sets": { "type": "array", "items": { - "$ref": "https://github.com/AMWA-TV/bcp-004-01/raw/v1.0.x/APIs/schemas/constraint_set.json" + "$ref": "constraint_set.json" } } } diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/empty_constraints_active.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/empty_constraints_active.json index 6a9627b83..a75a5e24f 100644 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/empty_constraints_active.json +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/empty_constraints_active.json @@ -9,7 +9,7 @@ "constraint_sets": { "type": "array", "items": { - "$ref": "https://raw.githubusercontent.com/AMWA-TV/nmos-receiver-capabilities/v1.0.0/APIs/schemas/constraint_set.json" + "$ref": "constraint_set.json" }, "minItems": 0, "maxItems": 0 diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/input.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/input.json index d49dda6f3..0d915fb38 100644 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/input.json +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/input.json @@ -4,7 +4,7 @@ "description": "Describes an Input", "title": "Input resource", "allOf": [ - { "$ref": "https://raw.githubusercontent.com/AMWA-TV/nmos-discovery-registration/v1.3.1/APIs/schemas/resource_core.json" }, + { "$ref": "resource_core.json" }, { "type": "object", "required": [ diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/output.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/output.json index 47d1feab5..0c9ccfc2b 100644 --- a/Development/third_party/is-11/v1.0.x/APIs/schemas/output.json +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/output.json @@ -4,7 +4,7 @@ "description": "Describes an Output", "title": "Output resource", "allOf": [ - { "$ref": "https://raw.githubusercontent.com/AMWA-TV/nmos-discovery-registration/v1.3.1/APIs/schemas/resource_core.json" }, + { "$ref": "resource_core.json" }, { "type": "object", "required": [ diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint.json new file mode 100644 index 000000000..0537109c3 --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes a Parameter Constraint", + "title": "Parameter Constraint", + "type": "object", + "anyOf": [ + { "$ref": "param_constraint_string.json" }, + { "$ref": "param_constraint_integer.json" }, + { "$ref": "param_constraint_number.json" }, + { "$ref": "param_constraint_boolean.json" }, + { "$ref": "param_constraint_rational.json" } + ] +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_boolean.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_boolean.json new file mode 100644 index 000000000..fac6b9966 --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_boolean.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes a Boolean Parameter Constraint", + "title": "Boolean Parameter Constraint", + "type": "object", + "properties": { + "enum": { + "type": "array", + "minItems": 1, + "items": { + "type": "boolean" + } + } + } +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_integer.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_integer.json new file mode 100644 index 000000000..35c5aec23 --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_integer.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes an Integer Parameter Constraint", + "title": "Integer Parameter Constraint", + "type": "object", + "properties": { + "enum": { + "type": "array", + "minItems": 1, + "items": { + "type": "integer" + } + }, + "minimum": { + "type": "integer" + }, + "maximum": { + "type": "integer" + } + } +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_number.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_number.json new file mode 100644 index 000000000..24a93c1d6 --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_number.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes a Number Parameter Constraint", + "title": "Number Parameter Constraint", + "type": "object", + "properties": { + "enum": { + "type": "array", + "minItems": 1, + "items": { + "type": "number" + } + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + } + } +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_rational.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_rational.json new file mode 100644 index 000000000..31a8db61c --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_rational.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes a Rational Parameter Constraint", + "title": "Rational Parameter Constraint", + "type": "object", + "properties": { + "enum": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/rational" + } + }, + "minimum": { + "$ref": "#/definitions/rational" + }, + "maximum": { + "$ref": "#/definitions/rational" + } + }, + "definitions": { + "rational": { + "type": "object", + "required": [ + "numerator" + ], + "properties": { + "numerator": { + "type": "integer" + }, + "denominator": { + "type": "integer", + "default": 1 + } + }, + "additionalProperties": false + } + } +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_string.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_string.json new file mode 100644 index 000000000..a86600836 --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/param_constraint_string.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes a String Parameter Constraint", + "title": "String Parameter Constraint", + "type": "object", + "properties": { + "enum": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + } +} diff --git a/Development/third_party/is-11/v1.0.x/APIs/schemas/resource_core.json b/Development/third_party/is-11/v1.0.x/APIs/schemas/resource_core.json new file mode 100644 index 000000000..4eb583a89 --- /dev/null +++ b/Development/third_party/is-11/v1.0.x/APIs/schemas/resource_core.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Describes the foundations of all NMOS resources", + "title": "Base resource", + "required": [ + "id", + "version", + "label", + "description", + "tags" + ], + "properties": { + "id": { + "description": "Globally unique identifier for the resource", + "type": "string", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + }, + "version": { + "description": "String formatted TAI timestamp (:) indicating precisely when an attribute of the resource last changed", + "type": "string", + "pattern": "^[0-9]+:[0-9]+$" + }, + "label": { + "description": "Freeform string label for the resource", + "type": "string" + }, + "description": { + "description": "Detailed description of the resource", + "type": "string" + }, + "tags": { + "description": "Key value set of freeform string tags to aid in filtering resources. Values should be represented as an array of strings. Can be empty.", + "type": "object", + "patternProperties": { + "": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file From 2e033b53f0196628321461f55e0242657de0a47d Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 21 Jul 2023 01:20:49 +0400 Subject: [PATCH 094/109] Remove support of obsolete "base_edid", "effective_edid" and "edid" JSON objects --- .../nmos-cpp-node/node_implementation.cpp | 14 ++++------- Development/nmos/json_fields.h | 3 --- Development/nmos/streamcompatibility_api.cpp | 24 ++++--------------- Development/nmos/streamcompatibility_api.h | 6 ++--- .../nmos/streamcompatibility_resources.cpp | 14 ++--------- .../nmos/streamcompatibility_resources.h | 4 ++-- 6 files changed, 16 insertions(+), 49 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 67ca97cb2..77e8be7fa 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -949,7 +949,7 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) const auto sender_ids = impl::make_ids(seed_id, nmos::types::sender, { impl::ports::video, impl::ports::audio }); auto input = edid_support - ? nmos::experimental::make_streamcompatibility_input(input_id, device_id, true, true, edid, bst::nullopt, sender_ids, model.settings) + ? nmos::experimental::make_streamcompatibility_input(input_id, device_id, true, true, edid, sender_ids, model.settings) : nmos::experimental::make_streamcompatibility_input(input_id, device_id, true, sender_ids, model.settings); impl::set_label_description(input, impl::ports::mux, 0); // The single Input consumes both video and audio signals if (!insert_resource_after(delay_millis, model.streamcompatibility_resources, std::move(input), gate)) return; @@ -1013,7 +1013,7 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) const auto receiver_ids = impl::make_ids(seed_id, nmos::types::receiver, { impl::ports::video, impl::ports::audio }); auto output = edid_support - ? nmos::experimental::make_streamcompatibility_output(output_id, device_id, true, boost::variant(edid), bst::nullopt, receiver_ids, model.settings) + ? nmos::experimental::make_streamcompatibility_output(output_id, device_id, true, boost::variant(edid), receiver_ids, model.settings) : nmos::experimental::make_streamcompatibility_output(output_id, device_id, true, receiver_ids, model.settings); impl::set_label_description(output, impl::ports::mux, 0); // The single Output produces both video and audio signals if (!insert_resource_after(delay_millis, model.streamcompatibility_resources, std::move(output), gate)) return; @@ -1408,13 +1408,10 @@ nmos::channelmapping_activation_handler make_node_implementation_channelmapping_ } // Example Stream Compatibility Management API Base EDID update callback to perform application-specific operations to apply updated Base EDID -// (e.g. providing the implementation with a parsed version of Base EDID) nmos::experimental::details::streamcompatibility_base_edid_handler make_node_implementation_streamcompatibility_base_edid_handler(slog::base_gate& gate) { - return [&gate](const nmos::id& input_id, const bst::optional& base_edid, web::json::value& base_edid_properties) + return [&gate](const nmos::id& input_id, const bst::optional& base_edid) { - base_edid_properties = web::json::value::null(); - if (base_edid) { slog::log(gate, SLOG_FLF) << "Base EDID updated for Input " << input_id; @@ -1423,14 +1420,13 @@ nmos::experimental::details::streamcompatibility_base_edid_handler make_node_imp { slog::log(gate, SLOG_FLF) << "Base EDID deleted for Input " << input_id; } - }; } // Example Stream Compatibility Management API callback to update Effective EDID - captures streamcompatibility_resources by reference! nmos::experimental::details::streamcompatibility_effective_edid_setter make_node_implementation_streamcompatibility_effective_edid_setter(const nmos::resources& streamcompatibility_resources, slog::base_gate& gate) { - return [&streamcompatibility_resources, &gate](const nmos::id& input_id, boost::variant& effective_edid, bst::optional& effective_edid_properties) + return [&streamcompatibility_resources, &gate](const nmos::id& input_id, boost::variant& effective_edid) { unsigned char edid_bytes[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, @@ -1451,8 +1447,6 @@ nmos::experimental::details::streamcompatibility_effective_edid_setter make_node 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - effective_edid_properties = bst::nullopt; - bst::optional base_edid = bst::nullopt; const std::pair id_type{ input_id, nmos::types::input }; diff --git a/Development/nmos/json_fields.h b/Development/nmos/json_fields.h index 1705dc0cf..c72758dbe 100644 --- a/Development/nmos/json_fields.h +++ b/Development/nmos/json_fields.h @@ -241,9 +241,6 @@ namespace nmos const web::json::field_as_bool connected{ U("connected") }; const web::json::field_as_bool edid_support{ U("edid_support") }; const web::json::field_as_bool base_edid_support{ U("base_edid_support") }; - const web::json::field_as_object base_edid_properties{ U("base_edid") }; - const web::json::field_as_object effective_edid_properties{ U("effective_edid") }; - const web::json::field_as_object edid_properties{ U("edid") }; const web::json::field_as_bool adjust_to_caps{ U("adjust_to_caps") }; // for sender diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index 464c5c810..f479dc61a 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -94,21 +94,15 @@ namespace nmos void update_effective_edid(nmos::node_model& model, const streamcompatibility_effective_edid_setter& effective_edid_setter, const utility::string_t resource_id) { boost::variant effective_edid; - bst::optional effective_edid_properties = bst::nullopt; - effective_edid_setter(resource_id, effective_edid, effective_edid_properties); + effective_edid_setter(resource_id, effective_edid); utility::string_t updated_timestamp; - modify_resource(model.streamcompatibility_resources, resource_id, [&effective_edid, &effective_edid_properties, &updated_timestamp](nmos::resource& input) + modify_resource(model.streamcompatibility_resources, resource_id, [&effective_edid, &updated_timestamp](nmos::resource& input) { input.data[nmos::fields::endpoint_effective_edid] = boost::apply_visitor(edid_file_visitor(), effective_edid); - if (effective_edid_properties) - { - input.data[nmos::fields::effective_edid_properties] = *effective_edid_properties; - } - updated_timestamp = nmos::make_version(); input.data[nmos::fields::version] = web::json::value::string(updated_timestamp); }); @@ -629,8 +623,6 @@ namespace nmos { if (!nmos::fields::temporarily_locked(endpoint_base_edid)) { - web::json::value base_edid_properties = web::json::value::null(); - const auto request_body = req.content_ready().get().extract_vector().get(); const utility::string_t base_edid{ request_body.begin(), request_body.end() }; @@ -638,7 +630,7 @@ namespace nmos if (base_edid_handler) { - base_edid_handler(resourceId, base_edid, base_edid_properties); + base_edid_handler(resourceId, base_edid); } // Pre-check for resources existence before Base EDID modified and effective_edid_setter executed @@ -653,13 +645,8 @@ namespace nmos utility::string_t updated_timestamp; // Update Base EDID in streamcompatibility_resources - modify_resource(resources, resourceId, [&base_edid, &base_edid_properties, &updated_timestamp, adjust_to_caps](nmos::resource& input) + modify_resource(resources, resourceId, [&base_edid, &updated_timestamp, adjust_to_caps](nmos::resource& input) { - if (!base_edid_properties.is_null()) - { - input.data[nmos::fields::base_edid_properties] = base_edid_properties; - } - input.data[nmos::fields::endpoint_base_edid] = make_streamcompatibility_edid_endpoint(base_edid); input.data[nmos::fields::adjust_to_caps] = value::boolean(adjust_to_caps); @@ -726,8 +713,7 @@ namespace nmos if (base_edid_handler) { - web::json::value tmp; - base_edid_handler(resourceId, bst::nullopt, tmp); + base_edid_handler(resourceId, bst::nullopt); } // Pre-check for resources existence before Base EDID modified and effective_edid_setter executed diff --git a/Development/nmos/streamcompatibility_api.h b/Development/nmos/streamcompatibility_api.h index aed732477..c408e523d 100644 --- a/Development/nmos/streamcompatibility_api.h +++ b/Development/nmos/streamcompatibility_api.h @@ -18,11 +18,11 @@ namespace nmos namespace details { // a streamcompatibility_base_edid_handler is a notification that the Base EDID for the specified IS-11 input has changed (PUT or DELETEd) - // it can be used to perform any final validation of the specified Base EDID and set parsed Base EDID properties + // it can be used to perform any final validation of the specified Base EDID // when PUT, it may throw web::json::json_exception, which will be mapped to a 400 Bad Request status code with NMOS error "debug" information including the exception message // or std::runtime_error, which will be mapped to a 500 Internal Error status code with NMOS error "debug" information including the exception message // when DELETE, this callback should not throw exceptions, as the Base EDID will already have been changed and those changes will not be rolled back - typedef std::function& base_edid, web::json::value& base_edid_properties)> streamcompatibility_base_edid_handler; + typedef std::function& base_edid)> streamcompatibility_base_edid_handler; // a streamcompatibility_active_constraints_handler is a notification that the Active Constraints for the specified IS-11 sender has changed (PUT or DELETEd) // it can be used to perform any final validation of the specified Active Constraints @@ -34,7 +34,7 @@ namespace nmos // effective EDID of the input is updated when either Active Constraints of a Sender associated with this input are updated // or base EDID of the input is updated or deleted // this callback should not throw exceptions, as it's called after the mentioned changes which will not be rolled back - typedef std::function& effective_edid, bst::optional& effective_edid_properties)> streamcompatibility_effective_edid_setter; + typedef std::function& effective_edid)> streamcompatibility_effective_edid_setter; } web::http::experimental::listener::api_router make_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_handler base_edid_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_handler active_constraints_handler, slog::base_gate& gate); diff --git a/Development/nmos/streamcompatibility_resources.cpp b/Development/nmos/streamcompatibility_resources.cpp index 4189c76b4..fa5ae5e88 100644 --- a/Development/nmos/streamcompatibility_resources.cpp +++ b/Development/nmos/streamcompatibility_resources.cpp @@ -118,7 +118,7 @@ namespace nmos return{ is11_versions::v1_0, types::input, std::move(data), id, false }; } - nmos::resource make_streamcompatibility_input(const nmos::id& id, const nmos::id& device_id, bool connected, bool base_edid_support, const boost::variant& effective_edid, const bst::optional& effective_edid_properties, const std::vector& senders, const nmos::settings& settings) + nmos::resource make_streamcompatibility_input(const nmos::id& id, const nmos::id& device_id, bool connected, bool base_edid_support, const boost::variant& effective_edid, const std::vector& senders, const nmos::settings& settings) { using web::json::value; using web::json::value_from_elements; @@ -133,11 +133,6 @@ namespace nmos data[nmos::fields::endpoint_effective_edid] = boost::apply_visitor(edid_file_visitor(), effective_edid); - if (effective_edid_properties) - { - data[nmos::fields::effective_edid_properties] = *effective_edid_properties; - } - data[nmos::fields::base_edid_support] = value::boolean(base_edid_support); if (base_edid_support) { @@ -161,7 +156,7 @@ namespace nmos return{ is11_versions::v1_0, types::output, std::move(data), id, false }; } - nmos::resource make_streamcompatibility_output(const nmos::id& id, const nmos::id& device_id, bool connected, const bst::optional>& edid, const bst::optional& edid_properties, const std::vector& receivers, const nmos::settings& settings) + nmos::resource make_streamcompatibility_output(const nmos::id& id, const nmos::id& device_id, bool connected, const bst::optional>& edid, const std::vector& receivers, const nmos::settings& settings) { using web::json::value_from_elements; using web::json::value_of; @@ -175,11 +170,6 @@ namespace nmos data[nmos::fields::endpoint_edid] = boost::apply_visitor(edid_file_visitor(), *edid); } - if (edid_properties) - { - data[nmos::fields::edid_properties] = *edid_properties; - } - return{ is11_versions::v1_0, types::output, std::move(data), id, false }; } } diff --git a/Development/nmos/streamcompatibility_resources.h b/Development/nmos/streamcompatibility_resources.h index c4ac6faf8..57945b4fa 100644 --- a/Development/nmos/streamcompatibility_resources.h +++ b/Development/nmos/streamcompatibility_resources.h @@ -28,13 +28,13 @@ namespace nmos // Makes an input without EDID support nmos::resource make_streamcompatibility_input(const nmos::id& id, const nmos::id& device_id, bool connected, const std::vector& senders, const nmos::settings& settings); // Makes an input with EDID support - nmos::resource make_streamcompatibility_input(const nmos::id& id, const nmos::id& device_id, bool connected, bool base_edid_support, const boost::variant& effective_edid, const bst::optional& effective_edid_properties, const std::vector& senders, const nmos::settings& settings); + nmos::resource make_streamcompatibility_input(const nmos::id& id, const nmos::id& device_id, bool connected, bool base_edid_support, const boost::variant& effective_edid, const std::vector& senders, const nmos::settings& settings); // See https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/output.html // Makes an output without EDID support nmos::resource make_streamcompatibility_output(const nmos::id& id, const nmos::id& device_id, bool connected, const std::vector& receivers, const nmos::settings& settings); // Makes an output with EDID support - nmos::resource make_streamcompatibility_output(const nmos::id& id, const nmos::id& device_id, bool connected, const bst::optional>& edid, const bst::optional& edid_properties, const std::vector& receivers, const nmos::settings& settings); + nmos::resource make_streamcompatibility_output(const nmos::id& id, const nmos::id& device_id, bool connected, const bst::optional>& edid, const std::vector& receivers, const nmos::settings& settings); struct edid_file_visitor : public boost::static_visitor { From 146beedc0b067797211f81d05322aac48ceb5a51 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sat, 22 Jul 2023 12:42:34 +0400 Subject: [PATCH 095/109] Add "master_enable" check before Receiver's stream compliance check --- Development/nmos/streamcompatibility_behaviour.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index 31ff456fc..80b1f177c 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -55,11 +55,14 @@ namespace nmos nmos::receiver_state receiver_state(nmos::receiver_states::unknown); utility::string_t receiver_state_debug; - const auto& transport_file = nmos::fields::transport_file(nmos::fields::endpoint_active(connection_receiver->data)); - - if (validate_receiver) + if (nmos::fields::master_enable(nmos::fields::endpoint_active(connection_receiver->data))) { - std::tie(receiver_state, receiver_state_debug) = validate_receiver(*node_receiver, transport_file); + const auto& transport_file = nmos::fields::transport_file(nmos::fields::endpoint_active(connection_receiver->data)); + + if (validate_receiver) + { + std::tie(receiver_state, receiver_state_debug) = validate_receiver(*node_receiver, transport_file); + } } if (nmos::fields::state(nmos::fields::status(streamcompatibility_receiver->data)) != receiver_state.name) From 74e20ccceb41d2f7a2a9c9c94b84b41b161e31da Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sat, 22 Jul 2023 14:45:26 +0400 Subject: [PATCH 096/109] Wrap adjust_to_caps checks with U macro --- Development/nmos-cpp-node/node_implementation.cpp | 3 +++ Development/nmos/streamcompatibility_api.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 77e8be7fa..dacfc55af 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1466,6 +1466,9 @@ nmos::experimental::details::streamcompatibility_effective_edid_setter make_node } } + bool adjust_to_caps = nmos::fields::adjust_to_caps(streamcompatibility_input->data); + slog::log(gate, SLOG_FLF) << "adjust_to_caps is " << adjust_to_caps; + const auto& edid_endpoint = nmos::fields::endpoint_base_edid(streamcompatibility_input->data); if (!edid_endpoint.is_null()) diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index f479dc61a..57301fdf7 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -609,7 +609,7 @@ namespace nmos if (query_params.has_field(nmos::fields::adjust_to_caps)) { const auto& query_adjust_to_caps = query_params.at(nmos::fields::adjust_to_caps).as_string(); - if (query_adjust_to_caps == "true" || query_adjust_to_caps == "1") + if (query_adjust_to_caps == U("true") || query_adjust_to_caps == U("1")) { adjust_to_caps = true; } From 8f3301c013d4c4276254cd7e2aa947bbbd7d2b63 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sat, 22 Jul 2023 15:27:10 +0400 Subject: [PATCH 097/109] Erase "debug" in Status of S/R if it's empty in the last update --- Development/nmos/streamcompatibility_behaviour.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index 80b1f177c..a862479dd 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -74,6 +74,10 @@ namespace nmos { nmos::fields::status(receiver.data)[nmos::fields::debug] = value::string(receiver_state_debug); } + else if (!nmos::fields::debug(nmos::fields::status(receiver.data)).empty()) + { + nmos::fields::status(receiver.data).erase(nmos::fields::debug); + } }); update_version(node_resources, receiver_id, nmos::make_version()); @@ -161,6 +165,10 @@ namespace nmos { nmos::fields::status(sender.data)[nmos::fields::debug] = web::json::value::string(sender_state_debug); } + else if (!nmos::fields::debug(nmos::fields::status(sender.data)).empty()) + { + nmos::fields::status(sender.data).erase(nmos::fields::debug); + } updated_timestamp = nmos::make_version(); sender.data[nmos::fields::version] = web::json::value::string(updated_timestamp); From c778adc461baf53db8b4d51e9408803b952fa918 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Sun, 23 Jul 2023 01:28:59 +0400 Subject: [PATCH 098/109] CI: temporarily switch to nmos-testing fork --- .github/workflows/build-test.yml | 12 ++++++------ .github/workflows/src/amwa-test.yml | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index c60dd7908..8486a2919 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -340,10 +340,10 @@ jobs: set -x root_dir=`pwd` - # Install AMWA NMOS Testing Tool (is-11 instead of master until IS-11 tests are merged) - git clone https://github.com/AMWA-TV/nmos-testing.git + # Install AMWA NMOS Testing Tool (N-Nagorny instead of AMWA-TV and is-11 instead of master until IS-11 tests are merged) + git clone https://github.com/N-Nagorny/nmos-testing.git cd nmos-testing - git checkout is-11 + git checkout is-11-output-tests-rework # Configure the Testing Tool so all APIs are tested with TLS printf "from . import Config as CONFIG\nCONFIG.ENABLE_HTTPS = True\n" > nmostesting/UserConfig.py @@ -848,10 +848,10 @@ jobs: set -x root_dir=`pwd` - # Install AMWA NMOS Testing Tool (is-11 instead of master until IS-11 tests are merged) - git clone https://github.com/AMWA-TV/nmos-testing.git + # Install AMWA NMOS Testing Tool (N-Nagorny instead of AMWA-TV and is-11 instead of master until IS-11 tests are merged) + git clone https://github.com/N-Nagorny/nmos-testing.git cd nmos-testing - git checkout is-11 + git checkout is-11-output-tests-rework # Configure the Testing Tool so all APIs are tested with TLS printf "from . import Config as CONFIG\nCONFIG.ENABLE_HTTPS = True\n" > nmostesting/UserConfig.py diff --git a/.github/workflows/src/amwa-test.yml b/.github/workflows/src/amwa-test.yml index 09f885c7a..6398e4b50 100644 --- a/.github/workflows/src/amwa-test.yml +++ b/.github/workflows/src/amwa-test.yml @@ -15,10 +15,10 @@ set -x root_dir=`pwd` - # Install AMWA NMOS Testing Tool (is-11 instead of master until IS-11 tests are merged) - git clone https://github.com/AMWA-TV/nmos-testing.git + # Install AMWA NMOS Testing Tool (N-Nagorny instead of AMWA-TV and is-11 instead of master until IS-11 tests are merged) + git clone https://github.com/N-Nagorny/nmos-testing.git cd nmos-testing - git checkout is-11 + git checkout is-11-output-tests-rework # Configure the Testing Tool so all APIs are tested with TLS printf "from . import Config as CONFIG\nCONFIG.ENABLE_HTTPS = True\n" > nmostesting/UserConfig.py From 4c446bacfe508ca70ede2816ab801c42f4f519f8 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 13 Oct 2023 00:58:40 +0400 Subject: [PATCH 099/109] Fix edid_href --- Development/nmos/streamcompatibility_api.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index 57301fdf7..a3b71ae3a 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -44,7 +44,7 @@ namespace nmos slog::log(gate, SLOG_FLF) << "Redirecting to EDID file for " << id_type; set_reply(res, web::http::status_codes::TemporaryRedirect); - res.headers().add(web::http::header_names::location, nmos::fields::edid_href(edid_endpoint)); + res.headers().add(web::http::header_names::location, nmos::fields::edid_href(edid_endpoint).as_string()); } else { From 0d8f8e45b1ba6ff24023e5872a9d035d934b717f Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Wed, 1 Nov 2023 09:56:35 -0400 Subject: [PATCH 100/109] CI: change the branch --- .github/workflows/build-test.yml | 4 ++-- .github/workflows/src/amwa-test.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 8486a2919..b73b095d6 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -343,7 +343,7 @@ jobs: # Install AMWA NMOS Testing Tool (N-Nagorny instead of AMWA-TV and is-11 instead of master until IS-11 tests are merged) git clone https://github.com/N-Nagorny/nmos-testing.git cd nmos-testing - git checkout is-11-output-tests-rework + git checkout is-11-independent-test-cases # Configure the Testing Tool so all APIs are tested with TLS printf "from . import Config as CONFIG\nCONFIG.ENABLE_HTTPS = True\n" > nmostesting/UserConfig.py @@ -851,7 +851,7 @@ jobs: # Install AMWA NMOS Testing Tool (N-Nagorny instead of AMWA-TV and is-11 instead of master until IS-11 tests are merged) git clone https://github.com/N-Nagorny/nmos-testing.git cd nmos-testing - git checkout is-11-output-tests-rework + git checkout is-11-independent-test-cases # Configure the Testing Tool so all APIs are tested with TLS printf "from . import Config as CONFIG\nCONFIG.ENABLE_HTTPS = True\n" > nmostesting/UserConfig.py diff --git a/.github/workflows/src/amwa-test.yml b/.github/workflows/src/amwa-test.yml index 6398e4b50..fbb8eda8a 100644 --- a/.github/workflows/src/amwa-test.yml +++ b/.github/workflows/src/amwa-test.yml @@ -18,7 +18,7 @@ # Install AMWA NMOS Testing Tool (N-Nagorny instead of AMWA-TV and is-11 instead of master until IS-11 tests are merged) git clone https://github.com/N-Nagorny/nmos-testing.git cd nmos-testing - git checkout is-11-output-tests-rework + git checkout is-11-independent-test-cases # Configure the Testing Tool so all APIs are tested with TLS printf "from . import Config as CONFIG\nCONFIG.ENABLE_HTTPS = True\n" > nmostesting/UserConfig.py From 20b8a7d6c013db1e7817a31c56aa4ef42c48e299 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Wed, 1 Nov 2023 10:07:55 -0400 Subject: [PATCH 101/109] Remove BCP-004-01 schemas since they were copied to IS-11 --- Development/cmake/NmosCppLibraries.cmake | 75 ------------------- .../nmos/bcp00401_schemas/bcp00401_schemas.h | 25 ------- Development/nmos/json_schema.cpp | 36 --------- Development/third_party/bcp-004-01/README.md | 8 -- .../v1.0.x/APIs/schemas/constraint_set.json | 30 -------- .../v1.0.x/APIs/schemas/constraint_sets.json | 9 --- .../v1.0.x/APIs/schemas/param_constraint.json | 13 ---- .../schemas/param_constraint_boolean.json | 15 ---- .../schemas/param_constraint_integer.json | 21 ------ .../APIs/schemas/param_constraint_number.json | 21 ------ .../schemas/param_constraint_rational.json | 39 ---------- .../APIs/schemas/param_constraint_string.json | 15 ---- .../schemas/receiver_constraint_sets.json | 30 -------- 13 files changed, 337 deletions(-) delete mode 100644 Development/nmos/bcp00401_schemas/bcp00401_schemas.h delete mode 100644 Development/third_party/bcp-004-01/README.md delete mode 100644 Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/constraint_set.json delete mode 100644 Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/constraint_sets.json delete mode 100644 Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint.json delete mode 100644 Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_boolean.json delete mode 100644 Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_integer.json delete mode 100644 Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_number.json delete mode 100644 Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_rational.json delete mode 100644 Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_string.json delete mode 100644 Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/receiver_constraint_sets.json diff --git a/Development/cmake/NmosCppLibraries.cmake b/Development/cmake/NmosCppLibraries.cmake index 1b4133fef..663f1bef4 100644 --- a/Development/cmake/NmosCppLibraries.cmake +++ b/Development/cmake/NmosCppLibraries.cmake @@ -775,80 +775,6 @@ target_include_directories(nmos_is11_schemas PUBLIC list(APPEND NMOS_CPP_TARGETS nmos_is11_schemas) add_library(nmos-cpp::nmos_is11_schemas ALIAS nmos_is11_schemas) -# nmos_bcp00401_schemas library - -set(NMOS_BCP00401_SCHEMAS_HEADERS - nmos/bcp00401_schemas/bcp00401_schemas.h - ) - -set(NMOS_BCP00401_V1_0_TAG v1.0.x) - -set(NMOS_BCP00401_V1_0_SCHEMAS_JSON - third_party/bcp-004-01/${NMOS_BCP00401_V1_0_TAG}/APIs/schemas/constraint_set.json - third_party/bcp-004-01/${NMOS_BCP00401_V1_0_TAG}/APIs/schemas/constraint_sets.json - third_party/bcp-004-01/${NMOS_BCP00401_V1_0_TAG}/APIs/schemas/param_constraint_boolean.json - third_party/bcp-004-01/${NMOS_BCP00401_V1_0_TAG}/APIs/schemas/param_constraint_integer.json - third_party/bcp-004-01/${NMOS_BCP00401_V1_0_TAG}/APIs/schemas/param_constraint.json - third_party/bcp-004-01/${NMOS_BCP00401_V1_0_TAG}/APIs/schemas/param_constraint_number.json - third_party/bcp-004-01/${NMOS_BCP00401_V1_0_TAG}/APIs/schemas/param_constraint_rational.json - third_party/bcp-004-01/${NMOS_BCP00401_V1_0_TAG}/APIs/schemas/param_constraint_string.json - third_party/bcp-004-01/${NMOS_BCP00401_V1_0_TAG}/APIs/schemas/receiver_constraint_sets.json - ) - -set(NMOS_BCP00401_SCHEMAS_JSON_MATCH "third_party/bcp-004-01/([^/]+)/APIs/schemas/([^;]+)\\.json") -set(NMOS_BCP00401_SCHEMAS_SOURCE_REPLACE "${CMAKE_CURRENT_BINARY_DIR_REPLACE}/nmos/bcp00401_schemas/\\1/\\2.cpp") -string(REGEX REPLACE "${NMOS_BCP00401_SCHEMAS_JSON_MATCH}(;|$)" "${NMOS_BCP00401_SCHEMAS_SOURCE_REPLACE}\\3" NMOS_BCP00401_V1_0_SCHEMAS_SOURCES "${NMOS_BCP00401_V1_0_SCHEMAS_JSON}") - -foreach(JSON ${NMOS_BCP00401_V1_0_SCHEMAS_JSON}) - string(REGEX REPLACE "${NMOS_BCP00401_SCHEMAS_JSON_MATCH}" "${NMOS_BCP00401_SCHEMAS_SOURCE_REPLACE}" SOURCE "${JSON}") - string(REGEX REPLACE "${NMOS_BCP00401_SCHEMAS_JSON_MATCH}" "\\1" NS "${JSON}") - string(REGEX REPLACE "${NMOS_BCP00401_SCHEMAS_JSON_MATCH}" "\\2" VAR "${JSON}") - string(MAKE_C_IDENTIFIER "${NS}" NS) - string(MAKE_C_IDENTIFIER "${VAR}" VAR) - - file(WRITE "${SOURCE}.in" "\ -// Auto-generated from: ${JSON}\n\ -\n\ -namespace nmos\n\ -{\n\ - namespace bcp00401_schemas\n\ - {\n\ - namespace ${NS}\n\ - {\n\ - const char* ${VAR} = R\"-auto-generated-(") - - file(READ "${JSON}" RAW) - file(APPEND "${SOURCE}.in" "${RAW}") - - file(APPEND "${SOURCE}.in" ")-auto-generated-\";\n\ - }\n\ - }\n\ -}\n") - - configure_file("${SOURCE}.in" "${SOURCE}" COPYONLY) -endforeach() - -add_library( - nmos_bcp00401_schemas STATIC - ${NMOS_BCP00401_SCHEMAS_HEADERS} - ${NMOS_BCP00401_V1_0_SCHEMAS_SOURCES} - ) - -source_group("nmos\\bcp00401_schemas\\Header Files" FILES ${NMOS_BCP00401_SCHEMAS_HEADERS}) -source_group("nmos\\bcp00401_schemas\\${NMOS_BCP00401_V1_0_TAG}\\Source Files" FILES ${NMOS_BCP00401_V1_0_SCHEMAS_SOURCES}) - -target_link_libraries( - nmos_bcp00401_schemas PRIVATE - nmos-cpp::compile-settings - ) -target_include_directories(nmos_bcp00401_schemas PUBLIC - $ - $ - ) - -list(APPEND NMOS_CPP_TARGETS nmos_bcp00401_schemas) -add_library(nmos-cpp::nmos_bcp00401_schemas ALIAS nmos_bcp00401_schemas) - # nmos-cpp library set(NMOS_CPP_BST_SOURCES @@ -1161,7 +1087,6 @@ target_link_libraries( nmos-cpp::nmos_is08_schemas nmos-cpp::nmos_is09_schemas nmos-cpp::nmos_is11_schemas - nmos-cpp::nmos_bcp00401_schemas nmos-cpp::mdns nmos-cpp::slog nmos-cpp::OpenSSL diff --git a/Development/nmos/bcp00401_schemas/bcp00401_schemas.h b/Development/nmos/bcp00401_schemas/bcp00401_schemas.h deleted file mode 100644 index f601034fe..000000000 --- a/Development/nmos/bcp00401_schemas/bcp00401_schemas.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef NMOS_BCP00401_SCHEMAS_H -#define NMOS_BCP00401_SCHEMAS_H - -// Extern declarations for auto-generated constants -// could be auto-generated, but isn't currently! -namespace nmos -{ - namespace bcp00401_schemas - { - namespace v1_0_x - { - extern const char* constraint_set; - extern const char* constraint_sets; - extern const char* param_constraint_boolean; - extern const char* param_constraint_integer; - extern const char* param_constraint; - extern const char* param_constraint_number; - extern const char* param_constraint_rational; - extern const char* param_constraint_string; - extern const char* receiver_constraint_sets; - } - } -} - -#endif diff --git a/Development/nmos/json_schema.cpp b/Development/nmos/json_schema.cpp index a0f9272f1..ae99a3dcf 100644 --- a/Development/nmos/json_schema.cpp +++ b/Development/nmos/json_schema.cpp @@ -1,7 +1,6 @@ #include "nmos/json_schema.h" #include "cpprest/basic_utils.h" -#include "nmos/bcp00401_schemas/bcp00401_schemas.h" #include "nmos/is04_versions.h" #include "nmos/is04_schemas/is04_schemas.h" #include "nmos/is05_versions.h" @@ -146,21 +145,6 @@ namespace nmos const web::uri senders_active_constraints_put_request_uri = make_schema_uri(tag, _XPLATSTR("constraints_active.json")); } } - - namespace bcp00401_schemas - { - web::uri make_schema_uri(const utility::string_t& tag, const utility::string_t& ref = {}) - { - return{ _XPLATSTR("https://github.com/AMWA-TV/bcp-004-01/raw/") + tag + _XPLATSTR("/APIs/schemas/") + ref }; - } - - // See https://github.com/AMWA-TV/bcp-004-01/tree/v1.0.x/APIs/schemas/ - namespace v1_0 - { - using namespace nmos::bcp00401_schemas::v1_0_x; - const utility::string_t tag(_XPLATSTR("v1.0.x")); - } - } } namespace nmos @@ -363,25 +347,6 @@ namespace nmos }; } - static std::map make_bcp00401_schemas() - { - using namespace nmos::bcp00401_schemas; - - return - { - // v1.0 - { make_schema_uri(v1_0::tag, _XPLATSTR("constraint_set.json")), make_schema(v1_0::constraint_set) }, - { make_schema_uri(v1_0::tag, _XPLATSTR("constraint_sets.json")), make_schema(v1_0::constraint_sets) }, - { make_schema_uri(v1_0::tag, _XPLATSTR("param_constraint_boolean.json")), make_schema(v1_0::param_constraint_boolean) }, - { make_schema_uri(v1_0::tag, _XPLATSTR("param_constraint_integer.json")), make_schema(v1_0::param_constraint_integer) }, - { make_schema_uri(v1_0::tag, _XPLATSTR("param_constraint.json")), make_schema(v1_0::param_constraint) }, - { make_schema_uri(v1_0::tag, _XPLATSTR("param_constraint_number.json")), make_schema(v1_0::param_constraint_number) }, - { make_schema_uri(v1_0::tag, _XPLATSTR("param_constraint_rational.json")), make_schema(v1_0::param_constraint_rational) }, - { make_schema_uri(v1_0::tag, _XPLATSTR("param_constraint_string.json")), make_schema(v1_0::param_constraint_string) }, - { make_schema_uri(v1_0::tag, _XPLATSTR("receiver_constraint_sets.json")), make_schema(v1_0::receiver_constraint_sets) }, - }; - } - inline void merge(std::map& to, std::map&& from) { to.insert(from.begin(), from.end()); // std::map::merge in C++17 @@ -394,7 +359,6 @@ namespace nmos merge(result, make_is08_schemas()); merge(result, make_is09_schemas()); merge(result, make_is11_schemas()); - merge(result, make_bcp00401_schemas()); return result; } diff --git a/Development/third_party/bcp-004-01/README.md b/Development/third_party/bcp-004-01/README.md deleted file mode 100644 index 57375e929..000000000 --- a/Development/third_party/bcp-004-01/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# AMWA BCP-004-01 NMOS Receiver Capabilities - -This directory contains files from the [AMWA BCP-004-01 NMOS Receiver Capabilities](https://github.com/AMWA-TV/bcp-004-01), in particular tagged versions of the JSON schemas used by the API specifications. - -Original source code: - -- (c) AMWA 2022 -- Licensed under the Apache License, Version 2.0; http://www.apache.org/licenses/LICENSE-2.0 diff --git a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/constraint_set.json b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/constraint_set.json deleted file mode 100644 index ab439d378..000000000 --- a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/constraint_set.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Describes a Constraint Set", - "title": "Constraint Set", - "type": "object", - "minProperties": 1, - "properties": { - "urn:x-nmos:cap:meta:label": { - "description": "Freeform string label for the Constraint Set", - "type": "string" - }, - "urn:x-nmos:cap:meta:preference": { - "description": "This value expresses the relative 'weight' that the Receiver assigns to its preference for the streams satisfied by the associated Constraint Set. The weight is an integer in the range -100 through 100, where -100 is least preferred and 100 is most preferred. When the attribute is omitted, the effective value for the associated Constraint Set is 0.", - "type": "integer", - "default": 0, - "maximum": 100, - "minimum": -100 - }, - "urn:x-nmos:cap:meta:enabled": { - "description": "This value indicates whether a Constraint Set is available to use immediately (true) or whether this is an offline capability which can be activated via some unspecified configuration mechanism (false). When the attribute is omitted its value is assumed to be true.", - "type": "boolean", - "default": true - } - }, - "patternProperties": { - "^urn:x-nmos:cap:(?!meta:)": { - "$ref": "param_constraint.json" - } - } -} diff --git a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/constraint_sets.json b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/constraint_sets.json deleted file mode 100644 index 68e9229f4..000000000 --- a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/constraint_sets.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Describes a list of Constraint Sets", - "title": "Constraint Sets", - "type": "array", - "items": { - "$ref": "constraint_set.json" - } -} diff --git a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint.json b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint.json deleted file mode 100644 index 0537109c3..000000000 --- a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Describes a Parameter Constraint", - "title": "Parameter Constraint", - "type": "object", - "anyOf": [ - { "$ref": "param_constraint_string.json" }, - { "$ref": "param_constraint_integer.json" }, - { "$ref": "param_constraint_number.json" }, - { "$ref": "param_constraint_boolean.json" }, - { "$ref": "param_constraint_rational.json" } - ] -} diff --git a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_boolean.json b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_boolean.json deleted file mode 100644 index fac6b9966..000000000 --- a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_boolean.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Describes a Boolean Parameter Constraint", - "title": "Boolean Parameter Constraint", - "type": "object", - "properties": { - "enum": { - "type": "array", - "minItems": 1, - "items": { - "type": "boolean" - } - } - } -} diff --git a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_integer.json b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_integer.json deleted file mode 100644 index 35c5aec23..000000000 --- a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_integer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Describes an Integer Parameter Constraint", - "title": "Integer Parameter Constraint", - "type": "object", - "properties": { - "enum": { - "type": "array", - "minItems": 1, - "items": { - "type": "integer" - } - }, - "minimum": { - "type": "integer" - }, - "maximum": { - "type": "integer" - } - } -} diff --git a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_number.json b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_number.json deleted file mode 100644 index 24a93c1d6..000000000 --- a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_number.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Describes a Number Parameter Constraint", - "title": "Number Parameter Constraint", - "type": "object", - "properties": { - "enum": { - "type": "array", - "minItems": 1, - "items": { - "type": "number" - } - }, - "minimum": { - "type": "number" - }, - "maximum": { - "type": "number" - } - } -} diff --git a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_rational.json b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_rational.json deleted file mode 100644 index 31a8db61c..000000000 --- a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_rational.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Describes a Rational Parameter Constraint", - "title": "Rational Parameter Constraint", - "type": "object", - "properties": { - "enum": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/definitions/rational" - } - }, - "minimum": { - "$ref": "#/definitions/rational" - }, - "maximum": { - "$ref": "#/definitions/rational" - } - }, - "definitions": { - "rational": { - "type": "object", - "required": [ - "numerator" - ], - "properties": { - "numerator": { - "type": "integer" - }, - "denominator": { - "type": "integer", - "default": 1 - } - }, - "additionalProperties": false - } - } -} diff --git a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_string.json b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_string.json deleted file mode 100644 index a86600836..000000000 --- a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/param_constraint_string.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Describes a String Parameter Constraint", - "title": "String Parameter Constraint", - "type": "object", - "properties": { - "enum": { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - } -} diff --git a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/receiver_constraint_sets.json b/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/receiver_constraint_sets.json deleted file mode 100644 index cf5a10c8c..000000000 --- a/Development/third_party/bcp-004-01/v1.0.x/APIs/schemas/receiver_constraint_sets.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Describes a Receiver with Constraint Sets", - "title": "Receiver with Constraint Sets", - "type": "object", - "required": [ - "caps" - ], - "properties": { - "caps": { - "description": "Capabilities", - "type": "object", - "dependencies": { - "constraint_sets": [ - "version" - ] - }, - "properties": { - "version": { - "description": "String formatted TAI timestamp (:) indicating when an attribute of the 'caps' object last changed", - "type": "string", - "pattern": "^[0-9]+:[0-9]+$" - }, - "constraint_sets": { - "$ref": "constraint_sets.json" - } - } - } - } -} From 290c745f5b8ba6e1a9184cbbe338016ebb6c4fc3 Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Mon, 4 Mar 2024 15:20:15 +0400 Subject: [PATCH 102/109] Switch nmos-testing to AMWA-TV:is-11 --- .github/workflows/build-test.yml | 12 ++++++------ .github/workflows/src/amwa-test.yml | 6 +++--- Sandbox/run_nmos_testing.sh | 3 ++- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index b73b095d6..c60dd7908 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -340,10 +340,10 @@ jobs: set -x root_dir=`pwd` - # Install AMWA NMOS Testing Tool (N-Nagorny instead of AMWA-TV and is-11 instead of master until IS-11 tests are merged) - git clone https://github.com/N-Nagorny/nmos-testing.git + # Install AMWA NMOS Testing Tool (is-11 instead of master until IS-11 tests are merged) + git clone https://github.com/AMWA-TV/nmos-testing.git cd nmos-testing - git checkout is-11-independent-test-cases + git checkout is-11 # Configure the Testing Tool so all APIs are tested with TLS printf "from . import Config as CONFIG\nCONFIG.ENABLE_HTTPS = True\n" > nmostesting/UserConfig.py @@ -848,10 +848,10 @@ jobs: set -x root_dir=`pwd` - # Install AMWA NMOS Testing Tool (N-Nagorny instead of AMWA-TV and is-11 instead of master until IS-11 tests are merged) - git clone https://github.com/N-Nagorny/nmos-testing.git + # Install AMWA NMOS Testing Tool (is-11 instead of master until IS-11 tests are merged) + git clone https://github.com/AMWA-TV/nmos-testing.git cd nmos-testing - git checkout is-11-independent-test-cases + git checkout is-11 # Configure the Testing Tool so all APIs are tested with TLS printf "from . import Config as CONFIG\nCONFIG.ENABLE_HTTPS = True\n" > nmostesting/UserConfig.py diff --git a/.github/workflows/src/amwa-test.yml b/.github/workflows/src/amwa-test.yml index fbb8eda8a..09f885c7a 100644 --- a/.github/workflows/src/amwa-test.yml +++ b/.github/workflows/src/amwa-test.yml @@ -15,10 +15,10 @@ set -x root_dir=`pwd` - # Install AMWA NMOS Testing Tool (N-Nagorny instead of AMWA-TV and is-11 instead of master until IS-11 tests are merged) - git clone https://github.com/N-Nagorny/nmos-testing.git + # Install AMWA NMOS Testing Tool (is-11 instead of master until IS-11 tests are merged) + git clone https://github.com/AMWA-TV/nmos-testing.git cd nmos-testing - git checkout is-11-independent-test-cases + git checkout is-11 # Configure the Testing Tool so all APIs are tested with TLS printf "from . import Config as CONFIG\nCONFIG.ENABLE_HTTPS = True\n" > nmostesting/UserConfig.py diff --git a/Sandbox/run_nmos_testing.sh b/Sandbox/run_nmos_testing.sh index 82897fe7e..c7d3b62c8 100755 --- a/Sandbox/run_nmos_testing.sh +++ b/Sandbox/run_nmos_testing.sh @@ -123,7 +123,8 @@ else (( expected_disabled_IS_07_02+=21 )) (( expected_disabled_IS_08_01+=7 )) (( expected_disabled_IS_08_02+=14 )) - (( expected_disabled_IS_11_01+=21 )) + # test_04_03, test_04_03_01, test_04_03_01_01, test_04_03_02, test_04_03_02_01 + (( expected_disabled_IS_11_01+=26 )) # test_33, test_33_1 (( expected_disabled_IS_04_02+=16 )) (( expected_disabled_IS_09_01+=7 )) From effaa6f140176623020dd8fbcbb8af732b1a982a Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Wed, 6 Mar 2024 21:05:23 +0400 Subject: [PATCH 103/109] Add IS-11 docs --- Documents/IS-11.md | 129 ++++++++++++++++++ .../images/IS-11-Client-Request-Callbacks.png | Bin 0 -> 74244 bytes .../IS-11-Node-Model-Update-Callbacks.png | Bin 0 -> 75426 bytes 3 files changed, 129 insertions(+) create mode 100644 Documents/IS-11.md create mode 100644 Documents/images/IS-11-Client-Request-Callbacks.png create mode 100644 Documents/images/IS-11-Node-Model-Update-Callbacks.png diff --git a/Documents/IS-11.md b/Documents/IS-11.md new file mode 100644 index 000000000..c2b3d1d6e --- /dev/null +++ b/Documents/IS-11.md @@ -0,0 +1,129 @@ +# IS-11 implementation in nmos-cpp + +``nmos-cpp`` provides three means of control over application-specific IS-11 implementation behaviour: +- callback functions being called on client requests to the Stream Compatibility Management API +- callback functions being called on NMOS resource updates in ``node_model`` +- internal JSON properties of ``streamcompatibility_resources`` that are not shown to a client + +## Callbacks for client request handling + +``streamcompatibility_`` prefix in callback type names here and below is omitted for brevity (e.g. ``base_edid_handler``, cf. ``streamcompatibility_base_edid_handler``). + +See the sequence diagram below on how a Node uses these callbacks. + +![IS-11-Client-Request-Callbacks](images/IS-11-Client-Request-Callbacks.png) + +### base_edid_handler + +Callback inputs: +- an Input ID +- an Optional of a Base EDID + +Callback outputs: +- an exception if present + +``base_edid_handler`` notifies application-specific code about a Base EDID modification request (``PUT`` and ``DELETE`` operations). + +It's expected to throw on ``PUT`` in order to indicate failures (e.g. an EDID validation failure). + +### effective_edid_setter + +Callback inputs: +- an Input ID + +Callback outputs: +- a Variant of an Effective EDID or an href to it + +``effective_edid_setter`` demands application-specific code to set Effective EDID of a specific Input when it may be required. These cases are: +- a client requested a Base EDID modification (``PUT`` or ``DELETE``) +- a client requested modification of Active Constraints of some Sender (``PUT`` or ``DELETE``) that has this Input in its list of Inputs + +It's expected to never throw. + +### active_constraints_handler + +Callback inputs: +- a Sender resource +- requested Constraint Sets + +Callback outputs: +- a Boolean indicating whether the device can adhere to the requested Constraint Sets +- intersections of all combinations of each of the requested Constraint Sets and each of the Constraint Sets that describe the internal device capabilities +- an exception if present + +``active_constraints_handler`` notifies application-specific code about Active Constraints modification request (``PUT`` and ``DELETE`` operations). + +It's expected to throw in order to indicate failures. + +## Callbacks for node_model update handling + +[IS-11](https://specs.amwa.tv/is-11/) introduces Sender and Receiver states and has normative language on disabling Senders and Receivers in "bad" states (``active_constraints_violation`` for Senders and ``non_compliant_stream`` for Receivers). + +Mostly, they occur when a correctly operating Sender or Receiver gets reconfigured (a Flow of the Sender changes so that the Sender doesn't satisfy its Active Constraints anymore or the Receiver gets its Receiver Caps updated so that the ``transport_file`` in Connection API doesn't satisfy them anymore). + +``streamcompatibility_behaviour_thread`` tracks changes in the Node Model in case application-specific code updates resources in a way that causes these "bad" states. + +Though, application-specific code is free to decide whether a violation happened after such an update via ``sender_validator`` and ``receiver_validator`` callbacks. + +See the sequence diagram below on how this whole process looks like. + +![IS-11-Node-Model-Update-Callbacks](images/IS-11-Node-Model-Update-Callbacks.png) + +### sender_validator + +Callback inputs: +- a Sender resource +- its Connection Sender resource +- its Source resource +- its Flow resource +- Constraint Sets from Active Constraints + +Callback outputs: +- a Sender state +- a complementary debug message if present + +``sender_validator`` demands application-specific code to determine the Status of Sender based on provided info. + +It's expected to never throw. + +### receiver_validator + +Callback inputs: +- a Receiver resource +- its transport file from Connection API /active + +Callback outputs: +- a Receiver state +- a complementary debug message if present + +``receiver_validator`` demands application-specific code to determine the Status of Sender based on provided info. + +It's expected to never throw. + +## Internal JSON properties + +### temporarily_locked + +[IS-11](https://specs.amwa.tv/is-11/) prescribes that a ``PUT`` operation for ``/inputs/{inputId}/edid/base`` and ``/senders/{senderId}/constraints/active`` may be responded with ``423 Locked`` when the ``PUT`` payload "can't be applied temporarily due to Device restrictions (e.g. if the Sender is active)". + +As far as a variety of application-specific scenarios fall under this case, ``nmos-cpp`` provides ``temporarily_locked`` read-write property that controls whether the resources above are locked at the moment. + +#### Inputs + +NMOS Node applications based on ``nmos-cpp`` can create an IS-11 Input calling one of overloaded ``nmos::experimental::make_streamcompatibility_input``. Depending on whether the Input supports EDID and Base EDID, ``data`` member of the returned ``nmos::resource`` contains appropriate properties ``endpoint_effective_edid`` and ``endpoint_base_edid`` with ``temporarily_locked`` inside each one. + +#### Senders + +Each IS-11 Sender's ``nmos::resource`` representation contains ``endpoint_active_constraints`` property with ``temporarily_locked`` inside. + +### intersection_of_caps_and_constraints + +Each time Active Constraints are being ``PUT`` into a Sender, an NMOS Node application must verify whether the Sender can adhere to them. ``nmos-cpp-node`` application verifies it by checking if each Constraint Set (from ``M`` overall) in the Active Constraints gives a valid intersection with at least one of Constraint Sets (of ``N``) that represent capabilities of the Sender. ``M * N`` intersections are stored in ``intersection_of_caps_and_constraints`` to help reconfiguring the Sender and its related resources (e.g. building Effective EDIDs) in a way that takes into account not only the will of the user (Active Constraints) but also the capabilities of the Sender. + + \ No newline at end of file diff --git a/Documents/images/IS-11-Client-Request-Callbacks.png b/Documents/images/IS-11-Client-Request-Callbacks.png new file mode 100644 index 0000000000000000000000000000000000000000..729110e41c235f87af1bd3b34197b80db48d07b0 GIT binary patch literal 74244 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 $1<2R!5RzfQ6p#F{ruF#l5vP&Jk>l!nGY<&qPA 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 Zt0b#nx&6k@mp$;GlDrzaME2U9{|3=DJr@7~ literal 0 HcmV?d00001 diff --git a/Documents/images/IS-11-Node-Model-Update-Callbacks.png b/Documents/images/IS-11-Node-Model-Update-Callbacks.png new file mode 100644 index 0000000000000000000000000000000000000000..941182cbfc51d98fbffae0460a6259acdea1a52b GIT binary patch literal 75426 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 l{P73>{}caLp2zer64~OWhU@SAP4>V)hPtLYwOY Date: Wed, 6 Mar 2024 21:07:22 +0400 Subject: [PATCH 104/109] Temporarily disable IS-11 tests --- .github/workflows/build-test.yml | 6 ++---- .github/workflows/src/amwa-test.yml | 3 +-- Sandbox/run_nmos_testing.sh | 6 +++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index c7f438038..e6704fbfd 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -388,10 +388,9 @@ jobs: set -x root_dir=`pwd` - # Install AMWA NMOS Testing Tool (is-11 instead of master until IS-11 tests are merged) + # Install AMWA NMOS Testing Tool git clone https://github.com/AMWA-TV/nmos-testing.git cd nmos-testing - git checkout is-11 # Configure the Testing Tool so all APIs are tested with TLS and authorization printf "from . import Config as CONFIG\nCONFIG.ENABLE_HTTPS = True\nCONFIG.MOCK_SERVICES_WARM_UP_DELAY = 30\nCONFIG.HTTP_TIMEOUT = 2\n" > nmostesting/UserConfig.py @@ -945,10 +944,9 @@ jobs: set -x root_dir=`pwd` - # Install AMWA NMOS Testing Tool (is-11 instead of master until IS-11 tests are merged) + # Install AMWA NMOS Testing Tool git clone https://github.com/AMWA-TV/nmos-testing.git cd nmos-testing - git checkout is-11 # Configure the Testing Tool so all APIs are tested with TLS and authorization printf "from . import Config as CONFIG\nCONFIG.ENABLE_HTTPS = True\nCONFIG.MOCK_SERVICES_WARM_UP_DELAY = 30\nCONFIG.HTTP_TIMEOUT = 2\n" > nmostesting/UserConfig.py diff --git a/.github/workflows/src/amwa-test.yml b/.github/workflows/src/amwa-test.yml index 620489ca4..663c0b331 100644 --- a/.github/workflows/src/amwa-test.yml +++ b/.github/workflows/src/amwa-test.yml @@ -15,10 +15,9 @@ set -x root_dir=`pwd` - # Install AMWA NMOS Testing Tool (is-11 instead of master until IS-11 tests are merged) + # Install AMWA NMOS Testing Tool git clone https://github.com/AMWA-TV/nmos-testing.git cd nmos-testing - git checkout is-11 # Configure the Testing Tool so all APIs are tested with TLS and authorization printf "from . import Config as CONFIG\nCONFIG.ENABLE_HTTPS = True\nCONFIG.MOCK_SERVICES_WARM_UP_DELAY = 30\nCONFIG.HTTP_TIMEOUT = 2\n" > nmostesting/UserConfig.py diff --git a/Sandbox/run_nmos_testing.sh b/Sandbox/run_nmos_testing.sh index 4c6de7b34..ec661b542 100755 --- a/Sandbox/run_nmos_testing.sh +++ b/Sandbox/run_nmos_testing.sh @@ -36,7 +36,7 @@ expected_disabled_IS_08_02=0 expected_disabled_IS_09_02=0 expected_disabled_IS_04_02=0 expected_disabled_IS_09_01=0 -expected_disabled_IS_11_01=0 +# expected_disabled_IS_11_01=0 config_secure=`${run_python} -c $'from nmostesting import Config\nprint(Config.ENABLE_HTTPS)'` || (echo "error running python"; exit 1) config_dns_sd_mode=`${run_python} -c $'from nmostesting import Config\nprint(Config.DNS_SD_MODE)'` || (echo "error running python"; exit 1) @@ -142,7 +142,7 @@ else (( expected_disabled_IS_08_01+=7 )) (( expected_disabled_IS_08_02+=14 )) # test_04_03, test_04_03_01, test_04_03_01_01, test_04_03_02, test_04_03_02_01 - (( expected_disabled_IS_11_01+=26 )) + # (( expected_disabled_IS_11_01+=26 )) # test_33, test_33_1 (( expected_disabled_IS_04_02+=16 )) (( expected_disabled_IS_09_01+=7 )) @@ -215,7 +215,7 @@ do_run_test IS-08-02 $expected_disabled_IS_08_02 --host "${host}" "${host}" --po do_run_test IS-09-02 $expected_disabled_IS_09_02 --host "${host}" null --port 0 0 --version null v1.0 -do_run_test IS-11-01 $expected_disabled_IS_11_01 --host "${host}" "${host}" "${host}" --port 1080 1080 1080 --version v1.0 v1.3 v1.1 +# do_run_test IS-11-01 $expected_disabled_IS_11_01 --host "${host}" "${host}" "${host}" --port 1080 1080 1080 --version v1.0 v1.3 v1.1 # Run Registry tests (leave Node running) "${registry_command}" "{\"pri\":0,\"http_port\":8088 ${common_params} ${registry_params}}" > ${results_dir}/registryoutput 2>&1 & From a5223248eda68efd39897f807029e02437ef0c80 Mon Sep 17 00:00:00 2001 From: lo-simon Date: Mon, 11 Mar 2024 20:21:48 +0000 Subject: [PATCH 105/109] Remove spaces --- Development/nmos/settings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Development/nmos/settings.h b/Development/nmos/settings.h index cdd8c3d90..0f475d26d 100644 --- a/Development/nmos/settings.h +++ b/Development/nmos/settings.h @@ -106,7 +106,7 @@ namespace nmos // is11_versions [node]: used to specify the enabled API versions for a version-locked configuration const web::json::field_as_array is11_versions{ U("is11_versions") }; // when omitted, nmos::is11_versions::all is used - + // is12_versions [node]: used to specify the enabled API versions for a version-locked configuration const web::json::field_as_array is12_versions{ U("is12_versions") }; // when omitted, nmos::is12_versions::all is used From fbb447058b6a41ce72f19473dc1136d661650273 Mon Sep 17 00:00:00 2001 From: lo-simon Date: Mon, 11 Mar 2024 20:23:02 +0000 Subject: [PATCH 106/109] Add IS-10 support --- Development/nmos/node_resources.cpp | 3 ++- Development/nmos/node_server.cpp | 2 +- Development/nmos/scope.h | 3 +++ Development/nmos/streamcompatibility_api.cpp | 8 +++++++- Development/nmos/streamcompatibility_api.h | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Development/nmos/node_resources.cpp b/Development/nmos/node_resources.cpp index fdc9fe3ae..ea908a36e 100644 --- a/Development/nmos/node_resources.cpp +++ b/Development/nmos/node_resources.cpp @@ -125,7 +125,8 @@ namespace nmos { web::json::push_back(data[U("controls")], value_of({ { U("href"), streamcompatibility_uri.set_host(host).to_uri().to_string() }, - { U("type"), type } + { U("type"), type }, + { U("authorization"), nmos::experimental::fields::server_authorization(settings) } })); } } diff --git a/Development/nmos/node_server.cpp b/Development/nmos/node_server.cpp index 2dbf8ea54..c8a54e1dd 100644 --- a/Development/nmos/node_server.cpp +++ b/Development/nmos/node_server.cpp @@ -77,7 +77,7 @@ namespace nmos node_server.api_routers[{ {}, nmos::fields::channelmapping_port(node_model.settings) }].mount({}, nmos::make_channelmapping_api(node_model, node_implementation.validate_map, validate_authorization ? validate_authorization(nmos::experimental::scopes::channelmapping) : nullptr, gate)); // Configure the Stream Compatibility API - node_server.api_routers[{ {}, nmos::fields::streamcompatibility_port(node_model.settings) }].mount({}, nmos::experimental::make_streamcompatibility_api(node_model, node_implementation.base_edid_changed, node_implementation.set_effective_edid, node_implementation.active_constraints_changed, gate)); + node_server.api_routers[{ {}, nmos::fields::streamcompatibility_port(node_model.settings) }].mount({}, nmos::experimental::make_streamcompatibility_api(node_model, node_implementation.base_edid_changed, node_implementation.set_effective_edid, node_implementation.active_constraints_changed, validate_authorization ? validate_authorization(nmos::experimental::scopes::streamcompatibility) : nullptr, gate)); const auto& events_ws_port = nmos::fields::events_ws_port(node_model.settings); auto& events_ws_api = node_server.ws_handlers[{ {}, nmos::fields::events_ws_port(node_model.settings) }]; diff --git a/Development/nmos/scope.h b/Development/nmos/scope.h index 25d65004e..e28338af8 100644 --- a/Development/nmos/scope.h +++ b/Development/nmos/scope.h @@ -24,6 +24,8 @@ namespace nmos const scope events{ U("events") }; // IS-08 const scope channelmapping{ U("channelmapping") }; + // IS-11 + const scope streamcompatibility{ U("streamcompatibility") }; // IS-12 const scope ncp{ U("ncp") }; } @@ -42,6 +44,7 @@ namespace nmos if (scopes::netctrl.name == scope) { return scopes::netctrl; } if (scopes::events.name == scope) { return scopes::events; } if (scopes::channelmapping.name == scope) { return scopes::channelmapping; } + if (scopes::streamcompatibility.name == scope) { return scopes::streamcompatibility; } if (scopes::ncp.name == scope) { return scopes::ncp; } return{}; } diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index a45611c1a..05d518885 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -174,7 +174,7 @@ namespace nmos web::http::experimental::listener::api_router make_unmounted_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_handler base_edid_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_handler active_constraints_handler, slog::base_gate& gate); - web::http::experimental::listener::api_router make_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_handler base_edid_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_handler active_constraints_handler, slog::base_gate& gate) + web::http::experimental::listener::api_router make_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_handler base_edid_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_handler active_constraints_handler, web::http::experimental::listener::route_handler validate_authorization, slog::base_gate& gate) { using namespace web::http::experimental::listener::api_router_using_declarations; @@ -192,6 +192,12 @@ namespace nmos return pplx::task_from_result(true); }); + if (validate_authorization) + { + streamcompatibility_api.support(U("/x-nmos/") + nmos::patterns::streamcompatibility_api.pattern + U("/?"), validate_authorization); + streamcompatibility_api.support(U("/x-nmos/") + nmos::patterns::streamcompatibility_api.pattern + U("/.*"), validate_authorization); + } + const auto versions = with_read_lock(model.mutex, [&model] { return nmos::is11_versions::from_settings(model.settings); }); streamcompatibility_api.support(U("/x-nmos/") + nmos::patterns::streamcompatibility_api.pattern + U("/?"), methods::GET, [versions](http_request req, http_response res, const string_t&, const route_parameters&) { diff --git a/Development/nmos/streamcompatibility_api.h b/Development/nmos/streamcompatibility_api.h index c408e523d..4fbd188f4 100644 --- a/Development/nmos/streamcompatibility_api.h +++ b/Development/nmos/streamcompatibility_api.h @@ -37,7 +37,7 @@ namespace nmos typedef std::function& effective_edid)> streamcompatibility_effective_edid_setter; } - web::http::experimental::listener::api_router make_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_handler base_edid_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_handler active_constraints_handler, slog::base_gate& gate); + web::http::experimental::listener::api_router make_streamcompatibility_api(nmos::node_model& model, details::streamcompatibility_base_edid_handler base_edid_handler, details::streamcompatibility_effective_edid_setter effective_edid_setter, details::streamcompatibility_active_constraints_handler active_constraints_handler, web::http::experimental::listener::route_handler validate_authorization, slog::base_gate& gate); } } From 3fa472025aa6b600bcf8d3dcd95a39d367e77bae Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Thu, 14 Mar 2024 02:41:41 +0400 Subject: [PATCH 107/109] Apply suggestions from code review Co-authored-by: Simon Lo --- Development/nmos-cpp-node/node_implementation.cpp | 4 ++-- Development/nmos/model.h | 2 +- Development/nmos/node_server.h | 2 +- Development/nmos/streamcompatibility_api.cpp | 2 +- Development/nmos/streamcompatibility_behaviour.cpp | 7 +++---- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index a25694a3a..98a72da6c 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -970,7 +970,7 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; utility::string_t edid(edid_bytes, edid_bytes + sizeof(edid_bytes)); - + const auto input_id = impl::make_id(seed_id, nmos::types::input); const auto sender_ids = impl::make_ids(seed_id, nmos::types::sender, { impl::ports::video, impl::ports::audio }); @@ -2234,6 +2234,6 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode .on_set_effective_edid(set_effective_edid) // may be omitted if not required .on_active_constraints_changed(make_node_implementation_streamcompatibility_active_constraints_handler(model, gate)) .on_validate_sender_resources_against_active_constraints(make_node_implementation_streamcompatibility_sender_validator()) // may be omitted if the default is sufficient - .on_validate_receiver_against_transport_file(make_node_implementation_streamcompatibility_receiver_validator()) + .on_validate_receiver_against_transport_file(make_node_implementation_streamcompatibility_receiver_validator()) // may be omitted if the default is sufficient .on_control_protocol_property_changed(make_node_implementation_control_protocol_property_changed_handler(gate)); // may be omitted if IS-12 not required } diff --git a/Development/nmos/model.h b/Development/nmos/model.h index 936eb4b7e..24acd217c 100644 --- a/Development/nmos/model.h +++ b/Development/nmos/model.h @@ -105,7 +105,7 @@ namespace nmos // IS-11 senders, receivers, inputs and outputs for this node // see nmos/streamcompatibility_resources.h nmos::resources streamcompatibility_resources; - + // IS-12 resources for this node // see nmos/control_protocol_resources.h nmos::resources control_protocol_resources; diff --git a/Development/nmos/node_server.h b/Development/nmos/node_server.h index 94c9a96af..63b71aa06 100644 --- a/Development/nmos/node_server.h +++ b/Development/nmos/node_server.h @@ -129,7 +129,7 @@ namespace nmos load_authorization_clients_handler load_authorization_clients; save_authorization_client_handler save_authorization_client; request_authorization_code_handler request_authorization_code; - + nmos::experimental::details::streamcompatibility_base_edid_handler base_edid_changed; nmos::experimental::details::streamcompatibility_effective_edid_setter set_effective_edid; nmos::experimental::details::streamcompatibility_active_constraints_handler active_constraints_changed; diff --git a/Development/nmos/streamcompatibility_api.cpp b/Development/nmos/streamcompatibility_api.cpp index 05d518885..8db28f098 100644 --- a/Development/nmos/streamcompatibility_api.cpp +++ b/Development/nmos/streamcompatibility_api.cpp @@ -734,7 +734,7 @@ namespace nmos utility::string_t updated_timestamp; - modify_resource(resources, resourceId, [&effective_edid_setter, &updated_timestamp](nmos::resource& input) + modify_resource(resources, resourceId, [&updated_timestamp](nmos::resource& input) { input.data[nmos::fields::endpoint_base_edid] = make_streamcompatibility_dummy_edid_endpoint(); diff --git a/Development/nmos/streamcompatibility_behaviour.cpp b/Development/nmos/streamcompatibility_behaviour.cpp index a862479dd..900064d4b 100644 --- a/Development/nmos/streamcompatibility_behaviour.cpp +++ b/Development/nmos/streamcompatibility_behaviour.cpp @@ -1,6 +1,4 @@ #include "nmos/streamcompatibility_behaviour.h" -#include "nmos/streamcompatibility_state.h" -#include "nmos/streamcompatibility_utils.h" #include #include @@ -16,6 +14,8 @@ #include "nmos/resources.h" #include "nmos/sdp_utils.h" #include "nmos/slog.h" +#include "nmos/streamcompatibility_state.h" +#include "nmos/streamcompatibility_utils.h" #include "sdp/sdp.h" namespace nmos @@ -214,8 +214,7 @@ namespace nmos auto receiver = find_resource(node_resources, receiver_id_type); if (node_resources.end() == receiver) throw std::logic_error("Matching IS-04 receiver not found"); - updated = most_recent_update < nmos::fields::version(receiver->data); - + updated = most_recent_update < receiver->updated; if (updated) { slog::log(gate, SLOG_FLF) << "Receiver " << receiver_id << " has been updated recently and Status of Receiver is being updated as well"; From 7bce8b50bb06d5f3a3b0e0b49f9809fe2636556d Mon Sep 17 00:00:00 2001 From: N-Nagorny Date: Fri, 15 Mar 2024 13:05:11 +0400 Subject: [PATCH 108/109] IS-11 branch: v1.0-dev -> v1.0.x Co-authored-by: Simon Lo --- Development/nmos/streamcompatibility_api.h | 2 +- Development/nmos/streamcompatibility_resources.h | 4 ++-- Development/nmos/streamcompatibility_state.h | 16 ++++++++-------- .../nmos/streamcompatibility_validation.cpp | 2 +- .../nmos/streamcompatibility_validation.h | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Development/nmos/streamcompatibility_api.h b/Development/nmos/streamcompatibility_api.h index 4fbd188f4..b49ac37af 100644 --- a/Development/nmos/streamcompatibility_api.h +++ b/Development/nmos/streamcompatibility_api.h @@ -7,7 +7,7 @@ #include "nmos/slog.h" // Stream Compatibility Management API implementation -// See https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/StreamCompatibilityManagementAPI.html +// See https://specs.amwa.tv/is-11/branches/v1.0.x/APIs/StreamCompatibilityManagementAPI.html namespace nmos { struct node_model; diff --git a/Development/nmos/streamcompatibility_resources.h b/Development/nmos/streamcompatibility_resources.h index 57945b4fa..2beabb8f7 100644 --- a/Development/nmos/streamcompatibility_resources.h +++ b/Development/nmos/streamcompatibility_resources.h @@ -24,13 +24,13 @@ namespace nmos web::json::value make_streamcompatibility_edid_endpoint(const web::uri& edid_file, bool locked = false); web::json::value make_streamcompatibility_edid_endpoint(const utility::string_t& edid_file, bool locked = false); - // See https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/input.html + // See https://specs.amwa.tv/is-11/branches/v1.0.x/APIs/schemas/with-refs/input.html // Makes an input without EDID support nmos::resource make_streamcompatibility_input(const nmos::id& id, const nmos::id& device_id, bool connected, const std::vector& senders, const nmos::settings& settings); // Makes an input with EDID support nmos::resource make_streamcompatibility_input(const nmos::id& id, const nmos::id& device_id, bool connected, bool base_edid_support, const boost::variant& effective_edid, const std::vector& senders, const nmos::settings& settings); - // See https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/output.html + // See https://specs.amwa.tv/is-11/branches/v1.0.x/APIs/schemas/with-refs/output.html // Makes an output without EDID support nmos::resource make_streamcompatibility_output(const nmos::id& id, const nmos::id& device_id, bool connected, const std::vector& receivers, const nmos::settings& settings); // Makes an output with EDID support diff --git a/Development/nmos/streamcompatibility_state.h b/Development/nmos/streamcompatibility_state.h index 5f567a896..0fde92e52 100644 --- a/Development/nmos/streamcompatibility_state.h +++ b/Development/nmos/streamcompatibility_state.h @@ -6,8 +6,8 @@ namespace nmos { // Stream Compatibility Input states - // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#status-of-input - // and https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/input.html + // See https://specs.amwa.tv/is-11/branches/v1.0.x/docs/Behaviour_-_Server_Side.html#status-of-input + // and https://specs.amwa.tv/is-11/branches/v1.0.x/APIs/schemas/with-refs/input.html DEFINE_STRING_ENUM(input_state) namespace input_states { @@ -17,8 +17,8 @@ namespace nmos } // Stream Compatibility Output states - // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#status-of-output - // and https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/output.html + // See https://specs.amwa.tv/is-11/branches/v1.0.x/docs/Behaviour_-_Server_Side.html#status-of-output + // and https://specs.amwa.tv/is-11/branches/v1.0.x/APIs/schemas/with-refs/output.html DEFINE_STRING_ENUM(output_state) namespace output_states { @@ -28,8 +28,8 @@ namespace nmos } // Stream Compatibility Sender states - // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#status-of-sender - // and https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/sender-status.html + // See https://specs.amwa.tv/is-11/branches/v1.0.x/docs/Behaviour_-_Server_Side.html#status-of-sender + // and https://specs.amwa.tv/is-11/branches/v1.0.x/APIs/schemas/with-refs/sender-status.html DEFINE_STRING_ENUM(sender_state) namespace sender_states { @@ -41,8 +41,8 @@ namespace nmos } // Stream Compatibility Receiver states - // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#status-of-receiver - // and https://specs.amwa.tv/is-11/branches/v1.0-dev/APIs/schemas/with-refs/receiver-status.html + // See https://specs.amwa.tv/is-11/branches/v1.0.x/docs/Behaviour_-_Server_Side.html#status-of-receiver + // and https://specs.amwa.tv/is-11/branches/v1.0.x/APIs/schemas/with-refs/receiver-status.html DEFINE_STRING_ENUM(receiver_state) namespace receiver_states { diff --git a/Development/nmos/streamcompatibility_validation.cpp b/Development/nmos/streamcompatibility_validation.cpp index 810ed3ba6..da921370e 100644 --- a/Development/nmos/streamcompatibility_validation.cpp +++ b/Development/nmos/streamcompatibility_validation.cpp @@ -53,7 +53,7 @@ namespace nmos // An inactive Sender in this state MUST NOT allow activations. // At any time if State of an active Receiver becomes non_compliant_stream, the Receiver SHOULD become inactive. // An inactive Receiver in this state SHOULD NOT allow activations." - // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#preventing-restrictions-violation + // See https://specs.amwa.tv/is-11/branches/v1.0.x/docs/Behaviour_-_Server_Side.html#preventing-restrictions-violation nmos::details::connection_resource_patch_validator make_connection_streamcompatibility_validator(nmos::node_model& model) { return [&model] (const nmos::resource& resource, const nmos::resource& connection_resource, const web::json::value& endpoint_staged, slog::base_gate& gate) diff --git a/Development/nmos/streamcompatibility_validation.h b/Development/nmos/streamcompatibility_validation.h index 26f2cdbb2..9bad6a688 100644 --- a/Development/nmos/streamcompatibility_validation.h +++ b/Development/nmos/streamcompatibility_validation.h @@ -94,7 +94,7 @@ namespace nmos // An inactive Sender in this state MUST NOT allow activations. // At any time if State of an active Receiver becomes non_compliant_stream, the Receiver SHOULD become inactive. // An inactive Receiver in this state SHOULD NOT allow activations." - // See https://specs.amwa.tv/is-11/branches/v1.0-dev/docs/Behaviour_-_Server_Side.html#preventing-restrictions-violation + // See https://specs.amwa.tv/is-11/branches/v1.0.x/docs/Behaviour_-_Server_Side.html#preventing-restrictions-violation nmos::details::connection_resource_patch_validator make_connection_streamcompatibility_validator(nmos::node_model& model); details::streamcompatibility_sender_validator make_streamcompatibility_sender_resources_validator(const details::resource_constraints_matcher& resource_matcher, const details::transport_file_constraint_sets_matcher& transport_file_matcher); From 7d472861b40eda987e05b83d535dbe2b05a0a53d Mon Sep 17 00:00:00 2001 From: N-Nagorny <15526762+N-Nagorny@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:51:42 +0400 Subject: [PATCH 109/109] Update IS-11 docs --- Documents/IS-11.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Documents/IS-11.md b/Documents/IS-11.md index c2b3d1d6e..cfac4b3f6 100644 --- a/Documents/IS-11.md +++ b/Documents/IS-11.md @@ -15,23 +15,25 @@ See the sequence diagram below on how a Node uses these callbacks. ### base_edid_handler -Callback inputs: +Callback input parameters: - an Input ID - an Optional of a Base EDID -Callback outputs: +Callback output parameters: - an exception if present ``base_edid_handler`` notifies application-specific code about a Base EDID modification request (``PUT`` and ``DELETE`` operations). It's expected to throw on ``PUT`` in order to indicate failures (e.g. an EDID validation failure). +Since ``nmos-cpp`` doesn't parse EDID, the reference implementation of this callback violates the spec because it doesn't reject invalid EDIDs. Production implementations using IS-11 Inputs with Base EDID support must replace the reference callback with an EDID-aware one. + ### effective_edid_setter -Callback inputs: +Callback input parameters: - an Input ID -Callback outputs: +Callback output parameters: - a Variant of an Effective EDID or an href to it ``effective_edid_setter`` demands application-specific code to set Effective EDID of a specific Input when it may be required. These cases are: @@ -42,11 +44,11 @@ It's expected to never throw. ### active_constraints_handler -Callback inputs: +Callback input parameters: - a Sender resource - requested Constraint Sets -Callback outputs: +Callback output parameters: - a Boolean indicating whether the device can adhere to the requested Constraint Sets - intersections of all combinations of each of the requested Constraint Sets and each of the Constraint Sets that describe the internal device capabilities - an exception if present @@ -71,14 +73,14 @@ See the sequence diagram below on how this whole process looks like. ### sender_validator -Callback inputs: +Callback input parameters: - a Sender resource - its Connection Sender resource - its Source resource - its Flow resource - Constraint Sets from Active Constraints -Callback outputs: +Callback output parameters: - a Sender state - a complementary debug message if present @@ -88,11 +90,11 @@ It's expected to never throw. ### receiver_validator -Callback inputs: +Callback input parameters: - a Receiver resource - its transport file from Connection API /active -Callback outputs: +Callback output parameters: - a Receiver state - a complementary debug message if present