diff --git a/Development/cmake/NmosCppLibraries.cmake b/Development/cmake/NmosCppLibraries.cmake index 6a8b92456..93d4964ac 100644 --- a/Development/cmake/NmosCppLibraries.cmake +++ b/Development/cmake/NmosCppLibraries.cmake @@ -407,9 +407,50 @@ set(NMOS_IS05_SCHEMAS_HEADERS nmos/is05_schemas/is05_schemas.h ) +set(NMOS_IS05_V1_2_TAG v1.2-dev) set(NMOS_IS05_V1_1_TAG v1.1.x) set(NMOS_IS05_V1_0_TAG v1.0.x) +set(NMOS_IS05_V1_2_SCHEMAS_JSON + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/activation-response-schema.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/activation-schema.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/bulk-receiver-post-schema.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/bulk-response-schema.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/bulk-sender-post-schema.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/connectionapi-base.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/connectionapi-bulk.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/connectionapi-receiver.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/connectionapi-sender.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/connectionapi-single.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/constraint-schema.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/constraints-schema.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/constraints-schema-mqtt.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/constraints-schema-rtp.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/constraints-schema-websocket.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/error.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/receiver_transport_params.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/receiver_transport_params_dash.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/receiver_transport_params_ext.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/receiver_transport_params_mqtt.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/receiver_transport_params_mxl.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/receiver_transport_params_rtp.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/receiver_transport_params_websocket.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/receiver-response-schema.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/receiver-stage-schema.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/receiver-transport-file.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/sender_transport_params.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/sender_transport_params_dash.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/sender_transport_params_ext.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/sender_transport_params_mqtt.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/sender_transport_params_mxl.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/sender_transport_params_rtp.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/sender_transport_params_websocket.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/sender-receiver-base.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/sender-response-schema.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/sender-stage-schema.json + third_party/is-05/${NMOS_IS05_V1_2_TAG}/APIs/schemas/transporttype-response-schema.json + ) + set(NMOS_IS05_V1_1_SCHEMAS_JSON third_party/is-05/${NMOS_IS05_V1_1_TAG}/APIs/schemas/activation-response-schema.json third_party/is-05/${NMOS_IS05_V1_1_TAG}/APIs/schemas/activation-schema.json @@ -474,10 +515,11 @@ set(NMOS_IS05_V1_0_SCHEMAS_JSON set(NMOS_IS05_SCHEMAS_JSON_MATCH "third_party/is-05/([^/]+)/APIs/schemas/([^;]+)\\.json") set(NMOS_IS05_SCHEMAS_SOURCE_REPLACE "${CMAKE_CURRENT_BINARY_DIR_REPLACE}/nmos/is05_schemas/\\1/\\2.cpp") +string(REGEX REPLACE "${NMOS_IS05_SCHEMAS_JSON_MATCH}(;|$)" "${NMOS_IS05_SCHEMAS_SOURCE_REPLACE}\\3" NMOS_IS05_V1_2_SCHEMAS_SOURCES "${NMOS_IS05_V1_2_SCHEMAS_JSON}") string(REGEX REPLACE "${NMOS_IS05_SCHEMAS_JSON_MATCH}(;|$)" "${NMOS_IS05_SCHEMAS_SOURCE_REPLACE}\\3" NMOS_IS05_V1_1_SCHEMAS_SOURCES "${NMOS_IS05_V1_1_SCHEMAS_JSON}") string(REGEX REPLACE "${NMOS_IS05_SCHEMAS_JSON_MATCH}(;|$)" "${NMOS_IS05_SCHEMAS_SOURCE_REPLACE}\\3" NMOS_IS05_V1_0_SCHEMAS_SOURCES "${NMOS_IS05_V1_0_SCHEMAS_JSON}") -foreach(JSON ${NMOS_IS05_V1_1_SCHEMAS_JSON} ${NMOS_IS05_V1_0_SCHEMAS_JSON}) +foreach(JSON ${NMOS_IS05_V1_2_SCHEMAS_JSON} ${NMOS_IS05_V1_1_SCHEMAS_JSON} ${NMOS_IS05_V1_0_SCHEMAS_JSON}) string(REGEX REPLACE "${NMOS_IS05_SCHEMAS_JSON_MATCH}" "${NMOS_IS05_SCHEMAS_SOURCE_REPLACE}" SOURCE "${JSON}") string(REGEX REPLACE "${NMOS_IS05_SCHEMAS_JSON_MATCH}" "\\1" NS "${JSON}") string(REGEX REPLACE "${NMOS_IS05_SCHEMAS_JSON_MATCH}" "\\2" VAR "${JSON}") @@ -509,11 +551,13 @@ endforeach() add_library( nmos_is05_schemas STATIC ${NMOS_IS05_SCHEMAS_HEADERS} + ${NMOS_IS05_V1_2_SCHEMAS_SOURCES} ${NMOS_IS05_V1_1_SCHEMAS_SOURCES} ${NMOS_IS05_V1_0_SCHEMAS_SOURCES} ) source_group("nmos\\is05_schemas\\Header Files" FILES ${NMOS_IS05_SCHEMAS_HEADERS}) +source_group("nmos\\is05_schemas\\${NMOS_IS05_V1_2_TAG}\\Source Files" FILES ${NMOS_IS05_V1_2_SCHEMAS_SOURCES}) source_group("nmos\\is05_schemas\\${NMOS_IS05_V1_1_TAG}\\Source Files" FILES ${NMOS_IS05_V1_1_SCHEMAS_SOURCES}) source_group("nmos\\is05_schemas\\${NMOS_IS05_V1_0_TAG}\\Source Files" FILES ${NMOS_IS05_V1_0_SCHEMAS_SOURCES}) @@ -1170,6 +1214,7 @@ set(NMOS_CPP_NMOS_HEADERS nmos/mdns_api.h nmos/mdns_versions.h nmos/media_type.h + nmos/mxl.h nmos/model.h nmos/mutex.h nmos/node_api.h diff --git a/Development/cmake/NmosCppTest.cmake b/Development/cmake/NmosCppTest.cmake index 2acecf83e..70bfbafa8 100644 --- a/Development/cmake/NmosCppTest.cmake +++ b/Development/cmake/NmosCppTest.cmake @@ -60,6 +60,7 @@ set(NMOS_CPP_TEST_NMOS_TEST_SOURCES nmos/test/query_api_test.cpp nmos/test/sdp_test_utils.cpp nmos/test/sdp_utils_test.cpp + nmos/test/settings_test.cpp nmos/test/slog_test.cpp nmos/test/system_resources_test.cpp nmos/test/video_jxsv_test.cpp diff --git a/Development/nmos-cpp-node/config.json b/Development/nmos-cpp-node/config.json index 43347c669..05f4f6ee4 100644 --- a/Development/nmos-cpp-node/config.json +++ b/Development/nmos-cpp-node/config.json @@ -22,6 +22,7 @@ // senders, receivers: controls which kinds of sender and receiver are instantiated by the example node // the values must be an array of unique strings identifying the kinds of 'port', like ["v", "a", "d"], see impl::ports + // for MXL senders and receivers, the values must be like ["xv", "xa", "xd"] // when omitted, all ports are instantiated //"senders": ["v", "a"], //"receivers": [], @@ -51,8 +52,11 @@ // component_depth: controls the bits per component sample of video flows //"component_depth": 10, - // video_type: media type of video flows, e.g. "video/raw" or "video/jxsv", see nmos::media_types - //"video_type": "video/jxsv", + // video_type: media type of video flows, e.g. "video/raw", "video/jxsv" for ST 2110 senders and receivers, see nmos::media_types + //"video_type": "video/raw", + + // mxl_video_type: media type of MXL video flows and receivers, e.g. "video/v210" or "video/v210a", see nmos/mxl.h + //"mxl_video_type": "video/v210", // channel_count: controls the number of channels in audio sources //"channel_count": 8, @@ -213,6 +217,9 @@ // seed id [registry, node]: optional, used to generate repeatable id values when running with the same configuration //"seed_id": uuid-string, + // mxl_domain_id [node]: optional, overrides the generated MXL domain id used by MXL sender/receiver transport params + //"mxl_domain_id": uuid-string, + // label [registry, node]: used in resource label field //"label": "", diff --git a/Development/nmos-cpp-node/main.cpp b/Development/nmos-cpp-node/main.cpp index 460ceb8ca..c212bfc3a 100644 --- a/Development/nmos-cpp-node/main.cpp +++ b/Development/nmos-cpp-node/main.cpp @@ -67,6 +67,12 @@ int main(int argc, char* argv[]) } } + // Validate the standard and example-node-specific settings (before inserting + // run-time defaults, so that errors in user-provided settings are reported with + // the offending key rather than as a bare json_exception via the field accessors) + // (throws web::json::json_exception with the offending key in the message) + validate_node_implementation_settings(node_model.settings); + // Prepare run-time default settings (different than header defaults) nmos::insert_node_default_settings(node_model.settings); diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 56291cd9e..6c4303fa9 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1,6 +1,8 @@ #include "node_implementation.h" +#include #include +#include #include #include #include @@ -10,6 +12,7 @@ #include #include "pplx/pplx_utils.h" // for pplx::complete_after, etc. #include "cpprest/host_utils.h" +#include "cpprest/json_validator.h" #ifdef HAVE_LLDP #include "lldp/lldp_manager.h" #endif @@ -39,6 +42,7 @@ #endif #include "nmos/media_type.h" #include "nmos/model.h" +#include "nmos/mxl.h" #include "nmos/node_interfaces.h" #include "nmos/node_resource.h" #include "nmos/node_resources.h" @@ -119,6 +123,9 @@ namespace impl // video_type: media type of video flows, e.g. "video/raw" or "video/jxsv", see nmos::media_types const web::json::field_as_string_or video_type{ U("video_type"), U("video/raw") }; + // mxl_video_type: media type of MXL video flows and receivers, e.g. "video/v210" or "video/v210a", see nmos/mxl.h + const web::json::field_as_string_or mxl_video_type{ U("mxl_video_type"), U("video/v210") }; + // channel_count: controls the number of channels in audio sources const web::json::field_as_integer_or channel_count{ U("channel_count"), 4 }; @@ -127,6 +134,9 @@ namespace impl // simulate_status_monitor_activity: when true status monitor statuses will change randomly after activation const web::json::field_as_bool_or simulate_status_monitor_activity{U("simulate_status_monitor_activity"), true}; + + // mxl_domain_id: optional, used to override the generated MXL domain id + const web::json::field_as_string_or mxl_domain_id{ U("mxl_domain_id"), {} }; } nmos::interlace_mode get_interlace_mode(const nmos::settings& settings); @@ -154,13 +164,22 @@ namespace impl // example number/enum event const port catcall{ U("c") }; + // video/v210, video/v210a, etc. + const port mxl_video{ U("xv") }; + // audio/float32 + const port mxl_audio{ U("xa") }; + // video/smpte291 + const port mxl_data{ U("xd") }; + const std::vector rtp{ video, audio, data, mux }; const std::vector ws{ temperature, burn, nonsense, catcall }; - const std::vector all{ boost::copy_range>(boost::range::join(rtp, ws)) }; + const std::vector mxl{ mxl_video, mxl_audio, mxl_data }; + const std::vector all{ boost::copy_range>(boost::join(boost::join(rtp, ws), mxl)) }; } bool is_rtp_port(const port& port); bool is_ws_port(const port& port); + bool is_mxl_port(const port& port); std::vector parse_ports(const web::json::value& value); const std::vector channels_repeat{ @@ -400,13 +419,19 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr const auto seed_id = nmos::experimental::fields::seed_id(model.settings); const auto node_id = impl::make_id(seed_id, nmos::types::node); const auto device_id = impl::make_id(seed_id, nmos::types::device); + // If mxl_domain_id is not set, generate a random UUID for the MXL domain + const auto mxl_domain_id = impl::fields::mxl_domain_id(model.settings).empty() + ? nmos::make_repeatable_id(seed_id, U("/x-nmos/mxl/domain")) + : impl::fields::mxl_domain_id(model.settings); const auto how_many = impl::fields::how_many(model.settings); const auto sender_ports = impl::parse_ports(impl::fields::senders(model.settings)); const auto rtp_sender_ports = boost::copy_range>(sender_ports | boost::adaptors::filtered(impl::is_rtp_port)); const auto ws_sender_ports = boost::copy_range>(sender_ports | boost::adaptors::filtered(impl::is_ws_port)); + const auto mxl_sender_ports = boost::copy_range>(sender_ports | boost::adaptors::filtered(impl::is_mxl_port)); const auto receiver_ports = impl::parse_ports(impl::fields::receivers(model.settings)); const auto rtp_receiver_ports = boost::copy_range>(receiver_ports | boost::adaptors::filtered(impl::is_rtp_port)); const auto ws_receiver_ports = boost::copy_range>(receiver_ports | boost::adaptors::filtered(impl::is_ws_port)); + const auto mxl_receiver_ports = boost::copy_range>(receiver_ports | boost::adaptors::filtered(impl::is_mxl_port)); 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); @@ -416,6 +441,7 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr const auto sampling = sdp::sampling{ impl::fields::color_sampling(model.settings) }; const auto bit_depth = impl::fields::component_depth(model.settings); const auto video_type = nmos::media_type{ impl::fields::video_type(model.settings) }; + const auto mxl_video_type = nmos::media_type{ impl::fields::mxl_video_type(model.settings) }; const auto channel_count = impl::fields::channel_count(model.settings); const auto smpte2022_7 = impl::fields::smpte2022_7(model.settings); @@ -528,13 +554,14 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr { auto sender_ids = impl::make_ids(seed_id, nmos::types::sender, rtp_sender_ports, how_many); if (0 <= nmos::fields::events_port(model.settings)) boost::range::push_back(sender_ids, impl::make_ids(seed_id, nmos::types::sender, ws_sender_ports, how_many)); + boost::range::push_back(sender_ids, impl::make_ids(seed_id, nmos::types::sender, mxl_sender_ports, how_many)); auto receiver_ids = impl::make_ids(seed_id, nmos::types::receiver, receiver_ports, how_many); auto device = nmos::make_device(device_id, node_id, sender_ids, receiver_ids, model.settings); device.data[nmos::fields::tags] = impl::fields::device_tags(model.settings); if (!insert_resource_after(delay_millis, model.node_resources, std::move(device), gate)) throw node_implementation_init_exception(); } - // example sources, flows and senders + // example rtp sources, flows and senders for (int index = 0; index < how_many; ++index) { for (const auto& port : rtp_sender_ports) @@ -672,7 +699,7 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr } } - // example receivers + // example rtp receivers for (int index = 0; index < how_many; ++index) { for (const auto& port : rtp_receiver_ports) @@ -903,6 +930,152 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr } } + // example mxl sources, flows and senders + for (int index = 0; index < how_many; ++index) + { + for (const auto& port : mxl_sender_ports) + { + const auto source_id = impl::make_id(seed_id, nmos::types::source, port, index); + const auto flow_id = impl::make_id(seed_id, nmos::types::flow, port, index); + const auto sender_id = impl::make_id(seed_id, nmos::types::sender, port, index); + + nmos::resource source; + if (impl::ports::mxl_video == port) + { + source = nmos::make_video_source(source_id, device_id, nmos::clock_names::clk0, frame_rate, model.settings); + } + else if (impl::ports::mxl_audio == port) + { + const auto channels = boost::copy_range>(boost::irange(0, channel_count) | boost::adaptors::transformed([&](const int& index) + { + return impl::channels_repeat[index % (int)impl::channels_repeat.size()]; + })); + + source = nmos::make_audio_source(source_id, device_id, nmos::clock_names::clk0, frame_rate, channels, model.settings); + } + else if (impl::ports::mxl_data == port) + { + source = nmos::make_data_source(source_id, device_id, nmos::clock_names::clk0, frame_rate, model.settings); + } + impl::insert_parents(source, seed_id, port, index); + impl::set_label_description(source, port, index); + + nmos::resource flow; + + if (impl::ports::mxl_video == port) + { + flow = nmos::make_coded_video_flow( + flow_id, source_id, device_id, + frame_rate, + frame_width, frame_height, interlace_mode, + colorspace, transfer_characteristic, sampling, bit_depth, + mxl_video_type, + model.settings + ); + } + else if (impl::ports::mxl_audio == port) + { + flow = nmos::make_raw_audio_flow(flow_id, source_id, device_id, 48000, nmos::media_types::audio_float32, 32, model.settings); + } + else if (impl::ports::mxl_data == port) + { + nmos::did_sdid timecode{ 0x60, 0x60 }; + flow = nmos::make_sdianc_data_flow(flow_id, source_id, device_id, { timecode }, model.settings); + // add optional grain_rate + flow.data[nmos::fields::grain_rate] = nmos::make_rational(frame_rate); + } + impl::insert_parents(flow, seed_id, port, index); + impl::set_label_description(flow, port, index); + + auto sender = nmos::make_sender(sender_id, flow_id, nmos::transports::mxl, device_id, {}, {}, model.settings); + impl::set_label_description(sender, port, index); + impl::insert_group_hint(sender, port, index); + + auto connection_sender = nmos::make_connection_mxl_sender(sender_id, mxl_domain_id, flow_id); + + if (impl::fields::activate_senders(model.settings)) + { + // initialize this sender with a scheduled activation, e.g. to enable the IS-05-01 test suite to run immediately + auto& staged = connection_sender.data[nmos::fields::endpoint_staged]; + staged[nmos::fields::master_enable] = value::boolean(true); + staged[nmos::fields::activation] = value_of({ + { nmos::fields::mode, nmos::activation_modes::activate_scheduled_relative.name }, + { nmos::fields::requested_time, U("0:0") }, + { nmos::fields::activation_time, nmos::make_version() } + }); + } + + resolve_auto(sender, connection_sender, connection_sender.data[nmos::fields::endpoint_active][nmos::fields::transport_params]); + + if (!insert_resource_after(delay_millis, model.node_resources, std::move(source), gate)) throw node_implementation_init_exception(); + if (!insert_resource_after(delay_millis, model.node_resources, std::move(flow), gate)) throw node_implementation_init_exception(); + if (!insert_resource_after(delay_millis, model.node_resources, std::move(sender), gate)) throw node_implementation_init_exception(); + if (!insert_resource_after(delay_millis, model.connection_resources, std::move(connection_sender), gate)) throw node_implementation_init_exception(); + } + } + + // example mxl receivers + for (int index = 0; index < how_many; ++index) + { + for (const auto& port : mxl_receiver_ports) + { + const auto receiver_id = impl::make_id(seed_id, nmos::types::receiver, port, index); + + nmos::resource receiver; + if (impl::ports::mxl_video == port) + { + receiver = nmos::make_receiver(receiver_id, device_id, nmos::transports::mxl, {}, nmos::formats::video, { mxl_video_type }, model.settings); + const auto interlace_modes = nmos::interlace_modes::progressive != interlace_mode + ? std::vector{ nmos::interlace_modes::interlaced_bff.name, nmos::interlace_modes::interlaced_tff.name, nmos::interlace_modes::interlaced_psf.name } + : std::vector{ nmos::interlace_modes::progressive.name }; + receiver.data[nmos::fields::caps][nmos::fields::constraint_sets] = value_of({ + value_of({ + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ mxl_video_type.name }) }, + { 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::interlace_mode, nmos::make_caps_string_constraint(interlace_modes) }, + { nmos::caps::format::color_sampling, nmos::make_caps_string_constraint({ sampling.name }) }, + { nmos::caps::format::component_depth, nmos::make_caps_integer_constraint({ bit_depth }) } + }) + }); + receiver.data[nmos::fields::version] = receiver.data[nmos::fields::caps][nmos::fields::version] = value(nmos::make_version()); + } + else if (impl::ports::mxl_audio == port) + { + receiver = nmos::make_receiver(receiver_id, device_id, nmos::transports::mxl, {}, nmos::formats::audio, { nmos::media_types::audio_float32 }, model.settings); + receiver.data[nmos::fields::caps][nmos::fields::constraint_sets] = value_of({ + value_of({ + { nmos::caps::format::media_type, nmos::make_caps_string_constraint({ nmos::media_types::audio_float32.name }) }, + { nmos::caps::format::channel_count, nmos::make_caps_integer_constraint({}, 1, channel_count) }, + { nmos::caps::format::sample_rate, nmos::make_caps_rational_constraint({ { 48000, 1 } }) }, + { nmos::caps::format::sample_depth, nmos::make_caps_integer_constraint({ 32 }) } + }) + }); + receiver.data[nmos::fields::version] = receiver.data[nmos::fields::caps][nmos::fields::version] = value(nmos::make_version()); + } + else if (impl::ports::mxl_data == port) + { + receiver = nmos::make_sdianc_data_receiver(receiver_id, device_id, nmos::transports::mxl, {}, model.settings); + receiver.data[nmos::fields::caps][nmos::fields::constraint_sets] = value_of({ + value_of({ + { nmos::caps::format::grain_rate, nmos::make_caps_rational_constraint({ frame_rate }) } + }) + }); + receiver.data[nmos::fields::version] = receiver.data[nmos::fields::caps][nmos::fields::version] = value(nmos::make_version()); + } + impl::set_label_description(receiver, port, index); + impl::insert_group_hint(receiver, port, index); + + auto connection_receiver = nmos::make_connection_mxl_receiver(receiver_id, mxl_domain_id); + + resolve_auto(receiver, connection_receiver, connection_receiver.data[nmos::fields::endpoint_active][nmos::fields::transport_params]); + + if (!insert_resource_after(delay_millis, model.node_resources, std::move(receiver), gate)) throw node_implementation_init_exception(); + if (!insert_resource_after(delay_millis, model.connection_resources, std::move(connection_receiver), gate)) throw node_implementation_init_exception(); + } + } + // example channelmapping resources demonstrating a range of input/output capabilities // see https://github.com/sony/nmos-cpp/issues/111#issuecomment-740613137 @@ -1545,7 +1718,8 @@ void node_implementation_run(nmos::node_model& model, nmos::experimental::contro const auto sender_ports = impl::parse_ports(impl::fields::senders(model.settings)); const auto rtp_sender_ports = boost::copy_range>(sender_ports | boost::adaptors::filtered(impl::is_rtp_port)); const auto ws_sender_ports = boost::copy_range>(sender_ports | boost::adaptors::filtered(impl::is_ws_port)); - const auto rtp_receiver_ports = boost::copy_range>(impl::parse_ports(impl::fields::receivers(model.settings)) | boost::adaptors::filtered(impl::is_rtp_port)); + const auto receiver_ports = impl::parse_ports(impl::fields::receivers(model.settings)); + const auto rtp_receiver_ports = boost::copy_range>(receiver_ports | boost::adaptors::filtered(impl::is_rtp_port)); const auto simulate_status_monitor_activity = impl::fields::simulate_status_monitor_activity(model.settings); auto& control_protocol_resources = model.control_protocol_resources; @@ -1851,6 +2025,13 @@ 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) { + // BCP-007-03: MXL receivers do not use transport_file (non-null data is rejected here when parsed) + const nmos::transport transport_subclassification{nmos::fields::transport(receiver.data)}; + if (nmos::transports::mxl == nmos::transport_base(transport_subclassification)) + { + throw std::runtime_error("MXL does not use a transport_file"); + } + 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)) @@ -1883,19 +2064,25 @@ nmos::connection_resource_auto_resolver make_node_implementation_auto_resolver(c const auto seed_id = nmos::experimental::fields::seed_id(settings); const auto device_id = impl::make_id(seed_id, nmos::types::device); const auto how_many = impl::fields::how_many(settings); - const auto rtp_sender_ports = boost::copy_range>(impl::parse_ports(impl::fields::senders(settings)) | boost::adaptors::filtered(impl::is_rtp_port)); + const auto sender_ports = impl::parse_ports(impl::fields::senders(settings)); + const auto rtp_sender_ports = boost::copy_range>(sender_ports | boost::adaptors::filtered(impl::is_rtp_port)); const auto rtp_sender_ids = impl::make_ids(seed_id, nmos::types::sender, rtp_sender_ports, how_many); - const auto ws_sender_ports = boost::copy_range>(impl::parse_ports(impl::fields::senders(settings)) | boost::adaptors::filtered(impl::is_ws_port)); + const auto ws_sender_ports = boost::copy_range>(sender_ports | boost::adaptors::filtered(impl::is_ws_port)); const auto ws_sender_ids = impl::make_ids(seed_id, nmos::types::sender, ws_sender_ports, how_many); + const auto mxl_sender_ports = boost::copy_range>(sender_ports | boost::adaptors::filtered(impl::is_mxl_port)); + const auto mxl_sender_ids = impl::make_ids(seed_id, nmos::types::sender, mxl_sender_ports, how_many); const auto ws_sender_uri = nmos::make_events_ws_api_connection_uri(device_id, settings); - const auto rtp_receiver_ports = boost::copy_range>(impl::parse_ports(impl::fields::receivers(settings)) | boost::adaptors::filtered(impl::is_rtp_port)); + const auto receiver_ports = impl::parse_ports(impl::fields::receivers(settings)); + const auto rtp_receiver_ports = boost::copy_range>(receiver_ports | boost::adaptors::filtered(impl::is_rtp_port)); const auto rtp_receiver_ids = impl::make_ids(seed_id, nmos::types::receiver, rtp_receiver_ports, how_many); - const auto ws_receiver_ports = boost::copy_range>(impl::parse_ports(impl::fields::receivers(settings)) | boost::adaptors::filtered(impl::is_ws_port)); + const auto ws_receiver_ports = boost::copy_range>(receiver_ports | boost::adaptors::filtered(impl::is_ws_port)); const auto ws_receiver_ids = impl::make_ids(seed_id, nmos::types::receiver, ws_receiver_ports, how_many); + const auto mxl_receiver_ports = boost::copy_range>(receiver_ports | boost::adaptors::filtered(impl::is_mxl_port)); + const auto mxl_receiver_ids = impl::make_ids(seed_id, nmos::types::receiver, mxl_receiver_ports, how_many); // although which properties may need to be defaulted depends on the resource type, // the default value will almost always be different for each resource - return [rtp_sender_ids, rtp_receiver_ids, ws_sender_ids, ws_sender_uri, ws_receiver_ids](const nmos::resource& resource, const nmos::resource& connection_resource, value& transport_params) + return [rtp_sender_ids, rtp_receiver_ids, ws_sender_ids, ws_sender_uri, ws_receiver_ids, mxl_sender_ids, mxl_receiver_ids](const nmos::resource& resource, const nmos::resource& connection_resource, value& transport_params) { const std::pair id_type{ connection_resource.id, connection_resource.type }; // this code relies on the specific constraints added by node_implementation_thread @@ -1930,6 +2117,16 @@ nmos::connection_resource_auto_resolver make_node_implementation_auto_resolver(c { nmos::details::resolve_auto(transport_params[0], nmos::fields::connection_authorization, [&] { return value::boolean(false); }); } + else if (mxl_sender_ids.end() != boost::range::find(mxl_sender_ids, id_type.first) + || mxl_receiver_ids.end() != boost::range::find(mxl_receiver_ids, id_type.first)) + { + nmos::details::resolve_auto(transport_params[0], nmos::fields::mxl_domain_id, [&] { return web::json::front(nmos::fields::constraint_enum(constraints.at(0).at(nmos::fields::mxl_domain_id))); }); + if (nmos::types::sender == id_type.second) + { + // BCP-007-03: mxl_flow_id does not use "auto" on receivers (UUID or null only). + nmos::details::resolve_auto(transport_params[0], nmos::fields::mxl_flow_id, [&] { return web::json::front(nmos::fields::constraint_enum(constraints.at(0).at(nmos::fields::mxl_flow_id))); }); + } + } }; } @@ -2269,6 +2466,11 @@ namespace impl return impl::ports::ws.end() != boost::range::find(impl::ports::ws, port); } + bool is_mxl_port(const impl::port& port) + { + return impl::ports::mxl.end() != boost::range::find(impl::ports::mxl, port); + } + std::vector parse_ports(const web::json::value& value) { if (value.is_null()) return impl::ports::all; @@ -2375,6 +2577,88 @@ namespace impl { web::json::push_back(resource.data[nmos::fields::tags][nmos::fields::group_hint], nmos::make_group_hint({ U("example"), resource.type.name + U(' ') + port.name + utility::conversions::details::to_string_t(index) })); } + + // JSON schema covering the example-node-specific impl::fields::* settings; combined with + // nmos::validate_node_settings, this covers all the properties this example reads from the + // settings JSON. additionalProperties is not set to false, so all other (standard) settings + // pass through unchecked, allowing the two validators to be composed. + // definitions: aliases (in nmos::details::settings_definitions_schema() source order) first, then local + static const char* node_settings_schema_text = R"-schema-( +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "definitions": { + "nonNegativeInteger": { "$ref": "urn:x-nmos-cpp:schemas:defs#/definitions/nonNegativeInteger" }, + "positiveInteger": { "$ref": "urn:x-nmos-cpp:schemas:defs#/definitions/positiveInteger" }, + "rational": { "$ref": "urn:x-nmos-cpp:schemas:defs#/definitions/rational" }, + "tags": { "$ref": "urn:x-nmos-cpp:schemas:defs#/definitions/tags" }, + "uuid": { "$ref": "urn:x-nmos-cpp:schemas:defs#/definitions/uuid" }, + "interlaceMode": { "$ref": "urn:x-nmos-cpp:schemas:defs#/definitions/interlaceMode" }, + "colorspace": { "$ref": "urn:x-nmos-cpp:schemas:defs#/definitions/colorspace" }, + "transferCharacteristic": { "$ref": "urn:x-nmos-cpp:schemas:defs#/definitions/transferCharacteristic" }, + "colorSampling": { "$ref": "urn:x-nmos-cpp:schemas:defs#/definitions/colorSampling" }, + "portKind": { "type": "string", "enum": ["v", "a", "d", "m", "t", "b", "s", "c", "xv", "xa", "xd"] } + }, + "properties": { + "node_tags": { "$ref": "#/definitions/tags" }, + "device_tags": { "$ref": "#/definitions/tags" }, + + "how_many": { "$ref": "#/definitions/nonNegativeInteger" }, + "activate_senders": { "type": "boolean" }, + + "senders": { "type": "array", "items": { "$ref": "#/definitions/portKind" } }, + "receivers": { "type": "array", "items": { "$ref": "#/definitions/portKind" } }, + + "frame_rate": { "$ref": "#/definitions/rational" }, + "frame_width": { "$ref": "#/definitions/positiveInteger" }, + "frame_height": { "$ref": "#/definitions/positiveInteger" }, + + "interlace_mode": { "$ref": "#/definitions/interlaceMode" }, + + "colorspace": { "$ref": "#/definitions/colorspace" }, + "transfer_characteristic": { "$ref": "#/definitions/transferCharacteristic" }, + "color_sampling": { "$ref": "#/definitions/colorSampling" }, + "component_depth": { "$ref": "#/definitions/positiveInteger" }, + "video_type": { "type": "string", "pattern": "^video\\/[^\\s\\/]+$" }, + "mxl_video_type": { "type": "string", "enum": ["video/v210", "video/v210a"] }, + + "channel_count": { "$ref": "#/definitions/positiveInteger" }, + + "smpte2022_7": { "type": "boolean" }, + "simulate_status_monitor_activity": { "type": "boolean" }, + + "mxl_domain_id": { "$ref": "#/definitions/uuid" } + } +} + )-schema-"; + + const std::pair& node_settings_schema() + { + static const std::pair instance{ + web::uri{ U("urn:x-nmos-cpp:schemas:node-settings") }, + web::json::value::parse(node_settings_schema_text) + }; + return instance; + } + +} + +void validate_node_implementation_settings(const nmos::settings& settings) +{ + // delegate validation of the standard properties (see nmos/settings.h) + nmos::validate_node_settings(settings); + + // validate the example node-specific impl::fields::* properties + static const std::map known{ + impl::node_settings_schema(), + nmos::details::settings_definitions_schema() + }; + static const web::json::experimental::json_validator validator + { + [](const web::uri& wanted) { return known.at(wanted); }, + boost::copy_range>(known | boost::adaptors::map_keys) + }; + validator.validate(settings, impl::node_settings_schema().first); } // This constructs all the callbacks used to integrate the example device-specific underlying implementation diff --git a/Development/nmos-cpp-node/node_implementation.h b/Development/nmos-cpp-node/node_implementation.h index 3c6b295c3..f793b0e39 100644 --- a/Development/nmos-cpp-node/node_implementation.h +++ b/Development/nmos-cpp-node/node_implementation.h @@ -1,6 +1,8 @@ #ifndef NMOS_CPP_NODE_NODE_IMPLEMENTATION_H #define NMOS_CPP_NODE_NODE_IMPLEMENTATION_H +#include "nmos/settings.h" + namespace slog { class base_gate; @@ -17,6 +19,13 @@ namespace nmos } } +// Validates settings for the example node, including both the standard properties (via +// nmos::validate_node_settings) and the additional impl::fields::* settings defined in +// node_implementation.cpp. +// Throws web::json::json_exception, with a message including the offending key (and where +// appropriate the actual value), for incorrect types and out-of-range or unrecognised values. +void validate_node_implementation_settings(const nmos::settings& settings); + // This is an example of how to integrate the nmos-cpp library with a device-specific underlying implementation. // It constructs and inserts a node resource and some sub-resources into the model, based on the model settings, // starts background tasks to emit regular events from the temperature event source, and then waits for shutdown. diff --git a/Development/nmos-cpp-registry/main.cpp b/Development/nmos-cpp-registry/main.cpp index a98b253b7..d560d13a9 100644 --- a/Development/nmos-cpp-registry/main.cpp +++ b/Development/nmos-cpp-registry/main.cpp @@ -60,6 +60,12 @@ int main(int argc, char* argv[]) } } + // Validate the standard settings (before inserting run-time defaults, so that + // errors in user-provided settings are reported with the offending key rather + // than as a bare json_exception via the field accessors) + // (throws web::json::json_exception with the offending key in the message) + nmos::validate_registry_settings(registry_model.settings); + // Prepare run-time default settings (different than header defaults) nmos::insert_registry_default_settings(registry_model.settings); diff --git a/Development/nmos/connection_api.cpp b/Development/nmos/connection_api.cpp index eb63bcafb..5370757f2 100644 --- a/Development/nmos/connection_api.cpp +++ b/Development/nmos/connection_api.cpp @@ -86,6 +86,9 @@ namespace nmos }, { nmos::is05_versions::v1_1, { nmos::transports::websocket, nmos::transports::mqtt } + }, + { + nmos::is05_versions::v1_2, { nmos::transports::mxl } } }; @@ -128,22 +131,38 @@ namespace nmos staged_core_validator().validate(staged, experimental::make_connectionapi_staged_patch_request_schema_uri(version, type)); } - // Extend an existing schema with "auto" as a valid value - web::json::value make_auto_schema(const web::json::value& schema) + // Extend an existing schema with "auto" and/or null as valid values + web::json::value make_staged_param_schema(const web::json::value& schema, bool auto_value, bool null_value) { + using web::json::value; using web::json::value_of; + if (!auto_value && !null_value) return schema; + const bool keep_order = true; + auto any_of = value::array(); + + if (auto_value) + { + web::json::push_back(any_of, value_of({ + { U("type"), U("string") }, + { U("pattern"), U("^auto$") } + }, keep_order)); + } + + web::json::push_back(any_of, schema); + + if (null_value) + { + web::json::push_back(any_of, value_of({ + { U("type"), U("null") } + }, keep_order)); + } + return value_of({ - { U("anyOf"), value_of({ - value_of({ - { U("type"), U("string") }, - { U("pattern"), U("^auto$") } - }, keep_order), - schema - }) } - }); + { U("anyOf"), any_of } + }, keep_order); } static const std::map>& rtp_auto_constraints() @@ -240,16 +259,90 @@ namespace nmos return auto_constraints; } + static const std::map>& mxl_auto_constraints() + { + // These are the constraints that support "auto" in /staged + // BCP-007-03: MXL Receivers MUST NOT use "auto" for mxl_flow_id (domain only). + // See https://specs.amwa.tv/bcp-007-03/branches/publish-auto-null/docs/NMOS-With-MXL.html + static const std::map> auto_constraints + { + { + nmos::types::sender, + { + nmos::fields::mxl_domain_id, + nmos::fields::mxl_flow_id + } + }, + { + nmos::types::receiver, + { + nmos::fields::mxl_domain_id + } + } + }; + return auto_constraints; + } + + static const std::map>& mxl_null_constraints() + { + // These are the constraints that support null (unconfigured) in /staged + // See https://specs.amwa.tv/bcp-007-03/branches/publish-auto-null/docs/NMOS-With-MXL.html + // and sender_transport_params_mxl.json / receiver_transport_params_mxl.json + static const std::map> null_constraints + { + { + nmos::types::sender, + { + nmos::fields::mxl_domain_id, + nmos::fields::mxl_flow_id + } + }, + { + nmos::types::receiver, + { + nmos::fields::mxl_domain_id, + nmos::fields::mxl_flow_id + } + } + }; + return null_constraints; + } + static const std::map>& auto_constraints(const nmos::transport& transport_base) { if (nmos::transports::rtp == transport_base) return rtp_auto_constraints(); if (nmos::transports::websocket == transport_base) return websocket_auto_constraints(); if (nmos::transports::mqtt == transport_base) return mqtt_auto_constraints(); + if (nmos::transports::mxl == transport_base) return mxl_auto_constraints(); - static const std::map> no_auto_constraints; + static const std::map> no_auto_constraints + { + { + nmos::types::sender, {} + }, + { + nmos::types::receiver, {} + } + }; return no_auto_constraints; } + static const std::map>& null_constraints(const nmos::transport& transport_base) + { + if (nmos::transports::mxl == transport_base) return mxl_null_constraints(); + + static const std::map> no_null_constraints + { + { + nmos::types::sender, {} + }, + { + nmos::types::receiver, {} + } + }; + return no_null_constraints; + } + // Make a json schema from /constraints and /transporttype web::json::value make_constraints_schema(const nmos::type& type, const web::json::value& constraints, const nmos::transport& transport_base) { @@ -261,6 +354,7 @@ namespace nmos auto items = value::array(); auto& type_auto_constraints = auto_constraints(transport_base).at(type); + auto& type_null_constraints = null_constraints(transport_base).at(type); for (const auto& leg : constraints.as_array()) { @@ -268,14 +362,10 @@ namespace nmos for (const auto& constraint : leg.as_object()) { - if (type_auto_constraints.end() != type_auto_constraints.find(constraint.first)) - { - properties[constraint.first] = make_auto_schema(constraint.second); - } - else - { - properties[constraint.first] = constraint.second; - } + const bool auto_value = type_auto_constraints.end() != type_auto_constraints.find(constraint.first); + const bool null_value = type_null_constraints.end() != type_null_constraints.find(constraint.first); + + properties[constraint.first] = make_staged_param_schema(constraint.second, auto_value, null_value); } web::json::push_back(items, value_of({ diff --git a/Development/nmos/connection_resources.cpp b/Development/nmos/connection_resources.cpp index 79d81a7e9..55ddd4885 100644 --- a/Development/nmos/connection_resources.cpp +++ b/Development/nmos/connection_resources.cpp @@ -612,4 +612,108 @@ namespace nmos // See https://specs.amwa.tv/is-07/releases/v1.0.1/docs/5.1._Transport_-_MQTT.html#33-connection_status_broker_topic return U("x-nmos/events/") + make_api_version(version) + U("/connections/") + connection_id; } + + namespace details + { + web::json::value make_connection_mxl_sender_core_constraints(const nmos::id& mxl_domain_id, const nmos::id& mxl_flow_id) + { + using web::json::value; + using web::json::value_of; + + const auto unconstrained = value::object(); + return value_of({ + { nmos::fields::mxl_domain_id, mxl_domain_id.empty() ? unconstrained : value_of({ + { nmos::fields::constraint_enum, value_of({ + mxl_domain_id + }) } + }) }, + { nmos::fields::mxl_flow_id, mxl_flow_id.empty() ? unconstrained : value_of({ + { nmos::fields::constraint_enum, value_of({ + mxl_flow_id + }) } + }) } + }); + } + + web::json::value make_connection_mxl_sender_staged_core_parameter_set() + { + using web::json::value; + using web::json::value_of; + + return value_of({ + { nmos::fields::mxl_domain_id, U("auto") }, + { nmos::fields::mxl_flow_id, U("auto") } + }); + } + + web::json::value make_connection_mxl_receiver_core_constraints(const nmos::id& mxl_domain_id) + { + using web::json::value; + using web::json::value_of; + + const auto unconstrained = value::object(); + return value_of({ + { nmos::fields::mxl_domain_id, mxl_domain_id.empty() ? unconstrained : value_of({ + { nmos::fields::constraint_enum, value_of({ + mxl_domain_id + }) } + }) }, + { nmos::fields::mxl_flow_id, unconstrained } + }); + } + + web::json::value make_connection_mxl_receiver_staged_core_parameter_set() + { + using web::json::value; + using web::json::value_of; + + return value_of({ + { nmos::fields::mxl_domain_id, U("auto") }, + { nmos::fields::mxl_flow_id, value::null() } + }); + } + } + + nmos::resource make_connection_mxl_sender(const nmos::id& id, const nmos::id& mxl_domain_id, const nmos::id& mxl_flow_id) + { + using web::json::value; + using web::json::value_of; + + const auto redundant = false; + + auto data = details::make_connection_resource_core(id, redundant); + + data[nmos::fields::endpoint_constraints] = details::legs_of(details::make_connection_mxl_sender_core_constraints(mxl_domain_id, mxl_flow_id), redundant); + + data[nmos::fields::endpoint_staged][nmos::fields::receiver_id] = value::null(); + data[nmos::fields::endpoint_staged][nmos::fields::transport_params] = details::legs_of(details::make_connection_mxl_sender_staged_core_parameter_set(), redundant); + + data[nmos::fields::endpoint_active] = data[nmos::fields::endpoint_staged]; + // The caller must resolve all instances of "auto" in the /active endpoint into the actual values that will be used! + + // Note that the transporttype endpoint is implemented in terms of the matching IS-04 sender + + return{ is05_versions::v1_2, types::sender, std::move(data), false }; + } + + nmos::resource make_connection_mxl_receiver(const nmos::id& id, const nmos::id& mxl_domain_id) + { + using web::json::value; + + const auto redundant = false; + + auto data = details::make_connection_resource_core(id, redundant); + + data[nmos::fields::endpoint_constraints] = details::legs_of(details::make_connection_mxl_receiver_core_constraints(mxl_domain_id), redundant); + + data[nmos::fields::endpoint_staged][nmos::fields::sender_id] = value::null(); + data[nmos::fields::endpoint_staged][nmos::fields::transport_file] = details::make_connection_receiver_staging_transport_file(); + data[nmos::fields::endpoint_staged][nmos::fields::transport_params] = details::legs_of(details::make_connection_mxl_receiver_staged_core_parameter_set(), redundant); + + data[nmos::fields::endpoint_active] = data[nmos::fields::endpoint_staged]; + + // Note that the transporttype endpoint is implemented in terms of the matching IS-04 receiver + + return{ is05_versions::v1_2, types::receiver, std::move(data), false }; + } } diff --git a/Development/nmos/connection_resources.h b/Development/nmos/connection_resources.h index adb035d34..2f757a728 100644 --- a/Development/nmos/connection_resources.h +++ b/Development/nmos/connection_resources.h @@ -60,6 +60,9 @@ namespace nmos utility::string_t make_events_mqtt_broker_topic(const nmos::id& source_id, const nmos::settings& settings); utility::string_t make_events_mqtt_connection_status_broker_topic(const nmos::id& connection_id, const nmos::settings& settings); + + nmos::resource make_connection_mxl_sender(const nmos::id& id, const nmos::id& mxl_domain_id, const nmos::id& mxl_flow_id); + nmos::resource make_connection_mxl_receiver(const nmos::id& id, const nmos::id& mxl_domain_id); } #endif diff --git a/Development/nmos/is05_schemas/is05_schemas.h b/Development/nmos/is05_schemas/is05_schemas.h index 4550dbe77..7b2fecee2 100644 --- a/Development/nmos/is05_schemas/is05_schemas.h +++ b/Development/nmos/is05_schemas/is05_schemas.h @@ -7,6 +7,28 @@ namespace nmos { namespace is05_schemas { + namespace v1_2_dev + { + extern const char* activation_schema; + extern const char* sender_stage_schema; + extern const char* sender_transport_params; + extern const char* sender_transport_params_rtp; + extern const char* sender_transport_params_dash; + extern const char* sender_transport_params_websocket; + extern const char* sender_transport_params_mqtt; + extern const char* sender_transport_params_mxl; + extern const char* sender_transport_params_ext; + extern const char* receiver_stage_schema; + extern const char* receiver_transport_file; + extern const char* receiver_transport_params; + extern const char* receiver_transport_params_rtp; + extern const char* receiver_transport_params_dash; + extern const char* receiver_transport_params_websocket; + extern const char* receiver_transport_params_mqtt; + extern const char* receiver_transport_params_mxl; + extern const char* receiver_transport_params_ext; + } + namespace v1_1_x { extern const char* activation_schema; diff --git a/Development/nmos/is05_versions.h b/Development/nmos/is05_versions.h index dfb39a527..4ed870ce9 100644 --- a/Development/nmos/is05_versions.h +++ b/Development/nmos/is05_versions.h @@ -12,8 +12,9 @@ namespace nmos { const api_version v1_0{ 1, 0 }; const api_version v1_1{ 1, 1 }; + const api_version v1_2{ 1, 2 }; - const std::set all{ nmos::is05_versions::v1_0, nmos::is05_versions::v1_1 }; + const std::set all{ nmos::is05_versions::v1_0, nmos::is05_versions::v1_1, nmos::is05_versions::v1_2 }; inline std::set from_settings(const nmos::settings& settings) { diff --git a/Development/nmos/json_fields.h b/Development/nmos/json_fields.h index 0ff9921db..118dc0380 100644 --- a/Development/nmos/json_fields.h +++ b/Development/nmos/json_fields.h @@ -172,6 +172,9 @@ namespace nmos const web::json::field_as_value_or broker_authorization{ U("broker_authorization"), {} }; // string or bool const web::json::field_as_value_or broker_topic{ U("broker_topic"), {} }; // string or null const web::json::field_as_value_or connection_status_broker_topic{ U("connection_status_broker_topic"), {} }; // string or null + // for urn:x-nmos:transport:mxl (see AMWA BCP-007-03 NMOS With MXL) + const web::json::field_as_value_or mxl_domain_id{ U("mxl_domain_id"), {} }; // UUID string, auto, or null + const web::json::field_as_value_or mxl_flow_id{ U("mxl_flow_id"), {} }; // senders: UUID, auto, or null; receivers: UUID or null // IS-07 Event & Tally diff --git a/Development/nmos/json_schema.cpp b/Development/nmos/json_schema.cpp index 3860509ca..ab021e059 100644 --- a/Development/nmos/json_schema.cpp +++ b/Development/nmos/json_schema.cpp @@ -77,6 +77,16 @@ namespace nmos return{ _XPLATSTR("https://github.com/AMWA-TV/is-05/raw/") + tag + _XPLATSTR("/APIs/schemas/") + ref }; } + // See https://github.com/AMWA-TV/is-05/blob/v1.2-dev/APIs/schemas/ + namespace v1_2 + { + using namespace nmos::is05_schemas::v1_2_dev; + const utility::string_t tag(_XPLATSTR("v1.2-dev")); + + const web::uri connectionapi_sender_staged_patch_request_uri = make_schema_uri(tag, _XPLATSTR("sender-stage-schema.json")); + const web::uri connectionapi_receiver_staged_patch_request_uri = make_schema_uri(tag, _XPLATSTR("receiver-stage-schema.json")); + } + // See https://github.com/AMWA-TV/is-05/blob/v1.1.x/APIs/schemas/ namespace v1_1 { @@ -323,6 +333,25 @@ namespace nmos return { + // v1.2 + { make_schema_uri(v1_2::tag, _XPLATSTR("sender-stage-schema.json")), make_schema(v1_2::sender_stage_schema) }, + { make_schema_uri(v1_2::tag, _XPLATSTR("receiver-stage-schema.json")), make_schema(v1_2::receiver_stage_schema) }, + { make_schema_uri(v1_2::tag, _XPLATSTR("receiver-transport-file.json")), make_schema(v1_2::receiver_transport_file) }, + { make_schema_uri(v1_2::tag, _XPLATSTR("activation-schema.json")), make_schema(v1_2::activation_schema) }, + { make_schema_uri(v1_2::tag, _XPLATSTR("sender_transport_params.json")), make_schema(v1_2::sender_transport_params) }, + { make_schema_uri(v1_2::tag, _XPLATSTR("sender_transport_params_rtp.json")), make_schema(v1_2::sender_transport_params_rtp) }, + { make_schema_uri(v1_2::tag, _XPLATSTR("sender_transport_params_dash.json")), make_schema(v1_2::sender_transport_params_dash) }, + { make_schema_uri(v1_2::tag, _XPLATSTR("sender_transport_params_websocket.json")), make_schema(v1_2::sender_transport_params_websocket) }, + { make_schema_uri(v1_2::tag, _XPLATSTR("sender_transport_params_mqtt.json")), make_schema(v1_2::sender_transport_params_mqtt) }, + { make_schema_uri(v1_2::tag, _XPLATSTR("sender_transport_params_mxl.json")), make_schema(v1_2::sender_transport_params_mxl) }, + { make_schema_uri(v1_2::tag, _XPLATSTR("sender_transport_params_ext.json")), make_schema(v1_2::sender_transport_params_ext) }, + { make_schema_uri(v1_2::tag, _XPLATSTR("receiver_transport_params.json")), make_schema(v1_2::receiver_transport_params) }, + { make_schema_uri(v1_2::tag, _XPLATSTR("receiver_transport_params_rtp.json")), make_schema(v1_2::receiver_transport_params_rtp) }, + { make_schema_uri(v1_2::tag, _XPLATSTR("receiver_transport_params_dash.json")), make_schema(v1_2::receiver_transport_params_dash) }, + { make_schema_uri(v1_2::tag, _XPLATSTR("receiver_transport_params_websocket.json")), make_schema(v1_2::receiver_transport_params_websocket) }, + { make_schema_uri(v1_2::tag, _XPLATSTR("receiver_transport_params_mqtt.json")), make_schema(v1_2::receiver_transport_params_mqtt) }, + { make_schema_uri(v1_2::tag, _XPLATSTR("receiver_transport_params_mxl.json")), make_schema(v1_2::receiver_transport_params_mxl) }, + { make_schema_uri(v1_2::tag, _XPLATSTR("receiver_transport_params_ext.json")), make_schema(v1_2::receiver_transport_params_ext) }, // v1.1 { make_schema_uri(v1_1::tag, _XPLATSTR("sender-stage-schema.json")), make_schema(v1_1::sender_stage_schema) }, { make_schema_uri(v1_1::tag, _XPLATSTR("receiver-stage-schema.json")), make_schema(v1_1::receiver_stage_schema) }, @@ -487,12 +516,14 @@ namespace nmos web::uri make_connectionapi_sender_staged_patch_request_schema_uri(const nmos::api_version& version) { + if (is05_versions::v1_2 <= version) return is05_schemas::v1_2::connectionapi_sender_staged_patch_request_uri; if (is05_versions::v1_1 <= version) return is05_schemas::v1_1::connectionapi_sender_staged_patch_request_uri; return is05_schemas::v1_0::connectionapi_sender_staged_patch_request_uri; } web::uri make_connectionapi_receiver_staged_patch_request_schema_uri(const nmos::api_version& version) { + if (is05_versions::v1_2 <= version) return is05_schemas::v1_2::connectionapi_receiver_staged_patch_request_uri; if (is05_versions::v1_1 <= version) return is05_schemas::v1_1::connectionapi_receiver_staged_patch_request_uri; return is05_schemas::v1_0::connectionapi_receiver_staged_patch_request_uri; } diff --git a/Development/nmos/mxl.h b/Development/nmos/mxl.h new file mode 100644 index 000000000..22625c69f --- /dev/null +++ b/Development/nmos/mxl.h @@ -0,0 +1,25 @@ +#ifndef NMOS_MXL_H +#define NMOS_MXL_H + +#include "nmos/media_type.h" + +namespace nmos +{ + namespace media_types + { + // MXL video media types + + const media_type video_v210{ U("video/v210") }; + const media_type video_v210a{ U("video/v210a") }; + + // MXL audio media types + + const media_type audio_float32{ U("audio/float32") }; + + // MXL data media types + + //const media_type video_smpte291{ U("video/smpte291") }; + } +} + +#endif \ No newline at end of file diff --git a/Development/nmos/node_resources.cpp b/Development/nmos/node_resources.cpp index 6e8a1cba1..464d628c0 100644 --- a/Development/nmos/node_resources.cpp +++ b/Development/nmos/node_resources.cpp @@ -397,13 +397,19 @@ namespace nmos // See https://specs.amwa.tv/is-04/releases/v1.2.0/APIs/schemas/with-refs/flow_audio_raw.html nmos::resource make_raw_audio_flow(const nmos::id& id, const nmos::id& source_id, const nmos::id& device_id, const nmos::rational& sample_rate, unsigned int bit_depth, const nmos::settings& settings) + { + return make_raw_audio_flow(id, source_id, device_id, sample_rate, nmos::media_types::audio_L(bit_depth), bit_depth, settings); + } + + // See https://specs.amwa.tv/is-04/releases/v1.2.0/APIs/schemas/with-refs/flow_audio_raw.html + nmos::resource make_raw_audio_flow(const nmos::id& id, const nmos::id& source_id, const nmos::id& device_id, const nmos::rational& sample_rate, const nmos::media_type& media_type, unsigned int bit_depth, const nmos::settings& settings) { using web::json::value; auto resource = make_audio_flow(id, source_id, device_id, sample_rate, settings); auto& data = resource.data; - data[U("media_type")] = value::string(nmos::media_types::audio_L(bit_depth).name); + data[U("media_type")] = value::string(media_type.name); data[U("bit_depth")] = bit_depth; return resource; diff --git a/Development/nmos/node_resources.h b/Development/nmos/node_resources.h index a1b9f6555..373b4666d 100644 --- a/Development/nmos/node_resources.h +++ b/Development/nmos/node_resources.h @@ -84,6 +84,7 @@ namespace nmos // See https://specs.amwa.tv/is-04/releases/v1.2.0/APIs/schemas/with-refs/flow_audio_raw.html nmos::resource make_raw_audio_flow(const nmos::id& id, const nmos::id& source_id, const nmos::id& device_id, const nmos::rational& sample_rate, unsigned int bit_depth, const nmos::settings& settings); + nmos::resource make_raw_audio_flow(const nmos::id& id, const nmos::id& source_id, const nmos::id& device_id, const nmos::rational& sample_rate, const nmos::media_type& media_type, unsigned int bit_depth, const nmos::settings& settings); nmos::resource make_raw_audio_flow(const nmos::id& id, const nmos::id& source_id, const nmos::id& device_id, const nmos::settings& settings); // See https://specs.amwa.tv/is-04/releases/v1.2.0/APIs/schemas/with-refs/flow_audio_coded.html diff --git a/Development/nmos/settings.cpp b/Development/nmos/settings.cpp index 94251cb4d..80cd97590 100644 --- a/Development/nmos/settings.cpp +++ b/Development/nmos/settings.cpp @@ -1,17 +1,322 @@ #include "nmos/settings.h" +#include #include +#include #include #include #include #include "cpprest/host_utils.h" #include "cpprest/http_utils.h" +#include "cpprest/json_validator.h" #include "cpprest/version.h" #include "nmos/id.h" #include "websocketpp/version.hpp" namespace nmos { + namespace details + { + // Useful value-type definitions for composing application-specific settings + // schemas; see settings_definitions_schema(). + static const char* settings_definitions_schema_text = R"-schema-( +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "definitions": { + "nonNegativeInteger": { "type": "integer", "minimum": 0 }, + "positiveInteger": { "type": "integer", "minimum": 1 }, + "rational": { + "title": "nmos::rational", + "type": "object", + "required": ["numerator"], + "properties": { + "numerator": { "type": "integer" }, + "denominator": { "type": "integer", "not": { "enum": [0] } } + } + }, + "stringArray": { "type": "array", "items": { "type": "string" } }, + "tags": { + "type": "object", + "additionalProperties": { "$ref": "#/definitions/stringArray" } + }, + "uuid": { + "title": "nmos::id", + "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}$" + }, + "interlaceMode": { + "title": "nmos::interlace_mode", + "type": "string", + "enum": ["progressive", "interlaced_tff", "interlaced_bff", "interlaced_psf"] + }, + "colorspace": { + "title": "nmos::colorspace", + "type": "string", + "enum": ["BT601", "BT709", "BT2020", "BT2100", "ST2065-1", "ST2065-3", "XYZ"] + }, + "transferCharacteristic": { + "title": "nmos::transfer_characteristic", + "type": "string", + "enum": ["SDR", "PQ", "HLG", "LINEAR", "BT2100LINPQ", "BT2100LINHLG", "ST2065-1", "ST428-1", "DENSITY"] + }, + "colorSampling": { + "title": "sdp::sampling", + "type": "string", + "enum": ["RGBA", "RGB", "YCbCr-4:4:4", "YCbCr-4:2:2", "YCbCr-4:2:0", "YCbCr-4:1:1", "CLYCbCr-4:4:4", "CLYCbCr-4:2:2", "CLYCbCr-4:2:0", "ICtCp-4:4:4", "ICtCp-4:2:2", "ICtCp-4:2:0", "XYZ", "KEY", "UNSPECIFIED"] + } + } +} + )-schema-"; + + const std::pair& settings_definitions_schema() + { + static const std::pair instance{ + web::uri{ U("urn:x-nmos-cpp:schemas:defs") }, + web::json::value::parse(settings_definitions_schema_text) + }; + return instance; + } + + // JSON schema covering the known properties in nmos/settings.h and nmos/certificate_settings.h + // (used by both validate_node_settings and validate_registry_settings; additionalProperties is + // not set to false, so fields not enumerated here pass through unchecked which is what we want + // both for forward compatibility and to allow downstream validators to layer their own checks) + // definitions: aliases (in settings_definitions_schema() source order) first, then local; + // properties: in the same order as in the headers; please keep them in sync + static const char* settings_schema_text = R"-schema-( +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "definitions": { + "nonNegativeInteger": { "$ref": "urn:x-nmos-cpp:schemas:defs#/definitions/nonNegativeInteger" }, + "positiveInteger": { "$ref": "urn:x-nmos-cpp:schemas:defs#/definitions/positiveInteger" }, + "stringArray": { "$ref": "urn:x-nmos-cpp:schemas:defs#/definitions/stringArray" }, + "tags": { "$ref": "urn:x-nmos-cpp:schemas:defs#/definitions/tags" }, + "uuid": { "$ref": "urn:x-nmos-cpp:schemas:defs#/definitions/uuid" }, + "apiVersion": { "type": "string", "pattern": "^v[0-9]+\\.[0-9]+$" }, + "apiVersionArray": { "type": "array", "items": { "$ref": "#/definitions/apiVersion" } }, + "port": { "type": "integer", "minimum": -1, "maximum": 65535 } + }, + "properties": { + "error_log": { "type": "string" }, + "access_log": { "type": "string" }, + "logging_level": { "type": "integer" }, + "logging_categories": { "$ref": "#/definitions/stringArray" }, + + "host_name": { "type": "string" }, + "domain": { "type": "string" }, + "dns_sd_browse_mode": { "type": "integer", "enum": [0, 1, 2] }, + "host_address": { "type": "string" }, + "host_addresses": { "$ref": "#/definitions/stringArray" }, + + "is04_versions": { "$ref": "#/definitions/apiVersionArray" }, + "is05_versions": { "$ref": "#/definitions/apiVersionArray" }, + "is07_versions": { "$ref": "#/definitions/apiVersionArray" }, + "is08_versions": { "$ref": "#/definitions/apiVersionArray" }, + "is09_versions": { "$ref": "#/definitions/apiVersionArray" }, + "is10_versions": { "$ref": "#/definitions/apiVersionArray" }, + "is12_versions": { "$ref": "#/definitions/apiVersionArray" }, + "is14_versions": { "$ref": "#/definitions/apiVersionArray" }, + + "pri": { "$ref": "#/definitions/nonNegativeInteger" }, + "highest_pri": { "$ref": "#/definitions/nonNegativeInteger" }, + "lowest_pri": { "$ref": "#/definitions/nonNegativeInteger" }, + "authorization_highest_pri": { "$ref": "#/definitions/nonNegativeInteger" }, + "authorization_lowest_pri": { "$ref": "#/definitions/nonNegativeInteger" }, + + "discovery_backoff_min": { "$ref": "#/definitions/nonNegativeInteger" }, + "discovery_backoff_max": { "$ref": "#/definitions/nonNegativeInteger" }, + "discovery_backoff_factor": { "type": "number", "minimum": 1 }, + + "service_name_prefix": { "type": "string" }, + "registry_address": { "type": "string" }, + "registry_version": { "$ref": "#/definitions/apiVersion" }, + + "http_port": { "$ref": "#/definitions/port" }, + "query_port": { "$ref": "#/definitions/port" }, + "query_ws_port": { "$ref": "#/definitions/port" }, + "registration_port": { "$ref": "#/definitions/port" }, + "node_port": { "$ref": "#/definitions/port" }, + "connection_port": { "$ref": "#/definitions/port" }, + "events_port": { "$ref": "#/definitions/port" }, + "events_ws_port": { "$ref": "#/definitions/port" }, + "channelmapping_port": { "$ref": "#/definitions/port" }, + "system_port": { "$ref": "#/definitions/port" }, + "control_protocol_ws_port": { "$ref": "#/definitions/port" }, + "configuration_port": { "$ref": "#/definitions/port" }, + + "listen_backlog": { "$ref": "#/definitions/nonNegativeInteger" }, + "registration_heartbeat_interval": { "$ref": "#/definitions/positiveInteger" }, + "registration_expiry_interval": { "$ref": "#/definitions/positiveInteger" }, + "registration_request_max": { "$ref": "#/definitions/positiveInteger" }, + "registration_heartbeat_max": { "$ref": "#/definitions/positiveInteger" }, + + "query_paging_default": { "$ref": "#/definitions/positiveInteger" }, + "query_paging_limit": { "$ref": "#/definitions/positiveInteger" }, + + "ptp_announce_receipt_timeout": { "type": "integer", "minimum": 2, "maximum": 10 }, + "ptp_domain_number": { "type": "integer", "minimum": 0, "maximum": 127 }, + + "immediate_activation_max": { "$ref": "#/definitions/positiveInteger" }, + "events_heartbeat_interval": { "$ref": "#/definitions/positiveInteger" }, + "events_expiry_interval": { "$ref": "#/definitions/positiveInteger" }, + + "system_address": { "type": "string" }, + "system_version": { "$ref": "#/definitions/apiVersion" }, + "system_request_max": { "$ref": "#/definitions/positiveInteger" }, + + "seed_id": { "$ref": "#/definitions/uuid" }, + "label": { "type": "string" }, + "description": { "type": "string" }, + "registration_available": { "type": "boolean" }, + "allow_invalid_resources": { "type": "boolean" }, + + "manifest_port": { "$ref": "#/definitions/port" }, + "settings_port": { "$ref": "#/definitions/port" }, + "logging_port": { "$ref": "#/definitions/port" }, + "admin_port": { "$ref": "#/definitions/port" }, + "mdns_port": { "$ref": "#/definitions/port" }, + "schemas_port": { "$ref": "#/definitions/port" }, + + "server_address": { "type": "string" }, + "settings_address": { "type": "string" }, + "logging_address": { "type": "string" }, + "admin_address": { "type": "string" }, + "mdns_address": { "type": "string" }, + "schemas_address": { "type": "string" }, + "client_address": { "type": "string" }, + + "query_ws_paging_default": { "$ref": "#/definitions/positiveInteger" }, + "query_ws_paging_limit": { "$ref": "#/definitions/positiveInteger" }, + "logging_limit": { "$ref": "#/definitions/positiveInteger" }, + "logging_paging_default": { "$ref": "#/definitions/positiveInteger" }, + "logging_paging_limit": { "$ref": "#/definitions/positiveInteger" }, + + "http_trace": { "type": "boolean" }, + + "proxy_map": { + "type": "array", + "items": { + "type": "object", + "required": ["client_port", "server_port"], + "properties": { + "client_port": { "$ref": "#/definitions/port" }, + "server_port": { "$ref": "#/definitions/port" } + } + } + }, + "proxy_address": { "type": "string" }, + "proxy_port": { "$ref": "#/definitions/port" }, + + "href_mode": { "type": "integer", "enum": [0, 1, 2, 3] }, + + "client_secure": { "type": "boolean" }, + "server_secure": { "type": "boolean" }, + "validate_certificates": { "type": "boolean" }, + + "system_interval_min": { "$ref": "#/definitions/positiveInteger" }, + "system_interval_max": { "$ref": "#/definitions/positiveInteger" }, + + "system_label": { "type": "string" }, + "system_description": { "type": "string" }, + "system_tags": { "$ref": "#/definitions/tags" }, + + "system_syslog_host_name": { "type": "string" }, + "system_syslog_port": { "$ref": "#/definitions/port" }, + "system_syslogv2_host_name": { "type": "string" }, + "system_syslogv2_port": { "$ref": "#/definitions/port" }, + + "hsts_max_age": { "type": "integer" }, + "hsts_include_sub_domains": { "type": "boolean" }, + + "ocsp_interval_min": { "$ref": "#/definitions/positiveInteger" }, + "ocsp_interval_max": { "$ref": "#/definitions/positiveInteger" }, + "ocsp_request_max": { "$ref": "#/definitions/positiveInteger" }, + + "authorization_selector": { "type": "string" }, + "authorization_address": { "type": "string" }, + "authorization_port": { "$ref": "#/definitions/port" }, + "authorization_version": { "$ref": "#/definitions/apiVersion" }, + "authorization_request_max": { "$ref": "#/definitions/positiveInteger" }, + "fetch_authorization_public_keys_interval_min": { "$ref": "#/definitions/positiveInteger" }, + "fetch_authorization_public_keys_interval_max": { "$ref": "#/definitions/positiveInteger" }, + "access_token_refresh_interval": { "type": "integer", "minimum": -1 }, + "client_authorization": { "type": "boolean" }, + "server_authorization": { "type": "boolean" }, + "authorization_code_flow_max": { "type": "integer", "minimum": -1 }, + "authorization_flow": { "type": "string", "enum": ["authorization_code", "client_credentials"] }, + "authorization_redirect_port": { "$ref": "#/definitions/port" }, + "initial_access_token": { "type": "string" }, + "authorization_scopes": { "$ref": "#/definitions/stringArray" }, + "token_endpoint_auth_method": { + "type": "string", + "enum": ["none", "client_secret_basic", "client_secret_post", "private_key_jwt", "client_secret_jwt"] + }, + "jwks_uri_port": { "$ref": "#/definitions/port" }, + "validate_openid_client": { "type": "boolean" }, + "no_trailing_dot_for_authorization_callback_uri": { "type": "boolean" }, + "service_unavailable_retry_after": { "$ref": "#/definitions/nonNegativeInteger" }, + + "manufacturer_name": { "type": "string" }, + "product_name": { "type": "string" }, + "product_key": { "type": "string" }, + "product_revision_level": { "type": "string" }, + "serial_number": { "type": "string" }, + + "ca_certificate_file": { "type": "string" }, + "server_certificates": { + "type": "array", + "items": { + "type": "object", + "required": ["private_key_file", "certificate_chain_file"], + "properties": { + "key_algorithm": { "type": "string", "enum": ["ECDSA", "RSA"] }, + "private_key_file": { "type": "string" }, + "certificate_chain_file": { "type": "string" } + } + } + }, + "dh_param_file": { "type": "string" }, + "private_key_files": { "$ref": "#/definitions/stringArray" }, + "certificate_chain_files": { "$ref": "#/definitions/stringArray" } + } +} + )-schema-"; + + const std::pair& settings_schema() + { + static const std::pair instance{ + web::uri{ U("urn:x-nmos-cpp:schemas:settings") }, + web::json::value::parse(settings_schema_text) + }; + return instance; + } + + void validate_settings(const settings& settings) + { + static const std::map known{ + settings_schema(), + settings_definitions_schema() + }; + static const web::json::experimental::json_validator validator + { + [](const web::uri& wanted) { return known.at(wanted); }, + boost::copy_range>(known | boost::adaptors::map_keys) + }; + validator.validate(settings, settings_schema().first); + } + } + + void validate_node_settings(const settings& settings) + { + details::validate_settings(settings); + } + + void validate_registry_settings(const settings& settings) + { + details::validate_settings(settings); + } + namespace details { // Get default DNS domain name diff --git a/Development/nmos/settings.h b/Development/nmos/settings.h index 02549d927..008461129 100644 --- a/Development/nmos/settings.h +++ b/Development/nmos/settings.h @@ -1,7 +1,9 @@ #ifndef NMOS_SETTINGS_H #define NMOS_SETTINGS_H +#include #include "bst/optional.h" +#include "cpprest/base_uri.h" #include "cpprest/json_utils.h" namespace web @@ -27,6 +29,32 @@ namespace nmos { typedef web::json::value settings; + // Validates the known properties (declared in nmos/settings.h and nmos/certificate_settings.h) + // against an embedded JSON schema. Unknown properties are silently ignored. Throws + // web::json::json_exception on failure. + void validate_node_settings(const settings& settings); + void validate_registry_settings(const settings& settings); + + namespace details + { + // The library's settings schema (used by validate_node_settings and + // validate_registry_settings). Exposed in the same form as + // settings_definitions_schema() so downstream code wanting to compose + // its own validator from the library's schemas can register both without + // re-parsing the schema text at every validator construction. + const std::pair& settings_schema(); + + // Useful value-type definitions (positiveInteger, nonNegativeInteger, + // stringArray, uuid, tags, rational, interlaceMode, colorspace, + // transferCharacteristic, colorSampling) for application code that wants + // to compose its own settings JSON schema via cross-schema $refs of the form + // { "$ref": "urn:x-nmos-cpp:schemas:defs#/definitions/" } + // The first member is the URI under which the fragment is registered; the second + // is the parsed schema. Pass `.first` to the json_validator's known-schemas list + // and return `.second` from the loader callback when it is invoked with that URI. + const std::pair& settings_definitions_schema(); + } + // Inserts run-time default settings for those which are impossible to determine at compile-time // if not already present in the specified settings void insert_node_default_settings(settings& settings); diff --git a/Development/nmos/test/settings_test.cpp b/Development/nmos/test/settings_test.cpp new file mode 100644 index 000000000..c621e46a1 --- /dev/null +++ b/Development/nmos/test/settings_test.cpp @@ -0,0 +1,283 @@ +// The first "test" is of course whether the header compiles standalone +#include "nmos/settings.h" + +#include +#include "bst/test/test.h" +#include "cpprest/json_utils.h" + +//////////////////////////////////////////////////////////////////////////////////////////// +// validate_node_settings/validate_registry_settings type and range checks +//////////////////////////////////////////////////////////////////////////////////////////// + +BST_TEST_CASE(testValidateNodeSettingsEmpty) +{ + // an empty settings object should be valid - defaults are used for everything + BST_CHECK_NO_THROW(nmos::validate_node_settings(web::json::value::object())); +} + +BST_TEST_CASE(testValidateRegistrySettingsEmpty) +{ + BST_CHECK_NO_THROW(nmos::validate_registry_settings(web::json::value::object())); +} + +BST_TEST_CASE(testValidateNodeSettingsAfterDefaults) +{ + // the run-time defaults shouldn't introduce any invalid values + nmos::settings settings; + nmos::insert_node_default_settings(settings); + BST_CHECK_NO_THROW(nmos::validate_node_settings(settings)); +} + +BST_TEST_CASE(testValidateRegistrySettingsAfterDefaults) +{ + nmos::settings settings; + nmos::insert_registry_default_settings(settings); + BST_CHECK_NO_THROW(nmos::validate_registry_settings(settings)); +} + +BST_TEST_CASE(testValidateNodeSettingsWrongType) +{ + using web::json::value_of; + + // all validation errors are reported as web::json::json_exception and the + // schema validator includes the JSON pointer (e.g. /http_port) in the message + { + const auto bad = value_of({ { U("http_port"), U("3210") } }); // should be an int + try + { + nmos::validate_node_settings(bad); + BST_CHECK(false); // expected json_exception + } + catch (const web::json::json_exception& e) + { + BST_CHECK(std::strstr(e.what(), "/http_port") != nullptr); + } + } + { + const auto bad = value_of({ { U("host_addresses"), U("127.0.0.1") } }); // should be an array + BST_CHECK_THROW(nmos::validate_node_settings(bad), web::json::json_exception); + } + { + const auto bad = value_of({ { U("http_trace"), U("true") } }); // should be a bool + BST_CHECK_THROW(nmos::validate_node_settings(bad), web::json::json_exception); + } +} + +BST_TEST_CASE(testValidateNodeSettingsRangeErrors) +{ + using web::json::value_of; + + { + const auto bad = value_of({ { U("http_port"), 65536 } }); + try + { + nmos::validate_node_settings(bad); + BST_CHECK(false); + } + catch (const web::json::json_exception& e) + { + BST_CHECK(std::strstr(e.what(), "/http_port") != nullptr); + BST_CHECK(std::strstr(e.what(), "65536") != nullptr); + } + } + { + // -1 is OK for any port (disables the corresponding feature); -2 is not + const auto ok = value_of({ { U("http_port"), -1 } }); + BST_CHECK_NO_THROW(nmos::validate_node_settings(ok)); + const auto bad = value_of({ { U("http_port"), -2 } }); + BST_CHECK_THROW(nmos::validate_node_settings(bad), web::json::json_exception); + } + { + // logging_level is unconstrained beyond being an integer (named slog severities + // are in [-40, 40] but the sentinels never_log_severity/reset_log_severity sit + // at INT_MAX/INT_MIN) + const auto ok = value_of({ { U("logging_level"), 41 } }); + BST_CHECK_NO_THROW(nmos::validate_node_settings(ok)); + } + { + const auto bad = value_of({ { U("dns_sd_browse_mode"), 3 } }); + BST_CHECK_THROW(nmos::validate_node_settings(bad), web::json::json_exception); + } + { + const auto bad = value_of({ { U("discovery_backoff_factor"), 0.5 } }); + BST_CHECK_THROW(nmos::validate_node_settings(bad), web::json::json_exception); + } +} + +BST_TEST_CASE(testValidateRegistrySettingsRangeErrors) +{ + using web::json::value_of; + + { + const auto bad = value_of({ { U("ptp_announce_receipt_timeout"), 1 } }); // valid range is 2-10 + BST_CHECK_THROW(nmos::validate_registry_settings(bad), web::json::json_exception); + } + { + const auto bad = value_of({ { U("ptp_domain_number"), 128 } }); // valid range is 0-127 + BST_CHECK_THROW(nmos::validate_registry_settings(bad), web::json::json_exception); + } + { + const auto ok = value_of({ { U("ptp_domain_number"), 0 } }); + BST_CHECK_NO_THROW(nmos::validate_registry_settings(ok)); + } +} + +BST_TEST_CASE(testValidateNodeSettingsVersions) +{ + using web::json::value_of; + + { + const auto ok = value_of({ + { U("is04_versions"), value_of({ U("v1.2"), U("v1.3") }) }, + { U("is05_versions"), value_of({ U("v1.0"), U("v1.1") }) } + }); + BST_CHECK_NO_THROW(nmos::validate_node_settings(ok)); + } + { + // any well-formed v. string is acceptable to the schema; + // whether the library actually implements it is checked at point of use + const auto ok = value_of({ { U("is04_versions"), value_of({ U("v1.99") }) } }); + BST_CHECK_NO_THROW(nmos::validate_node_settings(ok)); + } + { + const auto bad = value_of({ { U("is04_versions"), value_of({ U("not-a-version") }) } }); + BST_CHECK_THROW(nmos::validate_node_settings(bad), web::json::json_exception); + } + { + const auto bad = value_of({ { U("registry_version"), U("1.0") } }); // missing the "v" + BST_CHECK_THROW(nmos::validate_node_settings(bad), web::json::json_exception); + } + { + const auto bad = value_of({ { U("registry_version"), U("v1") } }); // missing the minor + BST_CHECK_THROW(nmos::validate_node_settings(bad), web::json::json_exception); + } + { + const auto bad = value_of({ { U("registry_version"), U("v1.2.3") } }); // too many parts + BST_CHECK_THROW(nmos::validate_node_settings(bad), web::json::json_exception); + } +} + +BST_TEST_CASE(testValidateNodeSettingsEnumStrings) +{ + using web::json::value_of; + + { + const auto ok = value_of({ { U("authorization_flow"), U("client_credentials") } }); + BST_CHECK_NO_THROW(nmos::validate_node_settings(ok)); + } + { + const auto bad = value_of({ { U("authorization_flow"), U("password") } }); + BST_CHECK_THROW(nmos::validate_node_settings(bad), web::json::json_exception); + } + { + const auto ok = value_of({ { U("authorization_scopes"), value_of({ U("registration"), U("node"), U("connection") }) } }); + BST_CHECK_NO_THROW(nmos::validate_node_settings(ok)); + } + { + // authorization scope strings aren't validated against an enum (see nmos/scope.h); + // adding a new NMOS API just adds a new scope and the schema shouldn't need updating + const auto ok = value_of({ { U("authorization_scopes"), value_of({ U("some-future-scope") }) } }); + BST_CHECK_NO_THROW(nmos::validate_node_settings(ok)); + } + { + const auto bad = value_of({ { U("authorization_scopes"), value_of({ 42 }) } }); // must still be strings + BST_CHECK_THROW(nmos::validate_node_settings(bad), web::json::json_exception); + } + { + const auto ok = value_of({ { U("token_endpoint_auth_method"), U("private_key_jwt") } }); + BST_CHECK_NO_THROW(nmos::validate_node_settings(ok)); + } + { + const auto bad = value_of({ { U("token_endpoint_auth_method"), U("not-a-method") } }); + BST_CHECK_THROW(nmos::validate_node_settings(bad), web::json::json_exception); + } +} + +BST_TEST_CASE(testValidateNodeSettingsSeedId) +{ + using web::json::value_of; + + { + // a well-formed UUID is accepted + const auto ok = value_of({ { U("seed_id"), U("12345678-1234-1234-89ab-1234567890ab") } }); + BST_CHECK_NO_THROW(nmos::validate_node_settings(ok)); + } + { + // an arbitrary string is rejected (seed_id is used as a v5 UUID namespace) + const auto bad = value_of({ { U("seed_id"), U("not-a-uuid") } }); + BST_CHECK_THROW(nmos::validate_node_settings(bad), web::json::json_exception); + } + { + // upper-case hex is rejected (NMOS UUIDs are lower-case) + const auto bad = value_of({ { U("seed_id"), U("12345678-1234-1234-89AB-1234567890ab") } }); + BST_CHECK_THROW(nmos::validate_node_settings(bad), web::json::json_exception); + } +} + +BST_TEST_CASE(testValidateServerCertificates) +{ + using web::json::value_of; + + { + const auto ok = value_of({ + { U("server_certificates"), value_of({ + value_of({ + { U("key_algorithm"), U("ECDSA") }, + { U("private_key_file"), U("key.pem") }, + { U("certificate_chain_file"), U("chain.pem") } + }) + }) } + }); + BST_CHECK_NO_THROW(nmos::validate_node_settings(ok)); + } + { + const auto bad = value_of({ + { U("server_certificates"), value_of({ + value_of({ { U("key_algorithm"), U("DSA") } }) + }) } + }); + BST_CHECK_THROW(nmos::validate_node_settings(bad), web::json::json_exception); + } + { + // omitting key_algorithm is fine - omission is the way to say "don't care" + const auto ok = value_of({ + { U("server_certificates"), value_of({ + value_of({ + { U("private_key_file"), U("key.pem") }, + { U("certificate_chain_file"), U("chain.pem") } + }) + }) } + }); + BST_CHECK_NO_THROW(nmos::validate_node_settings(ok)); + } + { + // but explicitly setting it to the empty string is a sign of user confusion + const auto bad = value_of({ + { U("server_certificates"), value_of({ + value_of({ { U("key_algorithm"), U("") } }) + }) } + }); + BST_CHECK_THROW(nmos::validate_node_settings(bad), web::json::json_exception); + } + { + // an entry without both private_key_file and certificate_chain_file is meaningless; + // the cert-loader logs warnings and pushes an empty certificate that fails at TLS time + const auto bad = value_of({ + { U("server_certificates"), value_of({ + value_of({ { U("key_algorithm"), U("ECDSA") } }) + }) } + }); + BST_CHECK_THROW(nmos::validate_node_settings(bad), web::json::json_exception); + } + { + // empty server_certificates array is the right way to say "no server certs" + const auto ok = value_of({ { U("server_certificates"), web::json::value::array() } }); + BST_CHECK_NO_THROW(nmos::validate_node_settings(ok)); + } +} + +BST_TEST_CASE(testValidateSettingsNonObject) +{ + BST_CHECK_THROW(nmos::validate_node_settings(web::json::value::string(U("hi"))), web::json::json_exception); + BST_CHECK_THROW(nmos::validate_registry_settings(web::json::value::array()), web::json::json_exception); +} diff --git a/Development/nmos/transport.h b/Development/nmos/transport.h index 8b45279b0..3da059d08 100644 --- a/Development/nmos/transport.h +++ b/Development/nmos/transport.h @@ -21,6 +21,8 @@ namespace nmos const transport mqtt{ U("urn:x-nmos:transport:mqtt") }; const transport websocket{ U("urn:x-nmos:transport:websocket") }; + + const transport mxl{ U("urn:x-nmos:transport:mxl") }; } // "Subclassifications are defined as the portion of the URN which follows the first occurrence of a '.', but prior to any '/' character." diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/activation-response-schema.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/activation-response-schema.json new file mode 100644 index 000000000..5bb0f4f74 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/activation-response-schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Parameters concerned with activation of the transport parameters", + "type": "object", + "additionalProperties": false, + "required": [ + "mode", + "requested_time", + "activation_time" + ], + "properties": { + "mode": { + "description": "Mode of activation: immediate (on message receipt), scheduled_absolute (when internal clock >= requested_time), scheduled_relative (when internal clock >= time of message receipt + requested_time), or null (no activation scheduled). This parameter returns to null on the staged endpoint once an activation is completed or when it is explicitly set to null. For immediate activations, in the response to the PATCH request this field will be set to 'activate_immediate', but will be null in response to any subsequent GET requests.", + "anyOf": [{ + "type": "string", + "enum": [ + "activate_immediate", + "activate_scheduled_absolute", + "activate_scheduled_relative" + ] + }, { + "type": "null" + }] + }, + "requested_time": { + "description": "String formatted TAI timestamp (:) indicating time (absolute or relative) for activation requested. This field returns to null once the activation is completed on the staged endpoint or when the resource is unlocked by setting the activation mode to null. For an immediate activation this field will always be null on the staged endpoint, even in the response to the PATCH request.", + "anyOf": [{ + "type": "string", + "pattern": "^[0-9]+:[0-9]+$" + }, { + "type": "null" + }] + }, + "activation_time": { + "description": "String formatted TAI timestamp (:) indicating the absolute time the sender or receiver will or did actually activate for scheduled activations, or the time activation occurred for immediate activations. On the staged endpoint this field returns to null once the activation is completed or when the resource is unlocked by setting the activation mode to null. For immediate activations on the staged endpoint this property will be the time the activation actually occurred in the response to the PATCH request, but null in response to any GET requests thereafter.", + "anyOf": [{ + "type": "string", + "pattern": "^[0-9]+:[0-9]+$" + }, { + "type": "null" + }] + } + } +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/activation-schema.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/activation-schema.json new file mode 100644 index 000000000..27cefb795 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/activation-schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "title": "Activation resource", + "description": "Parameters concerned with activation of the transport parameters", + "type": "object", + "additionalProperties": false, + "required": [ + "mode" + ], + "properties": { + "mode": { + "description": "Mode of activation: immediate (on message receipt), scheduled_absolute (when internal clock >= requested_time), scheduled_relative (when internal clock >= time of message receipt + requested_time), or null (no activation scheduled)", + "anyOf": [{ + "type": "string", + "enum": [ + "activate_immediate", + "activate_scheduled_absolute", + "activate_scheduled_relative" + ] + }, { + "type": "null" + }] + }, + "requested_time": { + "description": "String formatted TAI timestamp (:) indicating time (absolute or relative) for activation. Should be null or not present if 'mode' is null.", + "anyOf": [{ + "type": "string", + "pattern": "^[0-9]+:[0-9]+$" + }, { + "type": "null" + }] + } + } +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/bulk-receiver-post-schema.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/bulk-receiver-post-schema.json new file mode 100644 index 000000000..4b9be73a2 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/bulk-receiver-post-schema.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "description": "Describes a bulk receiver update resource", + "title": "Bulk receiver resource", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "params" + ], + "properties": { + "id": { + "description": "ID of the target receiver to apply parameters to", + "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}$" + }, + "params": { + "$ref": "receiver-stage-schema.json" + } + } + } +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/bulk-response-schema.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/bulk-response-schema.json new file mode 100644 index 000000000..46ab6d531 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/bulk-response-schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes a response to a bulk activation request", + "title": "Bulk activation response", + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "code" + ], + "properties": { + "id": { + "description": "ID of a device to be activated", + "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}$" + }, + "code": { + "description": "HTTP status code that would have resulted from an individual activation on this device", + "type": "integer", + "anyOf": [ + { "minimum": 200, "maximum": 299 }, + { "minimum": 400, "maximum": 599 } + ] + }, + "error": { + "description": "Human readable message which is suitable for user interface display, and helpful to the user. Only included if 'code' indicates an error state", + "type": "string" + }, + "debug": { + "description": "Debug information which may assist a programmer working with the API. Only included if 'code' indicates an error state", + "type": ["null", "string"] + } + } + } +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/bulk-sender-post-schema.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/bulk-sender-post-schema.json new file mode 100644 index 000000000..b5fb0dca0 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/bulk-sender-post-schema.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "description": "Describes a bulk sender update resource", + "title": "Bulk sender resource", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "params" + ], + "properties": { + "id": { + "description": "ID of the target sender to apply parameters to", + "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}$" + }, + "params": { + "$ref": "sender-stage-schema.json" + } + } + } +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/connectionapi-base.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/connectionapi-base.json new file mode 100644 index 000000000..400106144 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/connectionapi-base.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "description": "Describes the Connection API base resource", + "title": "Connection API base resource", + "items": { + "type": "string", + "enum": [ + "bulk/", + "single/" + ] + }, + "minItems": 2, + "maxItems": 2, + "uniqueItems": true +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/connectionapi-bulk.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/connectionapi-bulk.json new file mode 100644 index 000000000..03fc8f778 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/connectionapi-bulk.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "description": "Describes the Connection API /bulk base resource", + "title": "Connection API /bulk base resource", + "items": { + "type": "string", + "enum": [ + "senders/", + "receivers/" + ] + }, + "minItems": 2, + "maxItems": 2, + "uniqueItems": true +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/connectionapi-receiver.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/connectionapi-receiver.json new file mode 100644 index 000000000..99cd2d347 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/connectionapi-receiver.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "description": "Describes the Connection API /single/receivers/{receiverId} base resource", + "title": "Connection API /single/receivers/{receiverId} base resource", + "items": { + "type": "string", + "enum": [ + "constraints/", + "staged/", + "active/", + "transporttype/" + ] + }, + "minItems": 4, + "maxItems": 4, + "uniqueItems": true +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/connectionapi-sender.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/connectionapi-sender.json new file mode 100644 index 000000000..8d072dbb7 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/connectionapi-sender.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "description": "Describes the Connection API /single/senders/{senderId} base resource", + "title": "Connection API /single/senders/{senderId} base resource", + "items": { + "type": "string", + "enum": [ + "constraints/", + "staged/", + "active/", + "transportfile/", + "transporttype/" + ] + }, + "minItems": 5, + "maxItems": 5, + "uniqueItems": true +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/connectionapi-single.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/connectionapi-single.json new file mode 100644 index 000000000..0d36e835f --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/connectionapi-single.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "description": "Describes the Connection API /single base resource", + "title": "Connection API /single base resource", + "items": { + "type": "string", + "enum": [ + "senders/", + "receivers/" + ] + }, + "minItems": 2, + "maxItems": 2, + "uniqueItems": true +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/constraint-schema.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/constraint-schema.json new file mode 100644 index 000000000..fb9631173 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/constraint-schema.json @@ -0,0 +1,62 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Definition of a single constraint record", + "title": "Constraint", + "definitions": { + "constraint": { + "type": "object", + "description": "The constraints for a single transport parameter", + "properties": { + "maximum": { + "description": "The inclusive maximum value the parameter can be set to", + "type": [ + "integer", + "number" + ] + }, + "minimum": { + "description": "The inclusive minimum value the parameter can be set to", + "type": [ + "integer", + "number" + ] + }, + "enum": { + "description": "An array of allowed values", + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "integer" + }, + { + "type": "null" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + "pattern": { + "description": "A regex pattern that must be satisfied for this parameter", + "type": "string", + "format": "regex" + }, + "description": { + "description": "A human readable string describing the constraint (optional)", + "type": "string" + } + }, + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/constraints-schema-mqtt.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/constraints-schema-mqtt.json new file mode 100644 index 000000000..1219dc19e --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/constraints-schema-mqtt.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Used to express the dynamic constraints on MQTT transport parameters. These constraints may be set and changed at run time. Every transport parameter must have an entry, even if it is only an empty object.", + "required": [ + "broker_topic", + "broker_protocol", + "broker_authorization", + "connection_status_broker_topic" + ], + "additionalProperties": false, + "patternProperties": { + "^ext_[a-zA-Z0-9_]+$":{ + "$ref": "constraint-schema.json#/definitions/constraint" + } + }, + "properties": { + "destination_host": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "source_host": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "broker_topic": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "broker_protocol": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "broker_authorization": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "connection_status_broker_topic": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "source_port": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "destination_port": { + "$ref": "constraint-schema.json#/definitions/constraint" + } + } +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/constraints-schema-mxl.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/constraints-schema-mxl.json new file mode 100644 index 000000000..fffd12737 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/constraints-schema-mxl.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Used to express the dynamic constraints on MXL transport parameters. These constraints may be set and changed at run time. Every transport parameter must have an entry, even if it is only an empty object. See BCP-007-03.", + "required": [ + "mxl_domain_id", + "mxl_flow_id" + ], + "additionalProperties": false, + "patternProperties": { + "^ext_[a-zA-Z0-9_]+$": { + "$ref": "constraint-schema.json#/definitions/constraint" + } + }, + "properties": { + "mxl_domain_id": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "mxl_flow_id": { + "$ref": "constraint-schema.json#/definitions/constraint" + } + } +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/constraints-schema-rtp.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/constraints-schema-rtp.json new file mode 100644 index 000000000..a20613be6 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/constraints-schema-rtp.json @@ -0,0 +1,81 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Used to express the dynamic constraints on RTP transport parameters. These constraints may be set and changed at run time. Every transport parameter must have an entry, even if it is only an empty object.", + "required": [ + "source_ip", + "destination_port", + "rtp_enabled" + ], + "additionalProperties": false, + "patternProperties": { + "^ext_[a-zA-Z0-9_]+$":{ + "$ref": "constraint-schema.json#/definitions/constraint" + } + }, + "properties": { + "multicast_ip": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "destination_ip": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "destination_port": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "source_ip": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "interface_ip": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "source_port": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "fec_enabled": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "fec_destination_ip": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "fec_mode": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "fec_type": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "fec_block_width": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "fec_block_height": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "fec1D_destination_port": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "fec2D_destination_port": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "fec1D_source_port": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "fec2D_source_port": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "rtcp_enabled": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "rtcp_destination_ip": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "rtcp_destination_port": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "rtcp_source_port": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "rtp_enabled": { + "$ref": "constraint-schema.json#/definitions/constraint" + } + } +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/constraints-schema-websocket.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/constraints-schema-websocket.json new file mode 100644 index 000000000..cf3215eb8 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/constraints-schema-websocket.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Used to express the dynamic constraints on WebSocket transport parameters. These constraints may be set and changed at run time. Every transport parameter must have an entry, even if it is only an empty object.", + "required": [ + "connection_uri", + "connection_authorization" + ], + "additionalProperties": false, + "patternProperties": { + "^ext_[a-zA-Z0-9_]+$":{ + "$ref": "constraint-schema.json#/definitions/constraint" + } + }, + "properties": { + "connection_uri": { + "$ref": "constraint-schema.json#/definitions/constraint" + }, + "connection_authorization": { + "$ref": "constraint-schema.json#/definitions/constraint" + } + } +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/constraints-schema.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/constraints-schema.json new file mode 100644 index 000000000..ab3969c97 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/constraints-schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Used to express the dynamic constraints on transport parameters. These constraints may be set and changed at run time. Parameters must also conform with constraints inferred from the specification. Every transport parameter must have an entry, even if it is only an empty object.", + "title": "Constraints", + "anyOf": [{ + "type": "array", + "items": { + "$ref": "constraints-schema-rtp.json" + } + }, + { + "type": "array", + "items": { + "$ref": "constraints-schema-websocket.json" + } + }, + { + "type": "array", + "items": { + "$ref": "constraints-schema-mqtt.json" + } + }, + { + "type": "array", + "items": { + "$ref": "constraints-schema-mxl.json" + } + } + ] +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/error.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/error.json new file mode 100644 index 000000000..402147b52 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/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-05/v1.2-dev/APIs/schemas/receiver-response-schema.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver-response-schema.json new file mode 100644 index 000000000..df3343fcf --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver-response-schema.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Describes a receiver", + "title": "Receiver resource", + "additionalProperties": false, + "required": [ + "sender_id", + "master_enable", + "activation", + "transport_file", + "transport_params" + ], + "properties": { + "sender_id": { + "description": "ID of the Sender subscribed to by this Receiver. This will be null if the receiver has not been configured to receive anything, or if it is receiving from a non-NMOS sender.", + "type": [ + "string", + "null" + ], + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + }, + "master_enable": { + "description": "Master on/off control for receiver", + "type": "boolean" + }, + "activation": { + "$ref": "activation-response-schema.json" + }, + "transport_file": { + "$ref": "receiver-transport-file.json" + }, + "transport_params": { + "$ref": "receiver_transport_params.json" + } + } +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver-stage-schema.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver-stage-schema.json new file mode 100644 index 000000000..301719123 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver-stage-schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Describes a receiver", + "title": "Receiver resource", + "additionalProperties": false, + "properties": { + "sender_id": { + "description": "ID of the Sender subscribed to by this Receiver. This will be null if the receiver has not been configured to receive anything, or if it is receiving from a non-NMOS sender.", + "type": [ + "string", + "null" + ], + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + }, + "master_enable": { + "description": "Master on/off control for receiver", + "type": "boolean" + }, + "activation": { + "$ref": "activation-schema.json" + }, + "transport_file": { + "$ref": "receiver-transport-file.json" + }, + "transport_params": { + "$ref": "receiver_transport_params.json" + } + } +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver-transport-file.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver-transport-file.json new file mode 100644 index 000000000..3c7be7c2e --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver-transport-file.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Transport file parameters. 'data' and 'type' must both be strings or both be null. If 'type' is non-null 'data' is expected to contain a valid instance of the specified media type.", + "title": "Transport file", + "additionalProperties": false, + "required": [ + "data", + "type" + ], + "properties": { + "data": { + "description": "Content of the transport file", + "type": [ + "string", + "null" + ] + }, + "type": { + "description": "IANA assigned media type for file (e.g application/sdp)", + "type": [ + "string", + "null" + ] + } + } +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params.json new file mode 100644 index 000000000..b6c9aa6dc --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Transport-specific parameters. If this parameter is included in a client request it must include the same number of array elements (or 'legs') as specified in the constraints. If no changes are required to a specific leg it must be included as an empty object ({}).", + "title": "Receiver Transport Parameters", + "anyOf": [{ + "type": "array", + "items": { + "$ref": "receiver_transport_params_rtp.json" + } + }, + { + "type": "array", + "items": { + "$ref": "receiver_transport_params_dash.json" + } + }, + { + "type": "array", + "items": { + "$ref": "receiver_transport_params_websocket.json" + } + }, + { + "type": "array", + "items": { + "$ref": "receiver_transport_params_mqtt.json" + } + }, + { + "type": "array", + "items": { + "$ref": "receiver_transport_params_mxl.json" + } + } + ] +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params_dash.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params_dash.json new file mode 100644 index 000000000..ca07e7fce --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params_dash.json @@ -0,0 +1,3 @@ +{ + "not": {} +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params_ext.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params_ext.json new file mode 100644 index 000000000..9b0dbb9b5 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params_ext.json @@ -0,0 +1,11 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes external Receiver transport parameters defined in other AMWA specifications. The constraints in this schema are minimum constraints, but may be further constrained at the constraints endpoint.", + "title": "External Receiver Transport Parameters", + "type":[ + "string", + "boolean", + "null", + "number" + ] +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params_mqtt.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params_mqtt.json new file mode 100644 index 000000000..84f6548b1 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params_mqtt.json @@ -0,0 +1,83 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes MQTT Receiver transport parameters. The constraints in this schema are minimum constraints, but may be further constrained at the constraints endpoint. MQTT Receivers must support all parameters in this schema.", + "title": "MQTT Receiver Transport Parameters", + "type": "object", + "title": "Receiver Input", + "properties": { + "source_host": { + "type": [ + "string", + "null" + ], + "description": "Hostname or IP hosting the MQTT broker. If the parameter is set to auto the Receiver should establish for itself which broker it should use, based on a discovery mechanism or its own internal configuration. A null value indicates that the Receiver has not yet been configured.", + "anyOf": [{ + "pattern": "^auto$" + }, + { + "format": "hostname" + }, + { + "format": "ipv4" + }, + { + "format": "ipv6" + }, + { + "type": "null" + } + ] + }, + "source_port": { + "type": [ + "integer", + "string" + ], + "description": "Source port for MQTT traffic. If the parameter is set to auto the Receiver should establish for itself which broker it should use, based on a discovery mechanism or its own internal configuration.", + "minimum": 1, + "maximum": 65535, + "pattern": "^auto$" + }, + "broker_protocol": { + "type": "string", + "description": "Indication of whether TLS is used for communication with the broker. 'mqtt' indicates operation without TLS, and 'secure-mqtt' indicates use of TLS. If the parameter is set to auto the Receiver should establish for itself which protocol it should use, based on a discovery mechanism or its own internal configuration.", + "enum": [ + "auto", + "mqtt", + "secure-mqtt" + ] + }, + "broker_authorization": { + "type": [ + "string", + "boolean" + ], + "description": "Indication of whether authorization is used for communication with the broker. If the parameter is set to auto the Receiver should establish for itself whether authorization should be used, based on a discovery mechanism or its own internal configuration.", + "enum": [ + "auto", + true, + false + ] + }, + "broker_topic": { + "type": [ + "string", + "null" + ], + "description": "The topic which MQTT messages will be received from via the MQTT broker. A null value indicates that the Receiver has not yet been configured." + }, + "connection_status_broker_topic": { + "type": [ + "string", + "null" + ], + "description": "The topic used for MQTT status messages such as MQTT Last Will which are received via the MQTT broker. A null value indicates that the Receiver has not yet been configured, or is not using a connection status topic." + } + }, + "patternProperties": { + "^ext_[a-zA-Z0-9_]+$": { + "$ref": "receiver_transport_params_ext.json" + } + }, + "additionalProperties": false +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params_mxl.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params_mxl.json new file mode 100644 index 000000000..c1153be59 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params_mxl.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes MXL Receiver transport parameters. The constraints in this schema are minimum constraints, but may be further constrained at the constraints endpoint.", + "title": "MXL Receiver Transport Parameters", + "type": "object", + "properties": { + "mxl_domain_id": { + "type": [ + "string", + "null" + ], + "description": "MXL domain identity (UUID). Use auto to select from constraints.", + "anyOf": [{ + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + }, + { + "pattern": "^auto$" + }, + { + "type": "null" + } + ] + }, + "mxl_flow_id": { + "type": [ + "string", + "null" + ], + "description": "MXL flow identity for the read operation. Need not match the IS-04 Flow id. BCP-007-03: null or UUID only; the literal auto is not used for receivers.", + "anyOf": [{ + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + }, + { + "type": "null" + } + ] + } + }, + "patternProperties": { + "^ext_[a-zA-Z0-9_]+$": { + "$ref": "receiver_transport_params_ext.json" + } + }, + "additionalProperties": false +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params_rtp.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params_rtp.json new file mode 100644 index 000000000..ee119205c --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params_rtp.json @@ -0,0 +1,152 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes RTP Receiver transport parameters. The constraints in this schema are minimum constraints, but may be further constrained at the constraints endpoint. Receivers must support at least the `source_ip`, `interface_ip`, `rtp_enabled` and `destination_port` parameters, and must support the `multicast_ip` parameter if they are capable of multicast operation. Receivers supporting FEC and/or RTCP must support parameters prefixed with `fec` and `rtcp` respectively.", + "title": "RTP Receiver Transport Parameters", + "type": "object", + "title": "Receiver Input", + "properties": { + "source_ip": { + "type": [ + "string", + "null" + ], + "description": "Source IP address of RTP packets in unicast mode, source filter for source specific multicast. A null value indicates that the source IP address has not been configured in unicast mode, or the Receiver is in any-source multicast mode.", + "anyOf": [{ + "format": "ipv4" + }, + { + "format": "ipv6" + }, + { + "type": "null" + } + ] + }, + "multicast_ip": { + "type": [ + "string", + "null" + ], + "description": "IP multicast group address used in multicast operation only. Should be set to null during unicast operation. A null value indicates the parameter has not been configured, or the receiver is operating in unicast mode.", + "anyOf": [{ + "format": "ipv4" + }, + { + "format": "ipv6" + }, + { + "type": "null" + } + ] + }, + "interface_ip": { + "type": "string", + "description": "IP address of the network interface the receiver should use. The receiver should provide an enum in the constraints endpoint, which should contain the available interface addresses. If set to auto in multicast mode the receiver should determine which interface to use for itself, for example by using the routing tables. The behaviour of auto is undefined in unicast mode, and controllers should supply a specific interface address.", + "anyOf": [{ + "format": "ipv4" + }, + { + "format": "ipv6" + }, + { + "pattern": "^auto$" + } + ] + }, + "destination_port": { + "type": [ + "integer", + "string" + ], + "description": "destination port for RTP packets (auto = 5004 by default)", + "minimum": 1, + "maximum": 65535, + "pattern": "^auto$" + }, + "fec_enabled": { + "type": "boolean", + "description": "FEC on/off" + }, + "fec_destination_ip": { + "type": "string", + "description": "May be used if NAT is being used at the destination (auto = multicast_ip (multicast mode) or interface_ip (unicast mode) by default)", + "anyOf": [{ + "format": "ipv4" + }, + { + "format": "ipv6" + }, + { + "pattern": "^auto$" + } + ] + }, + "fec_mode": { + "type": "string", + "description": "forward error correction mode to apply. (auto = highest available number of dimensions by default)", + "enum": [ + "auto", + "1D", + "2D" + ] + }, + "fec1D_destination_port": { + "type": [ + "integer", + "string" + ], + "description": "destination port for RTP Column FEC packets (auto = RTP destination_port + 2 by default)", + "minimum": 1, + "maximum": 65535, + "pattern": "^auto$" + }, + "fec2D_destination_port": { + "type": [ + "integer", + "string" + ], + "description": "destination port for RTP Row FEC packets (auto = RTP destination_port + 4 by default)", + "minimum": 1, + "maximum": 65535, + "pattern": "^auto$" + }, + "rtcp_destination_ip": { + "type": "string", + "description": "Destination IP address of RTCP packets (auto = multicast_ip (multicast mode) or interface_ip (unicast mode) by default)", + "anyOf": [{ + "format": "ipv4" + }, + { + "format": "ipv6" + }, + { + "pattern": "^auto$" + } + ] + }, + "rtcp_enabled": { + "type": "boolean", + "description": "RTCP on/off" + }, + "rtcp_destination_port": { + "type": [ + "integer", + "string" + ], + "description": "destination port for RTCP packets (auto = RTP destination_port + 1 by default)", + "minimum": 1, + "maximum": 65535, + "pattern": "^auto$" + }, + "rtp_enabled": { + "type": "boolean", + "description": "RTP reception active/inactive" + } + }, + "patternProperties": { + "^ext_[a-zA-Z0-9_]+$": { + "$ref": "receiver_transport_params_ext.json" + } + }, + "additionalProperties": false +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params_websocket.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params_websocket.json new file mode 100644 index 000000000..38f168143 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/receiver_transport_params_websocket.json @@ -0,0 +1,42 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes WebSocket Receiver transport parameters. The constraints in this schema are minimum constraints, but may be further constrained at the constraints endpoint. WebSocket Receivers must support all parameters in this schema.", + "title": "WebSocket Receiver Transport Parameters", + "type": "object", + "title": "Receiver Input", + "properties": { + "connection_uri": { + "type": [ + "string", + "null" + ], + "description": "URI hosting the WebSocket server as defined in RFC 6455 Section 3. A null value indicates that the receiver has not yet been configured.", + "anyOf": [{ + "format": "uri", + "pattern": "^wss?:\/\/.*" + }, + { + "type": "null" + } + ] + }, + "connection_authorization": { + "type": [ + "string", + "boolean" + ], + "description": "Indication of whether authorization is required to make a connection. If the parameter is set to auto the Receiver should establish for itself whether authorization should be used, based on its own internal configuration.", + "enum": [ + "auto", + true, + false + ] + } + }, + "patternProperties": { + "^ext_[a-zA-Z0-9_]+$": { + "$ref": "receiver_transport_params_ext.json" + } + }, + "additionalProperties": false +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender-receiver-base.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender-receiver-base.json new file mode 100644 index 000000000..942bf0d86 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender-receiver-base.json @@ -0,0 +1,11 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "description": "Describes the Connection API sender/receiver base resource", + "title": "Connection API sender/receiver base resource", + "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-05/v1.2-dev/APIs/schemas/sender-response-schema.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender-response-schema.json new file mode 100644 index 000000000..958bc40db --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender-response-schema.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Describes a sender", + "title": "Sender resource", + "additionalProperties": false, + "required": [ + "receiver_id", + "master_enable", + "activation", + "transport_params" + ], + "properties": { + "receiver_id": { + "description": "ID of the target Receiver of this Sender. This will be null if the sender is operating in multicast mode, or has not been assigned a receiver in unicast mode, or is sending to a non-NMOS receiver in unicast mode.", + "type": [ + "string", + "null" + ], + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + }, + "master_enable": { + "description": "Master on/off control for sender", + "type": "boolean" + }, + "activation": { + "$ref": "activation-response-schema.json" + }, + "transport_params": { + "$ref": "sender_transport_params.json" + } + } +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender-stage-schema.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender-stage-schema.json new file mode 100644 index 000000000..6afc2dd22 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender-stage-schema.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Describes a sender", + "title": "Sender resource", + "additionalProperties": false, + "properties": { + "receiver_id": { + "description": "ID of the target Receiver of this Sender. This will be null if the sender is operating in multicast mode, or has not been assigned a receiver in unicast mode, or is sending to a non-NMOS receiver in unicast mode.", + "type": [ + "string", + "null" + ], + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + }, + "master_enable": { + "description": "Master on/off control for sender", + "type": "boolean" + }, + "activation": { + "$ref": "activation-schema.json" + }, + "transport_params": { + "$ref": "sender_transport_params.json" + } + } +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params.json new file mode 100644 index 000000000..c8d0d7586 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Transport-specific parameters. If this parameter is included in a client request it must include the same number of array elements (or 'legs') as specified in the constraints. If no changes are required to a specific leg it must be included as an empty object ({}).", + "title": "Sender Transport Parameters", + "anyOf": [{ + "type": "array", + "items": { + "$ref": "sender_transport_params_rtp.json" + } + }, + { + "type": "array", + "items": { + "$ref": "sender_transport_params_dash.json" + } + }, + { + "type": "array", + "items": { + "$ref": "sender_transport_params_websocket.json" + } + }, + { + "type": "array", + "items": { + "$ref": "sender_transport_params_mqtt.json" + } + }, + { + "type": "array", + "items": { + "$ref": "sender_transport_params_mxl.json" + } + } + ] +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params_dash.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params_dash.json new file mode 100644 index 000000000..ca07e7fce --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params_dash.json @@ -0,0 +1,3 @@ +{ + "not": {} +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params_ext.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params_ext.json new file mode 100644 index 000000000..8209fd754 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params_ext.json @@ -0,0 +1,11 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes external Sender transport parameters defined in other AMWA specifications. The constraints in this schema are minimum constraints, but may be further constrained at the constraints endpoint.", + "title": "External Sender Transport Parameters", + "type":[ + "string", + "boolean", + "null", + "number" + ] +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params_mqtt.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params_mqtt.json new file mode 100644 index 000000000..a7e98074d --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params_mqtt.json @@ -0,0 +1,83 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes MQTT Sender transport parameters. The constraints in this schema are minimum constraints, but may be further constrained at the constraints endpoint. MQTT Senders must support all properties in this schema.", + "title": "MQTT Sender Transport Parameters", + "type": "object", + "title": "Sender Output", + "properties": { + "destination_host": { + "type": [ + "string", + "null" + ], + "description": "Hostname or IP hosting the MQTT broker. If the parameter is set to auto the Sender should establish for itself which broker it should use, based on a discovery mechanism or its own internal configuration. A null value indicates that the Sender has not yet been configured.", + "anyOf": [{ + "pattern": "^auto$" + }, + { + "format": "hostname" + }, + { + "format": "ipv4" + }, + { + "format": "ipv6" + }, + { + "type": "null" + } + ] + }, + "destination_port": { + "type": [ + "integer", + "string" + ], + "description": "Destination port for MQTT traffic. If the parameter is set to auto the Sender should establish for itself which broker it should use, based on a discovery mechanism or its own internal configuration.", + "minimum": 1, + "maximum": 65535, + "pattern": "^auto$" + }, + "broker_protocol": { + "type": "string", + "description": "Indication of whether TLS is used for communication with the broker. 'mqtt' indicates operation without TLS, and 'secure-mqtt' indicates use of TLS. If the parameter is set to auto the Sender should establish for itself which protocol it should use, based on a discovery mechanism or its own internal configuration.", + "enum": [ + "auto", + "mqtt", + "secure-mqtt" + ] + }, + "broker_authorization": { + "type": [ + "string", + "boolean" + ], + "description": "Indication of whether authorization is used for communication with the broker. If the parameter is set to auto the Sender should establish for itself whether authorization should be used, based on a discovery mechanism or its own internal configuration.", + "enum": [ + "auto", + true, + false + ] + }, + "broker_topic": { + "type": [ + "string", + "null" + ], + "description": "The topic which MQTT messages will be sent to on the MQTT broker. A null value indicates that the Sender has not yet been configured." + }, + "connection_status_broker_topic": { + "type": [ + "string", + "null" + ], + "description": "The topic which MQTT status messages such as MQTT Last Will are sent to on the MQTT broker. A null value indicates that the Sender has not yet been configured, or is not using a connection status topic." + } + }, + "patternProperties": { + "^ext_[a-zA-Z0-9_]+$": { + "$ref": "sender_transport_params_ext.json" + } + }, + "additionalProperties": false +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params_mxl.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params_mxl.json new file mode 100644 index 000000000..527b1c908 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params_mxl.json @@ -0,0 +1,48 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes MXL Sender transport parameters. The constraints in this schema are minimum constraints, but may be further constrained at the constraints endpoint.", + "title": "MXL Sender Transport Parameters", + "type": "object", + "properties": { + "mxl_domain_id": { + "type": [ + "string", + "null" + ], + "description": "MXL domain identity (UUID). Use auto to select from constraints.", + "anyOf": [{ + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + }, + { + "pattern": "^auto$" + }, + { + "type": "null" + } + ] + }, + "mxl_flow_id": { + "type": [ + "string", + "null" + ], + "description": "MXL flow identity for the write operation. Need not match the IS-04 Flow id. Use auto to select from constraints.", + "anyOf": [{ + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + }, + { + "pattern": "^auto$" + }, + { + "type": "null" + } + ] + } + }, + "patternProperties": { + "^ext_[a-zA-Z0-9_]+$": { + "$ref": "sender_transport_params_ext.json" + } + }, + "additionalProperties": false +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params_rtp.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params_rtp.json new file mode 100644 index 000000000..b849b9b7b --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params_rtp.json @@ -0,0 +1,191 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes RTP Sender transport parameters. The constraints in this schema are minimum constraints, but may be further constrained at the constraints endpoint. As a minimum all senders must support `source_ip`, `destination_ip`, `source_port`, `rtp_enabled` and `destination_port`. Senders supporting FEC and/or RTCP must support parameters prefixed with `fec` and `rtcp` respectively.", + "title": "RTP Sender Transport Parameters", + "type": "object", + "title": "Sender Output", + "properties": { + "source_ip": { + "type": "string", + "description": "IP address from which RTP packets will be sent (IP address of interface bound to this output). The sender should provide an enum in the constraints endpoint, which should contain the available interface addresses. If the parameter is set to auto the sender should establish for itself which interface it should use, based on routing rules or its own internal configuration.", + "anyOf": [{ + "format": "ipv4" + }, + { + "format": "ipv6" + }, + { + "pattern": "^auto$" + } + ] + }, + "destination_ip": { + "type": "string", + "description": "IP address to which RTP packets will be sent. If auto is set the sender should select a multicast address to send to itself. For example it may implement MADCAP (RFC 2730), ZMAAP, or be allocated address by some other system responsible for co-ordination multicast address use.", + "anyOf": [{ + "format": "ipv4" + }, + { + "format": "ipv6" + }, + { + "pattern": "^auto$" + } + ] + }, + "source_port": { + "type": [ + "integer", + "string" + ], + "description": "source port for RTP packets (auto = 5004 by default)", + "minimum": 0, + "maximum": 65535, + "pattern": "^auto$" + }, + "destination_port": { + "type": [ + "integer", + "string" + ], + "description": "destination port for RTP packets (auto = 5004 by default)", + "minimum": 1, + "maximum": 65535, + "pattern": "^auto$" + }, + "fec_enabled": { + "type": "boolean", + "description": "FEC on/off" + }, + "fec_destination_ip": { + "type": "string", + "description": "May be used if NAT is being used at the destination (auto = destination_ip by default)", + "anyOf": [{ + "format": "ipv4" + }, + { + "format": "ipv6" + }, + { + "pattern": "^auto$" + } + ] + }, + "fec_type": { + "type": "string", + "description": "forward error correction mode to apply", + "enum": [ + "XOR", + "Reed-Solomon" + ] + }, + "fec_mode": { + "type": "string", + "description": "forward error correction mode to apply", + "enum": [ + "1D", + "2D" + ] + }, + "fec_block_width": { + "type": "integer", + "description": "width of block over which FEC is calculated in packets", + "minimum": 4, + "maximum": 200 + }, + "fec_block_height": { + "type": "integer", + "description": "height of block over which FEC is calculated in packets", + "minimum": 4, + "maximum": 200 + }, + "fec1D_destination_port": { + "type": [ + "integer", + "string" + ], + "description": "destination port for RTP Column FEC packets (auto = RTP destination_port + 2 by default)", + "minimum": 1, + "maximum": 65535, + "pattern": "^auto$" + }, + "fec2D_destination_port": { + "type": [ + "integer", + "string" + ], + "description": "destination port for RTP Row FEC packets (auto = RTP destination_port + 4 by default)", + "minimum": 1, + "maximum": 65535, + "pattern": "^auto$" + }, + "fec1D_source_port": { + "type": [ + "integer", + "string" + ], + "description": "source port for RTP FEC packets (auto = RTP source_port + 2 by default)", + "minimum": 0, + "maximum": 65535, + "pattern": "^auto$" + }, + "fec2D_source_port": { + "type": [ + "integer", + "string" + ], + "description": "source port for RTP FEC packets (auto = RTP source_port + 4 by default)", + "minimum": 0, + "maximum": 65535, + "pattern": "^auto$" + }, + "rtcp_enabled": { + "type": "boolean", + "description": "rtcp on/off" + }, + "rtcp_destination_ip": { + "type": "string", + "description": "IP address to which RTCP packets will be sent (auto = same as RTP destination_ip by default)", + "anyOf": [{ + "format": "ipv4" + }, + { + "format": "ipv6" + }, + { + "pattern": "^auto$" + } + ] + }, + "rtcp_destination_port": { + "type": [ + "integer", + "string" + ], + "description": "destination port for RTCP packets (auto = RTP destination_port + 1 by default)", + "minimum": 1, + "maximum": 65535, + "pattern": "^auto$" + }, + "rtcp_source_port": { + "type": [ + "integer", + "string" + ], + "description": "source port for RTCP packets (auto = RTP source_port + 1 by default)", + "minimum": 0, + "maximum": 65535, + "pattern": "^auto$" + }, + "rtp_enabled": { + "type": "boolean", + "description": "RTP transmission active/inactive" + } + }, + "patternProperties": { + "^ext_[a-zA-Z0-9_]+$": { + "$ref": "sender_transport_params_ext.json" + } + }, + "additionalProperties": false +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params_websocket.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params_websocket.json new file mode 100644 index 000000000..65c187f28 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/sender_transport_params_websocket.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Describes WebSocket Sender transport parameters. The constraints in this schema are minimum constraints, but may be further constrained at the constraints endpoint. WebSocket Senders must support all parameters in this schema.", + "title": "WebSocket Sender Transport Parameters", + "type": "object", + "title": "Sender Output", + "properties": { + "connection_uri": { + "type": [ + "string", + "null" + ], + "description": "URI hosting the WebSocket server as defined in RFC 6455 Section 3. The sender should provide an enum in the constraints endpoint, which should contain the available interface addresses formatted as connection URIs. If the parameter is set to auto the sender should establish for itself which interface it should use, based on routing rules or its own internal configuration. A null value indicates that the sender has not yet been configured.", + "anyOf": [{ + "pattern": "^auto$" + }, + { + "format": "uri", + "pattern": "^wss?:\/\/.*" + }, + { + "type": "null" + } + ] + }, + "connection_authorization": { + "type": [ + "string", + "boolean" + ], + "description": "Indication of whether authorization is required to make a connection. If the parameter is set to auto the Sender should establish for itself whether authorization should be used, based on its own internal configuration.", + "enum": [ + "auto", + true, + false + ] + } + }, + "patternProperties": { + "^ext_[a-zA-Z0-9_]+$": { + "$ref": "sender_transport_params_ext.json" + } + }, + "additionalProperties": false +} diff --git a/Development/third_party/is-05/v1.2-dev/APIs/schemas/transporttype-response-schema.json b/Development/third_party/is-05/v1.2-dev/APIs/schemas/transporttype-response-schema.json new file mode 100644 index 000000000..6a3f1a346 --- /dev/null +++ b/Development/third_party/is-05/v1.2-dev/APIs/schemas/transporttype-response-schema.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Transport Type", + "description": "Transport type URN base used by the Sender or Receiver (i.e. with any subclassifications or versions removed)", + "type": "string", + "oneOf": [ + { + "enum": [ + "urn:x-nmos:transport:rtp", + "urn:x-nmos:transport:dash", + "urn:x-nmos:transport:websocket", + "urn:x-nmos:transport:mqtt", + "urn:x-nmos:transport:mxl" + ] + } + ], + "format": "uri" +} diff --git a/README.md b/README.md index 25967f0e8..a09ba47ee 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ This repository contains an implementation of the [AMWA Networked Media Open Spe - [AMWA BCP-003-02 Authorization in NMOS Systems](https://specs.amwa.tv/bcp-003-02/) - [AMWA BCP-004-01 NMOS Receiver Capabilities](https://specs.amwa.tv/bcp-004-01/) - [AMWA BCP-006-01 NMOS With JPEG XS](https://specs.amwa.tv/bcp-006-01/) +- [AMWA BCP-007-03 NMOS With MXL](https://specs.amwa.tv/bcp-007-03/) - [AMWA BCP-008-01 NMOS Receiver Status](https://specs.amwa.tv/bcp-008-01/) - [AMWA BCP-008-02 NMOS Sender Status](https://specs.amwa.tv/bcp-008-02/) - [AMWA MS-05-01 NMOS Control Architecture](https://specs.amwa.tv/ms-05-01/) @@ -82,6 +83,7 @@ The [AMWA NMOS API Testing Tool](https://github.com/AMWA-TV/nmos-testing) is aut **Test Suite/Status:** [![BCP-003-01][BCP-003-01-badge]][BCP-003-01-sheet] [![BCP-006-01-01][BCP-006-01-01-badge]][BCP-006-01-01-sheet] +[![BCP-007-03-01][BCP-007-03-01-badge]][BCP-007-03-01-sheet] [![BCP-008-01-01][BCP-008-01-01-badge]][BCP-008-01-01-sheet] [![BCP-008-02-01][BCP-008-02-01-badge]][BCP-008-02-01-sheet] [![IS-04-01][IS-04-01-badge]][IS-04-01-sheet] @@ -100,6 +102,7 @@ The [AMWA NMOS API Testing Tool](https://github.com/AMWA-TV/nmos-testing) is aut [BCP-003-01-badge]: https://raw.githubusercontent.com/sony/nmos-cpp/badges/BCP-003-01.svg [BCP-006-01-01-badge]: https://raw.githubusercontent.com/sony/nmos-cpp/badges/BCP-006-01-01.svg +[BCP-007-03-01-badge]: https://raw.githubusercontent.com/sony/nmos-cpp/badges/BCP-007-03-01.svg [BCP-008-01-01-badge]: https://raw.githubusercontent.com/sony/nmos-cpp/badges/BCP-008-01-01.svg [BCP-008-02-01-badge]: https://raw.githubusercontent.com/sony/nmos-cpp/badges/BCP-008-02-01.svg [IS-04-01-badge]: https://raw.githubusercontent.com/sony/nmos-cpp/badges/IS-04-01.svg @@ -117,6 +120,7 @@ The [AMWA NMOS API Testing Tool](https://github.com/AMWA-TV/nmos-testing) is aut [IS-14-01-badge]: https://raw.githubusercontent.com/sony/nmos-cpp/badges/IS-14-01.svg [BCP-003-01-sheet]: https://docs.google.com/spreadsheets/d/1UgZoI0lGCMDn9-zssccf2Azil3WN6jogroMT8Wh6H64/edit#gid=468090822 [BCP-006-01-01-sheet]: https://docs.google.com/spreadsheets/d/1UgZoI0lGCMDn9-zssccf2Azil3WN6jogroMT8Wh6H64/edit?gid=1835994800 +[BCP-007-03-01-sheet]: https://docs.google.com/spreadsheets/d/1UgZoI0lGCMDn9-zssccf2Azil3WN6jogroMT8Wh6H64/edit?gid=926264123 [BCP-008-01-01-sheet]: https://docs.google.com/spreadsheets/d/1UgZoI0lGCMDn9-zssccf2Azil3WN6jogroMT8Wh6H64/edit?gid=1290589116 [BCP-008-02-01-sheet]: https://docs.google.com/spreadsheets/d/1UgZoI0lGCMDn9-zssccf2Azil3WN6jogroMT8Wh6H64/edit?gid=582384563 [IS-04-01-sheet]: https://docs.google.com/spreadsheets/d/1UgZoI0lGCMDn9-zssccf2Azil3WN6jogroMT8Wh6H64/edit#gid=0 @@ -139,6 +143,7 @@ The implementation is designed to be extended. Development is ongoing, following Recent activity on the project (newest first): +- Added support for BCP-007-03 NMOS With MXL - Added support for IS-14 NMOS Device Configuration - Added support for BCP-008-01 Receiver Status Monitoring - Added support for BCP-008-02 Sender Status Monitoring