diff --git a/Development/cmake/NmosCppLibraries.cmake b/Development/cmake/NmosCppLibraries.cmake index 82b78503c..b49f78736 100644 --- a/Development/cmake/NmosCppLibraries.cmake +++ b/Development/cmake/NmosCppLibraries.cmake @@ -760,6 +760,80 @@ target_include_directories(nmos_is10_schemas PUBLIC list(APPEND NMOS_CPP_TARGETS nmos_is10_schemas) add_library(nmos-cpp::nmos_is10_schemas ALIAS nmos_is10_schemas) +# nmos_is12_schemas library + +set(NMOS_IS12_SCHEMAS_HEADERS + nmos/is12_schemas/is12_schemas.h + ) + +set(NMOS_IS12_V1_0_TAG v1.0.x) + +set(NMOS_IS12_V1_0_SCHEMAS_JSON + third_party/is-12/${NMOS_IS12_V1_0_TAG}/APIs/schemas/base-message.json + third_party/is-12/${NMOS_IS12_V1_0_TAG}/APIs/schemas/command-message.json + third_party/is-12/${NMOS_IS12_V1_0_TAG}/APIs/schemas/command-response-message.json + third_party/is-12/${NMOS_IS12_V1_0_TAG}/APIs/schemas/error-message.json + third_party/is-12/${NMOS_IS12_V1_0_TAG}/APIs/schemas/event-data.json + third_party/is-12/${NMOS_IS12_V1_0_TAG}/APIs/schemas/notification-message.json + third_party/is-12/${NMOS_IS12_V1_0_TAG}/APIs/schemas/property-changed-event-data.json + third_party/is-12/${NMOS_IS12_V1_0_TAG}/APIs/schemas/subscription-message.json + third_party/is-12/${NMOS_IS12_V1_0_TAG}/APIs/schemas/subscription-response-message.json + ) + +set(NMOS_IS12_SCHEMAS_JSON_MATCH "third_party/is-12/([^/]+)/APIs/schemas/([^;]+)\\.json") +set(NMOS_IS12_SCHEMAS_SOURCE_REPLACE "${CMAKE_CURRENT_BINARY_DIR_REPLACE}/nmos/is12_schemas/\\1/\\2.cpp") +string(REGEX REPLACE "${NMOS_IS12_SCHEMAS_JSON_MATCH}(;|$)" "${NMOS_IS12_SCHEMAS_SOURCE_REPLACE}\\3" NMOS_IS12_V1_0_SCHEMAS_SOURCES "${NMOS_IS12_V1_0_SCHEMAS_JSON}") + +foreach(JSON ${NMOS_IS12_V1_0_SCHEMAS_JSON}) + string(REGEX REPLACE "${NMOS_IS12_SCHEMAS_JSON_MATCH}" "${NMOS_IS12_SCHEMAS_SOURCE_REPLACE}" SOURCE "${JSON}") + string(REGEX REPLACE "${NMOS_IS12_SCHEMAS_JSON_MATCH}" "\\1" NS "${JSON}") + string(REGEX REPLACE "${NMOS_IS12_SCHEMAS_JSON_MATCH}" "\\2" VAR "${JSON}") + string(MAKE_C_IDENTIFIER "${NS}" NS) + string(MAKE_C_IDENTIFIER "${VAR}" VAR) + + file(WRITE "${SOURCE}.in" "\ +// Auto-generated from: ${JSON}\n\ +\n\ +namespace nmos\n\ +{\n\ + namespace is12_schemas\n\ + {\n\ + namespace ${NS}\n\ + {\n\ + const char* ${VAR} = R\"-auto-generated-(") + + file(READ "${JSON}" RAW) + file(APPEND "${SOURCE}.in" "${RAW}") + + file(APPEND "${SOURCE}.in" ")-auto-generated-\";\n\ + }\n\ + }\n\ +}\n") + + configure_file("${SOURCE}.in" "${SOURCE}" COPYONLY) +endforeach() + +add_library( + nmos_is12_schemas STATIC + ${NMOS_IS12_SCHEMAS_HEADERS} + ${NMOS_IS12_V1_0_SCHEMAS_SOURCES} + ) + +source_group("nmos\\is12_schemas\\Header Files" FILES ${NMOS_IS12_SCHEMAS_HEADERS}) +source_group("nmos\\is12_schemas\\${NMOS_IS12_V1_0_TAG}\\Source Files" FILES ${NMOS_IS12_V1_0_SCHEMAS_SOURCES}) + +target_link_libraries( + nmos_is12_schemas PRIVATE + nmos-cpp::compile-settings + ) +target_include_directories(nmos_is12_schemas PUBLIC + $ + $ + ) + +list(APPEND NMOS_CPP_TARGETS nmos_is12_schemas) +add_library(nmos-cpp::nmos_is12_schemas ALIAS nmos_is12_schemas) + # nmos-cpp library set(NMOS_CPP_BST_SOURCES @@ -851,6 +925,13 @@ set(NMOS_CPP_NMOS_SOURCES nmos/connection_api.cpp nmos/connection_events_activation.cpp nmos/connection_resources.cpp + nmos/control_protocol_handlers.cpp + nmos/control_protocol_methods.cpp + nmos/control_protocol_resource.cpp + nmos/control_protocol_resources.cpp + nmos/control_protocol_state.cpp + nmos/control_protocol_utils.cpp + nmos/control_protocol_ws_api.cpp nmos/did_sdid.cpp nmos/events_api.cpp nmos/events_resources.cpp @@ -937,6 +1018,16 @@ set(NMOS_CPP_NMOS_HEADERS nmos/connection_api.h nmos/connection_events_activation.h nmos/connection_resources.h + nmos/control_protocol_handlers.h + nmos/control_protocol_methods.h + nmos/control_protocol_nmos_channel_mapping_resource_type.h + nmos/control_protocol_nmos_resource_type.h + nmos/control_protocol_resource.h + nmos/control_protocol_resources.h + nmos/control_protocol_state.h + nmos/control_protocol_typedefs.h + nmos/control_protocol_utils.h + nmos/control_protocol_ws_api.h nmos/device_type.h nmos/did_sdid.h nmos/event_type.h @@ -956,6 +1047,7 @@ set(NMOS_CPP_NMOS_HEADERS nmos/is08_versions.h nmos/is09_versions.h nmos/is10_versions.h + nmos/is12_versions.h nmos/issuers.h nmos/json_fields.h nmos/json_schema.h @@ -1105,6 +1197,7 @@ target_link_libraries( nmos-cpp::nmos_is08_schemas nmos-cpp::nmos_is09_schemas nmos-cpp::nmos_is10_schemas + nmos-cpp::nmos_is12_schemas nmos-cpp::mdns nmos-cpp::slog nmos-cpp::OpenSSL diff --git a/Development/cmake/NmosCppTest.cmake b/Development/cmake/NmosCppTest.cmake index b069a2d8e..ac56a57d8 100644 --- a/Development/cmake/NmosCppTest.cmake +++ b/Development/cmake/NmosCppTest.cmake @@ -43,6 +43,7 @@ set(NMOS_CPP_TEST_NMOS_TEST_SOURCES nmos/test/api_utils_test.cpp nmos/test/capabilities_test.cpp nmos/test/channels_test.cpp + nmos/test/control_protocol_test.cpp nmos/test/did_sdid_test.cpp nmos/test/event_type_test.cpp nmos/test/json_validator_test.cpp diff --git a/Development/nmos-cpp-node/config.json b/Development/nmos-cpp-node/config.json index 95b3362ec..f7e5216e6 100644 --- a/Development/nmos-cpp-node/config.json +++ b/Development/nmos-cpp-node/config.json @@ -104,6 +104,9 @@ // is10_versions [registry, node]: used to specify the enabled API versions for a version-locked configuration //"is10_versions": ["v1.0"], + // is12_versions [node]: used to specify the enabled API versions for a version-locked configuration + //"is12_versions": ["v1.0"], + // pri [registry, node]: used for the 'pri' TXT record; specifying nmos::service_priorities::no_priority (maximum value) disables advertisement completely //"pri": 100, @@ -144,6 +147,8 @@ //"channelmapping_port": 3215, // system_port [node]: used to construct request URLs for the System API (if not discovered via DNS-SD) //"system_port": 10641, + // control_protocol_ws_port [node]: used to construct request URLs for the Control Protocol websocket, or negative to disable the control protocol features + //"control_protocol_ws_port": 3218, // listen_backlog [registry, node]: the maximum length of the queue of pending connections, or zero for the implementation default (the implementation may not honour this value) //"listen_backlog": 0, @@ -364,5 +369,19 @@ // If the Resource Server fails to verify a token using all public keys available it MUST reject the token." //"service_unavailable_retry_after": 5, + // manufacturer_name [node]: the manufacturer name of the NcDeviceManager used for NMOS Control Protocol + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdevicemanager + //"manufacturer_name": "", + + // product_name/product_key/product_revision_level [node]: the product description of the NcDeviceManager used for NMOS Control Protocol + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncproduct + //"product_name": "", + //"product_key": "", + //"product_revision_level": "", + + // serial_number [node]: the serial number of the NcDeviceManager used for NMOS Control Protocol + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdevicemanager + //"serial_number": "", + "don't worry": "about trailing commas" } diff --git a/Development/nmos-cpp-node/main.cpp b/Development/nmos-cpp-node/main.cpp index abc239e44..720cafd36 100644 --- a/Development/nmos-cpp-node/main.cpp +++ b/Development/nmos-cpp-node/main.cpp @@ -18,6 +18,8 @@ #include "nmos/server_utils.h" // for make_http_listener_config #include "node_implementation.h" +#include "nmos/control_protocol_state.h" + int main(int argc, char* argv[]) { // Construct our data models including mutexes to protect them @@ -137,13 +139,22 @@ int main(int argc, char* argv[]) .on_request_authorization_code(nmos::experimental::make_request_authorization_code_handler(gate)); // may be omitted, only required for OAuth client which is using the Authorization Code Flow to obtain the access token } + nmos::experimental::control_protocol_state control_protocol_state; + if (0 <= nmos::fields::control_protocol_ws_port(node_model.settings)) + { + node_implementation + .on_get_control_class_descriptor(nmos::make_get_control_protocol_class_descriptor_handler(control_protocol_state)) + .on_get_control_datatype_descriptor(nmos::make_get_control_protocol_datatype_descriptor_handler(control_protocol_state)) + .on_get_control_protocol_method_descriptor(nmos::make_get_control_protocol_method_descriptor_handler(control_protocol_state)); + } + // Set up the node server auto node_server = nmos::experimental::make_node_server(node_model, node_implementation, log_model, gate); // Add the underlying implementation, which will set up the node resources, etc. - node_server.thread_functions.push_back([&] { node_implementation_thread(node_model, gate); }); + node_server.thread_functions.push_back([&] { node_implementation_thread(node_model, control_protocol_state, gate); }); // only implement communication with OCSP server if http_listener supports OCSP stapling // cf. preprocessor conditions in nmos::make_http_listener_config diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index dc3ee9d26..ab1a686ff 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -21,10 +21,15 @@ #include "nmos/colorspace.h" #include "nmos/connection_resources.h" #include "nmos/connection_events_activation.h" +#include "nmos/control_protocol_resources.h" +#include "nmos/control_protocol_resource.h" +#include "nmos/control_protocol_state.h" +#include "nmos/control_protocol_utils.h" #include "nmos/events_resources.h" #include "nmos/format.h" #include "nmos/group_hint.h" #include "nmos/interlace_mode.h" +#include "nmos/is12_versions.h" // for IS-12 gain control #ifdef HAVE_LLDP #include "nmos/lldp_manager.h" #endif @@ -186,7 +191,7 @@ namespace impl } // forward declarations for node_implementation_thread -void node_implementation_init(nmos::node_model& model, slog::base_gate& gate); +void node_implementation_init(nmos::node_model& model, nmos::experimental::control_protocol_state& control_protocol_state, slog::base_gate& gate); void node_implementation_run(nmos::node_model& model, slog::base_gate& gate); nmos::connection_resource_auto_resolver make_node_implementation_auto_resolver(const nmos::settings& settings); nmos::connection_sender_transportfile_setter make_node_implementation_transportfile_setter(const nmos::resources& node_resources, const nmos::settings& settings); @@ -196,13 +201,13 @@ struct node_implementation_init_exception {}; // 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. -void node_implementation_thread(nmos::node_model& model, slog::base_gate& gate_) +void node_implementation_thread(nmos::node_model& model, nmos::experimental::control_protocol_state& control_protocol_state, slog::base_gate& gate_) { nmos::details::omanip_gate gate{ gate_, nmos::stash_category(impl::categories::node_implementation) }; try { - node_implementation_init(model, gate); + node_implementation_init(model, control_protocol_state, gate); node_implementation_run(model, gate); } catch (const node_implementation_init_exception&) @@ -232,7 +237,7 @@ void node_implementation_thread(nmos::node_model& model, slog::base_gate& gate_) } } -void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) +void node_implementation_init(nmos::node_model& model, nmos::experimental::control_protocol_state& control_protocol_state, slog::base_gate& gate) { using web::json::value; using web::json::value_from_elements; @@ -294,6 +299,27 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) return success; }; + // it is important that the model be locked before inserting, updating or deleting a resource + // and that the the node behaviour thread be notified after doing so + const auto insert_root_after = [&model, insert_resource_after](unsigned int milliseconds, nmos::control_protocol_resource& root, slog::base_gate& gate) + { + std::function insert_resources; + + insert_resources = [&milliseconds, insert_resource_after, &insert_resources, &gate](nmos::resources& resources, nmos::control_protocol_resource& resource) + { + for (auto& resource_ : resource.resources) + { + insert_resources(resources, resource_); + if (!insert_resource_after(milliseconds, resources, std::move(resource_), gate)) throw node_implementation_init_exception(); + } + }; + + auto& resources = model.control_protocol_resources; + + insert_resources(resources, root); + if (!insert_resource_after(milliseconds, resources, std::move(root), gate)) throw node_implementation_init_exception(); + }; + const auto resolve_auto = make_node_implementation_auto_resolver(model.settings); const auto set_transportfile = make_node_implementation_transportfile_setter(model.node_resources, model.settings); @@ -894,6 +920,374 @@ void node_implementation_init(nmos::node_model& model, slog::base_gate& gate) auto channelmapping_output = nmos::make_channelmapping_output(id, name, description, source_id, channel_labels, routable_inputs); if (!insert_resource_after(delay_millis, model.channelmapping_resources, std::move(channelmapping_output), gate)) throw node_implementation_init_exception(); } + + // examples of using IS-12 control protocol + // they are based on the NC-DEVICE-MOCK + // See https://specs.amwa.tv/nmos-device-control-mock/#about-nc-device-mock + // See https://github.com/AMWA-TV/nmos-device-control-mock/blob/main/code/src/NCModel/Features.ts + if (0 <= nmos::fields::control_protocol_ws_port(model.settings)) + { + // example to create a non-standard Gain control class + const auto gain_control_class_id = nmos::make_nc_class_id(nmos::nc_worker_class_id, 0, { 1 }); + const web::json::field_as_number gain_value{ U("gainValue") }; + { + // Gain control class property descriptors + std::vector gain_control_property_descriptors = { nmos::experimental::make_control_class_property_descriptor(U("Gain value"), { 3, 1 }, gain_value, U("NcFloat32")) }; + + // create Gain control class descriptor + auto gain_control_class_descriptor = nmos::experimental::make_control_class_descriptor(U("Gain control class descriptor"), gain_control_class_id, U("GainControl"), gain_control_property_descriptors); + + // insert Gain control class descriptor to global state, which will be used by the control_protocol_ws_message_handler to process incoming ws message + control_protocol_state.insert(gain_control_class_descriptor); + } + // helper function to create Gain control instance + auto make_gain_control = [&gain_value, &gain_control_class_id](nmos::nc_oid oid, nmos::nc_oid owner, const utility::string_t& role, const utility::string_t& user_label, const utility::string_t& description, const web::json::value& touchpoints = web::json::value::null(), const web::json::value& runtime_property_constraints = web::json::value::null(), float gain = 0.0) + { + auto data = nmos::details::make_nc_worker(gain_control_class_id, oid, true, owner, role, value::string(user_label), description, touchpoints, runtime_property_constraints, true); + data[gain_value] = value::number(gain); + + return nmos::control_protocol_resource{ nmos::is12_versions::v1_0, nmos::types::nc_worker, std::move(data), true }; + }; + + // example to create a non-standard Example control class + const auto example_control_class_id = nmos::make_nc_class_id(nmos::nc_worker_class_id, 0, { 2 }); + const web::json::field_as_number enum_property{ U("enumProperty") }; + const web::json::field_as_string string_property{ U("stringProperty") }; + const web::json::field_as_number number_property{ U("numberProperty") }; + const web::json::field_as_number deprecated_number_property{ U("deprecatedNumberProperty") }; + const web::json::field_as_bool boolean_property{ U("booleanProperty") }; + const web::json::field_as_value object_property{ U("objectProperty") }; + const web::json::field_as_number method_no_args_count{ U("methodNoArgsCount") }; + const web::json::field_as_number method_simple_args_count{ U("methodSimpleArgsCount") }; + const web::json::field_as_number method_object_arg_count{ U("methodObjectArgCount") }; + const web::json::field_as_array string_sequence{ U("stringSequence") }; + const web::json::field_as_array boolean_sequence{ U("booleanSequence") }; + const web::json::field_as_array enum_sequence{ U("enumSequence") }; + const web::json::field_as_array number_sequence{ U("numberSequence") }; + const web::json::field_as_array object_sequence{ U("objectSequence") }; + const web::json::field_as_number enum_arg{ U("enumArg") }; + const web::json::field_as_string string_arg{ U("stringArg") }; + const web::json::field_as_number number_arg{ U("numberArg") }; + const web::json::field_as_bool boolean_arg{ U("booleanArg") }; + const web::json::field_as_value obj_arg{ U("objArg") }; + enum example_enum + { + Undefined = 0, + Alpha = 1, + Beta = 2, + Gamma = 3 + }; + { + // following constraints are used for the example control class level 0 datatype, level 1 property constraints and the method parameters constraints + auto make_string_example_argument_constraints = []() {return nmos::details::make_nc_parameter_constraints_string(10, U("^[a-z]+$")); }; + auto make_number_example_argument_constraints = []() {return nmos::details::make_nc_parameter_constraints_number(0, 1000, 1); }; + + // Example control class property descriptors + std::vector example_control_property_descriptors = { + nmos::experimental::make_control_class_property_descriptor(U("Example enum property"), { 3, 1 }, enum_property, U("ExampleEnum")), + // create "Example string property" with level 1: property constraints, See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Constraints.html + // use nmos::details::make_nc_parameter_constraints_string to create property constraints + nmos::experimental::make_control_class_property_descriptor(U("Example string property"), { 3, 2 }, string_property, U("NcString"), false, false, false, false, make_string_example_argument_constraints()), + // create "Example numeric property" with level 1: property constraints, See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Constraints.html + // use nmos::details::make_nc_parameter_constraints_number to create property constraints + nmos::experimental::make_control_class_property_descriptor(U("Example numeric property"), { 3, 3 }, number_property, U("NcUint64"), false, false, false, false, make_number_example_argument_constraints()), + nmos::experimental::make_control_class_property_descriptor(U("Example deprecated numeric property"), { 3, 4 }, deprecated_number_property, U("NcUint64"), false, false, false, true, make_number_example_argument_constraints()), + nmos::experimental::make_control_class_property_descriptor(U("Example boolean property"), { 3, 5 }, boolean_property, U("NcBoolean")), + nmos::experimental::make_control_class_property_descriptor(U("Example object property"), { 3, 6 }, object_property, U("ExampleDataType")), + nmos::experimental::make_control_class_property_descriptor(U("Example method no args invoke counter"), { 3, 7 }, method_no_args_count, U("NcUint64"), true), + nmos::experimental::make_control_class_property_descriptor(U("Example method simple args invoke counter"), { 3, 8 }, method_simple_args_count, U("NcUint64"), true), + nmos::experimental::make_control_class_property_descriptor(U("Example method obj arg invoke counter"), { 3, 9 }, method_object_arg_count, U("NcUint64"), true), + // create "Example sequence string property" with level 1: property constraints, See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Constraints.html + // use nmos::details::make_nc_parameter_constraints_string to create sequence property constraints + nmos::experimental::make_control_class_property_descriptor(U("Example string sequence property"), { 3, 10 }, string_sequence, U("NcString"), false, false, true, false, make_string_example_argument_constraints()), + nmos::experimental::make_control_class_property_descriptor(U("Example boolean sequence property"), { 3, 11 }, boolean_sequence, U("NcBoolean"), false, false, true), + nmos::experimental::make_control_class_property_descriptor(U("Example enum sequence property"), { 3, 12 }, enum_sequence, U("ExampleEnum"), false, false, true), + // create "Example sequence numeric property" with level 1: property constraints, See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Constraints.html + // use nmos::details::make_nc_parameter_constraints_number to create sequence property constraints + nmos::experimental::make_control_class_property_descriptor(U("Example number sequence property"), { 3, 13 }, number_sequence, U("NcUint64"), false, false, true, false, make_number_example_argument_constraints()), + nmos::experimental::make_control_class_property_descriptor(U("Example object sequence property"), { 3, 14 }, object_sequence, U("ExampleDataType"), false, false, true) + }; + + auto example_method_with_no_args = [](nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, slog::base_gate& gate) + { + // note, model mutex is already locked by the outer function, so access to control_protocol_resources is OK... + + slog::log(gate, SLOG_FLF) << "Executing the example method with no arguments"; + + return nmos::make_control_protocol_message_response(handle, { is_deprecated ? nmos::nc_method_status::method_deprecated : nmos::nc_method_status::ok }); + }; + auto example_method_with_simple_args = [](nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, slog::base_gate& gate) + { + // note, model mutex is already locked by the outer function, so access to control_protocol_resources is OK... + // and the method parameters constriants has already been validated by the outer function + + slog::log(gate, SLOG_FLF) << "Executing the example method with simple arguments: " << arguments.serialize(); + + return nmos::make_control_protocol_message_response(handle, { is_deprecated ? nmos::nc_method_status::method_deprecated : nmos::nc_method_status::ok }); + }; + auto example_method_with_object_args = [](nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, slog::base_gate& gate) + { + // note, model mutex is already locked by the outer function, so access to control_protocol_resources is OK... + // and the method parameters constriants has already been validated by the outer function + + slog::log(gate, SLOG_FLF) << "Executing the example method with object argument: " << arguments.serialize(); + + return nmos::make_control_protocol_message_response(handle, { is_deprecated ? nmos::nc_method_status::method_deprecated : nmos::nc_method_status::ok }); + }; + // Example control class method descriptors + std::vector example_control_method_descriptors = + { + { nmos::experimental::make_control_class_method_descriptor(U("Example method with no arguments"), { 3, 1 }, U("MethodNoArgs"), U("NcMethodResult"), {}, false, example_method_with_no_args) }, + { nmos::experimental::make_control_class_method_descriptor(U("Example deprecated method with no arguments"), { 3, 2 }, U("MethodNoArgs"), U("NcMethodResult"), {}, true, example_method_with_no_args) }, + { nmos::experimental::make_control_class_method_descriptor(U("Example method with simple arguments"), { 3, 3 }, U("MethodSimpleArgs"), U("NcMethodResult"), + { + nmos::experimental::make_control_class_method_parameter_descriptor(U("Enum example argument"), enum_arg, U("ExampleEnum")), + nmos::experimental::make_control_class_method_parameter_descriptor(U("String example argument"), string_arg, U("NcString"), false, false, make_string_example_argument_constraints()), // e.g. include method property constraints + nmos::experimental::make_control_class_method_parameter_descriptor(U("Number example argument"), number_arg, U("NcUint64"), false, false, make_number_example_argument_constraints()), // e.g. include method property constraints + nmos::experimental::make_control_class_method_parameter_descriptor(U("Boolean example argument"), boolean_arg, U("NcBoolean")) + }, + false, example_method_with_simple_args) + }, + { nmos::experimental::make_control_class_method_descriptor(U("Example method with object argument"), { 3, 4 }, U("MethodObjectArg"), U("NcMethodResult"), + { + nmos::experimental::make_control_class_method_parameter_descriptor(U("Object example argument"), obj_arg, U("ExampleDataType")) + }, + false, example_method_with_object_args) + } + }; + + // create Example control class descriptor + auto example_control_class_descriptor = nmos::experimental::make_control_class_descriptor(U("Example control class descriptor"), example_control_class_id, U("ExampleControl"), example_control_property_descriptors, example_control_method_descriptors); + + // insert Example control class descriptor to global state, which will be used by the control_protocol_ws_message_handler to process incoming ws message + control_protocol_state.insert(example_control_class_descriptor); + + // create/insert Example datatypes to global state, which will be used by the control_protocol_ws_message_handler to process incoming ws message + auto make_example_enum_datatype = [&]() + { + using web::json::value; + + auto items = value::array(); + web::json::push_back(items, nmos::details::make_nc_enum_item_descriptor(U("Undefined"), U("Undefined"), example_enum::Undefined)); + web::json::push_back(items, nmos::details::make_nc_enum_item_descriptor(U("Alpha"), U("Alpha"), example_enum::Alpha)); + web::json::push_back(items, nmos::details::make_nc_enum_item_descriptor(U("Beta"), U("Beta"), example_enum::Beta)); + web::json::push_back(items, nmos::details::make_nc_enum_item_descriptor(U("Gamma"), U("Gamma"), example_enum::Gamma)); + return nmos::details::make_nc_datatype_descriptor_enum(U("Example enum datatype"), U("ExampleEnum"), items, value::null()); + }; + auto make_example_datatype_datatype = [&]() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("Enum property example"), enum_property, U("ExampleEnum"), false, false, value::null())); + { + // level 0: datatype constraints, See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Constraints.html + // use nmos::details::make_nc_parameter_constraints_string to create datatype constraints + value datatype_constraints = make_string_example_argument_constraints(); + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("String property example"), string_property, U("NcString"), false, false, datatype_constraints)); + } + { + // level 0: datatype constraints, See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Constraints.html + // use nmos::details::make_nc_parameter_constraints_number to create datatype constraints + value datatype_constraints = make_number_example_argument_constraints(); + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("Number property example"), number_property, U("NcUint64"), false, false, datatype_constraints)); + } + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("Boolean property example"), boolean_property, U("NcBoolean"), false, false, value::null())); + return nmos::details::make_nc_datatype_descriptor_struct(U("Example data type"), U("ExampleDataType"), fields, value::null()); + }; + control_protocol_state.insert(nmos::experimental::datatype_descriptor{ make_example_enum_datatype() }); + control_protocol_state.insert(nmos::experimental::datatype_descriptor{ make_example_datatype_datatype() }); + } + // helper function to create Example datatype + auto make_example_datatype = [&](example_enum enum_property_, const utility::string_t& string_property_, uint64_t number_property_, bool boolean_property_) + { + using web::json::value_of; + + return value_of({ + { enum_property, enum_property_ }, + { string_property, string_property_ }, + { number_property, number_property_ }, + { boolean_property, boolean_property_ } + }); + }; + // helper function to create Example control instance + auto make_example_control = [&](nmos::nc_oid oid, nmos::nc_oid owner, const utility::string_t& role, const utility::string_t& user_label, const utility::string_t& description, const value& touchpoints = value::null(), + const value& runtime_property_constraints = value::null(), // level 2: runtime constraints. See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Constraints.html + // use of make_nc_property_constraints_string and make_nc_property_constraints_number to create runtime constraints + example_enum enum_property_ = example_enum::Undefined, + const utility::string_t& string_property_ = U(""), + uint64_t number_property_ = 0, + uint64_t deprecated_number_property_ = 0, + bool boolean_property_ = true, + const value& object_property_ = value::null(), + uint64_t method_no_args_count_ = 0, + uint64_t method_simple_args_count_ = 0, + uint64_t method_object_arg_count_ = 0, + std::vector string_sequence_ = {}, + std::vector boolean_sequence_ = {}, + std::vector enum_sequence_ = {}, + std::vector number_sequence_ = {}, + std::vector object_sequence_ = {}) + { + auto data = nmos::details::make_nc_worker(example_control_class_id, oid, true, owner, role, value::string(user_label), description, touchpoints, runtime_property_constraints, true); + data[enum_property] = value::number(enum_property_); + data[string_property] = value::string(string_property_); + data[number_property] = value::number(number_property_); + data[deprecated_number_property] = value::number(deprecated_number_property_); + data[boolean_property] = value::boolean(boolean_property_); + data[object_property] = object_property_; + data[method_no_args_count] = value::number(method_no_args_count_); + data[method_simple_args_count] = value::number(method_simple_args_count_); + data[method_object_arg_count] = value::number(method_object_arg_count_); + { + value sequence; + for (const auto& value_ : string_sequence_) { web::json::push_back(sequence, value::string(value_)); } + data[string_sequence] = sequence; + } + { + value sequence; + for (const auto& value_ : boolean_sequence_) { web::json::push_back(sequence, value::boolean(value_)); } + data[boolean_sequence] = sequence; + } + { + value sequence; + for (const auto& value_ : enum_sequence_) { web::json::push_back(sequence, value_); } + data[enum_sequence] = sequence; + } + { + value sequence; + for (const auto& value_ : number_sequence_) { web::json::push_back(sequence, value_); } + data[number_sequence] = sequence; + } + { + value sequence; + for (const auto& value_ : object_sequence_) { web::json::push_back(sequence, value_); } + data[object_sequence] = sequence; + } + + return nmos::control_protocol_resource{ nmos::is12_versions::v1_0, nmos::types::nc_worker, std::move(data), true }; + }; + + // example to create a non-standard Temperature Sensor control class + const auto temperature_sensor_control_class_id = nmos::make_nc_class_id(nmos::nc_worker_class_id, 0, { 3 }); + const web::json::field_as_number temperature{ U("temperature") }; + const web::json::field_as_string unit{ U("uint") }; + { + // Temperature Sensor control class property descriptors + std::vector temperature_sensor_property_descriptors = { + nmos::experimental::make_control_class_property_descriptor(U("Temperature"), { 3, 1 }, temperature, U("NcFloat32"), true), + nmos::experimental::make_control_class_property_descriptor(U("Unit"), { 3, 2 }, unit, U("NcString"), true) + }; + + // create Temperature Sensor control class descriptor + auto temperature_sensor_control_class_descriptor = nmos::experimental::make_control_class_descriptor(U("Temperature Sensor control class descriptor"), temperature_sensor_control_class_id, U("TemperatureSensor"), temperature_sensor_property_descriptors); + + // insert Temperature Sensor control class descriptor to global state, which will be used by the control_protocol_ws_message_handler to process incoming ws message + control_protocol_state.insert(temperature_sensor_control_class_descriptor); + } + // helper function to create Temperature Sensor control instance + auto make_temperature_sensor = [&temperature, &unit, temperature_sensor_control_class_id](nmos::nc_oid oid, nmos::nc_oid owner, const utility::string_t& role, const utility::string_t& user_label, const utility::string_t& description, const web::json::value& touchpoints = web::json::value::null(), const web::json::value& runtime_property_constraints = web::json::value::null(), float temperature_ = 0.0, const utility::string_t& unit_ = U("Celsius")) + { + auto data = nmos::details::make_nc_worker(temperature_sensor_control_class_id, oid, true, owner, role, value::string(user_label), description, touchpoints, runtime_property_constraints, true); + data[temperature] = value::number(temperature_); + data[unit] = value::string(unit_); + + return nmos::control_protocol_resource{ nmos::is12_versions::v1_0, nmos::types::nc_worker, std::move(data), true }; + }; + + // example root block + auto root_block = nmos::make_root_block(); + + nmos::nc_oid oid = nmos::root_block_oid; + + // example device manager + auto device_manager = nmos::make_device_manager(++oid, model.settings); + + // example class manager + auto class_manager = nmos::make_class_manager(++oid, control_protocol_state); + + // example stereo gain + const auto stereo_gain_oid = ++oid; + auto stereo_gain = nmos::make_block(stereo_gain_oid, nmos::root_block_oid, U("stereo-gain"), U("Stereo gain"), U("Stereo gain block")); + + // example channel gain + const auto channel_gain_oid = ++oid; + auto channel_gain = nmos::make_block(channel_gain_oid, stereo_gain_oid, U("channel-gain"), U("Channel gain"), U("Channel gain block")); + // example left/right gains + auto left_gain = make_gain_control(++oid, channel_gain_oid, U("left-gain"), U("Left gain"), U("Left channel gain")); + auto right_gain = make_gain_control(++oid, channel_gain_oid, U("right-gain"), U("Right gain"), U("Right channel gain")); + // add left-gain and right-gain to channel gain + nmos::push_back(channel_gain, left_gain); + nmos::push_back(channel_gain, right_gain); + + // example master-gain + auto master_gain = make_gain_control(++oid, channel_gain_oid, U("master-gain"), U("Master gain"), U("Master gain block")); + // add channel-gain and master-gain to stereo-gain + nmos::push_back(stereo_gain, channel_gain); + nmos::push_back(stereo_gain, master_gain); + + // example example-control + auto example_control = make_example_control(++oid, nmos::root_block_oid, U("ExampleControl"), U("Example control worker"), U("Example control worker"), + value::null(), + // specify the level 2: runtime constraints, see https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Constraints.html + // use of make_nc_property_constraints_string and make_nc_property_constraints_number to create runtime constraints + value_of({ + { nmos::details::make_nc_property_constraints_string({3, 2}, 5, U("^[a-z]+$")) }, + { nmos::details::make_nc_property_constraints_number({3, 3}, 10, 100, 2) } + }), + example_enum::Undefined, + U("test"), + 30, + 10, + false, + make_example_datatype(example_enum::Undefined, U("default"), 5, false), + 0, + 0, + 0, + { U("red"), U("blue"), U("green") }, + { true, false }, + { example_enum::Alpha, example_enum::Gamma }, + { 0, 50, 80 }, + { make_example_datatype(example_enum::Alpha, U("example"), 50, false), make_example_datatype(example_enum::Gamma, U("different"), 75, true) } + ); + + // example receiver-monitor(s) + { + int count = 0; + for (int index = 0; index < how_many; ++index) + { + for (const auto& port : rtp_receiver_ports) + { + const auto receiver_id = impl::make_id(seed_id, nmos::types::receiver, port, index); + + utility::stringstream_t role; + role << U("monitor-") << ++count; + const auto& receiver = nmos::find_resource(model.node_resources, receiver_id); + const auto receiver_monitor = nmos::make_receiver_monitor(++oid, true, nmos::root_block_oid, role.str(), nmos::fields::label(receiver->data), nmos::fields::description(receiver->data), value_of({ { nmos::details::make_nc_touchpoint_nmos({nmos::ncp_nmos_resource_types::receiver, receiver_id}) } })); + + // add receiver-monitor to root-block + nmos::push_back(root_block, receiver_monitor); + } + } + } + + // example temperature-sensor + const auto temperature_sensor = make_temperature_sensor(++oid, nmos::root_block_oid, U("temperature-sensor"), U("Temperature Sensor"), U("Temperature Sensor block")); + + // add temperature-sensor to root-block + nmos::push_back(root_block, temperature_sensor); + // add example-control to root-block + nmos::push_back(root_block, example_control); + // add stereo-gain to root-block + nmos::push_back(root_block, stereo_gain); + // add class-manager to root-block + nmos::push_back(root_block, class_manager); + // add device-manager to root-block + nmos::push_back(root_block, device_manager); + + // insert control protocol resources to model + insert_root_after(delay_millis, root_block, gate); + } } void node_implementation_run(nmos::node_model& model, slog::base_gate& gate) @@ -911,6 +1305,7 @@ void node_implementation_run(nmos::node_model& model, slog::base_gate& gate) std::shared_ptr events_engine(new std::default_random_engine(events_seeder)); auto cancellation_source = pplx::cancellation_token_source(); + auto token = cancellation_source.get_token(); auto events = pplx::do_while([&model, seed_id, how_many, ws_sender_ports, events_engine, &gate, token] { @@ -956,6 +1351,33 @@ void node_implementation_run(nmos::node_model& model, slog::base_gate& gate) } } + // update temperature sensor + { + const auto temperature_sensor_control_class_id = nmos::make_nc_class_id(nmos::nc_worker_class_id, 0, { 3 }); + const web::json::field_as_number temperature{ U("temperature") }; + + auto& resources = model.control_protocol_resources; + + auto found = nmos::find_resource_if(resources, nmos::types::nc_worker, [&temperature_sensor_control_class_id](const nmos::resource& resource) + { + return temperature_sensor_control_class_id == nmos::details::parse_nc_class_id(nmos::fields::nc::class_id(resource.data)); + }); + + if (resources.end() != found) + { + const auto property_changed_event = nmos::make_property_changed_event(nmos::fields::nc::oid(found->data), + { + { {3, 1}, nmos::nc_property_change_type::type::value_changed, web::json::value(temp.scaled_value()) } + }); + + nmos::modify_control_protocol_resource(model.control_protocol_resources, found->id, [&](nmos::resource& resource) + { + resource.data[temperature] = temp.scaled_value(); + + }, property_changed_event); + } + } + slog::log(gate, SLOG_FLF) << "Temperature updated: " << temp.scaled_value() << " (" << impl::temperature_Celsius.name << ")"; model.notify(); @@ -970,6 +1392,7 @@ void node_implementation_run(nmos::node_model& model, slog::base_gate& gate) cancellation_source.cancel(); // wait without the lock since it is also used by the background tasks nmos::details::reverse_lock_guard unlock{ lock }; + events.wait(); } @@ -1241,13 +1664,17 @@ nmos::connection_activation_handler make_node_implementation_connection_activati auto handle_events_ws_message = make_node_implementation_events_ws_message_handler(model, gate); auto handle_close = nmos::experimental::make_events_ws_close_handler(model, gate); auto connection_events_activation_handler = nmos::make_connection_events_websocket_activation_handler(handle_load_ca_certificates, handle_events_ws_message, handle_close, model.settings, gate); + // this example uses this callback to update IS-12 Receiver-Monitor connection status + auto receiver_monitor_connection_activation_handler = nmos::make_receiver_monitor_connection_activation_handler(model.control_protocol_resources); - return [connection_events_activation_handler, &gate](const nmos::resource& resource, const nmos::resource& connection_resource) + return [connection_events_activation_handler, receiver_monitor_connection_activation_handler, &gate](const nmos::resource& resource, const nmos::resource& connection_resource) { const std::pair id_type{ resource.id, resource.type }; slog::log(gate, SLOG_FLF) << nmos::stash_category(impl::categories::node_implementation) << "Activating " << id_type; connection_events_activation_handler(resource, connection_resource); + + receiver_monitor_connection_activation_handler(connection_resource); }; } @@ -1269,6 +1696,24 @@ nmos::channelmapping_activation_handler make_node_implementation_channelmapping_ }; } +// Example Control Protocol WebSocket API property changed callback to perform application-specific operations to complete the property changed +nmos::control_protocol_property_changed_handler make_node_implementation_control_protocol_property_changed_handler(slog::base_gate& gate) +{ + return [&gate](const nmos::resource& resource, const utility::string_t& property_name, int index) + { + if (index >= 0) + { + // sequence property + slog::log(gate, SLOG_FLF) << nmos::stash_category(impl::categories::node_implementation) << "Property: " << property_name << " index " << index << " has value changed to " << resource.data.at(property_name).at(index).serialize(); + } + else + { + // non-sequence property + slog::log(gate, SLOG_FLF) << nmos::stash_category(impl::categories::node_implementation) << "Property: " << property_name << " has value changed to " << resource.data.at(property_name).serialize(); + } + }; +} + namespace impl { nmos::interlace_mode get_interlace_mode(const nmos::settings& settings) @@ -1422,5 +1867,6 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode .on_set_transportfile(make_node_implementation_transportfile_setter(model.node_resources, model.settings)) .on_connection_activated(make_node_implementation_connection_activation_handler(model, gate)) .on_validate_channelmapping_output_map(make_node_implementation_map_validator()) // may be omitted if not required - .on_channelmapping_activated(make_node_implementation_channelmapping_activation_handler(gate)); + .on_channelmapping_activated(make_node_implementation_channelmapping_activation_handler(gate)) + .on_control_protocol_property_changed(make_node_implementation_control_protocol_property_changed_handler(gate)); // may be omitted if IS-12 not required } diff --git a/Development/nmos-cpp-node/node_implementation.h b/Development/nmos-cpp-node/node_implementation.h index 421769f38..3c6b295c3 100644 --- a/Development/nmos-cpp-node/node_implementation.h +++ b/Development/nmos-cpp-node/node_implementation.h @@ -13,13 +13,14 @@ namespace nmos namespace experimental { struct node_implementation; + struct control_protocol_state; } } // 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. -void node_implementation_thread(nmos::node_model& model, slog::base_gate& gate); +void node_implementation_thread(nmos::node_model& model, nmos::experimental::control_protocol_state& control_protocol_state, slog::base_gate& gate); // This constructs all the callbacks used to integrate the example device-specific underlying implementation // into the server instance for the NMOS Node. diff --git a/Development/nmos/api_utils.cpp b/Development/nmos/api_utils.cpp index 36602b626..899d759d6 100644 --- a/Development/nmos/api_utils.cpp +++ b/Development/nmos/api_utils.cpp @@ -162,7 +162,15 @@ namespace nmos { U("receivers"), nmos::types::receiver }, { U("subscriptions"), nmos::types::subscription }, { U("inputs"), nmos::types::input }, - { U("outputs"), nmos::types::output } + { U("outputs"), nmos::types::output }, + { U("nc_block"), nmos::types::nc_block }, + { U("nc_worker"), nmos::types::nc_worker }, + { U("nc_manager"), nmos::types::nc_manager }, + { U("nc_device_manager"), nmos::types::nc_device_manager }, + { U("nc_class_manager"), nmos::types::nc_class_manager }, + { U("nc_receiver_monitor"), nmos::types::nc_receiver_monitor }, + { U("nc_receiver_monitor_protected"), nmos::types::nc_receiver_monitor_protected }, + { U("nc_ident_beacon"), nmos::types::nc_ident_beacon } }; return types_from_resourceType.at(resourceType); } @@ -181,7 +189,15 @@ namespace nmos { nmos::types::subscription, U("subscriptions") }, { nmos::types::grain, {} }, // subscription websocket grains aren't exposed via the Query API { nmos::types::input, U("inputs") }, - { nmos::types::output, U("outputs") } + { nmos::types::output, U("outputs") }, + { nmos::types::nc_block, U("nc_block") }, + { nmos::types::nc_worker, U("nc_worker") }, + { nmos::types::nc_manager, U("nc_manager") }, + { nmos::types::nc_device_manager, U("nc_device_manager") }, + { nmos::types::nc_class_manager, U("nc_class_manager") }, + { nmos::types::nc_receiver_monitor, U("nc_receiver_monitor") }, + { nmos::types::nc_receiver_monitor_protected, U("nc_receiver_monitor_protected") }, + { nmos::types::nc_ident_beacon, U("nc_ident_beacon") } }; return resourceTypes_from_type.at(type); } diff --git a/Development/nmos/control_protocol_handlers.cpp b/Development/nmos/control_protocol_handlers.cpp new file mode 100644 index 000000000..a248d62f9 --- /dev/null +++ b/Development/nmos/control_protocol_handlers.cpp @@ -0,0 +1,100 @@ +#include "nmos/control_protocol_handlers.h" + +#include "nmos/control_protocol_resource.h" +#include "nmos/control_protocol_state.h" +#include "nmos/control_protocol_utils.h" +#include "nmos/slog.h" + +namespace nmos +{ + get_control_protocol_class_descriptor_handler make_get_control_protocol_class_descriptor_handler(nmos::experimental::control_protocol_state& control_protocol_state) + { + return [&](const nc_class_id& class_id) + { + auto lock = control_protocol_state.read_lock(); + + auto& control_class_descriptors = control_protocol_state.control_class_descriptors; + auto found = control_class_descriptors.find(class_id); + if (control_class_descriptors.end() != found) + { + return found->second; + } + return nmos::experimental::control_class_descriptor{}; + }; + } + + get_control_protocol_datatype_descriptor_handler make_get_control_protocol_datatype_descriptor_handler(nmos::experimental::control_protocol_state& control_protocol_state) + { + return [&](const nmos::nc_name& name) + { + auto lock = control_protocol_state.read_lock(); + + auto found = control_protocol_state.datatype_descriptors.find(name); + if (control_protocol_state.datatype_descriptors.end() != found) + { + return found->second; + } + return nmos::experimental::datatype_descriptor{}; + }; + } + + get_control_protocol_method_descriptor_handler make_get_control_protocol_method_descriptor_handler(experimental::control_protocol_state& control_protocol_state) + { + return [&](const nc_class_id& class_id_, const nc_method_id& method_id) + { + auto class_id = class_id_; + + auto get_control_protocol_class_descriptor = make_get_control_protocol_class_descriptor_handler(control_protocol_state); + + auto lock = control_protocol_state.read_lock(); + + while (!class_id.empty()) + { + const auto& control_class_descriptor = get_control_protocol_class_descriptor(class_id); + + auto& method_descriptors = control_class_descriptor.method_descriptors; + auto found = std::find_if(method_descriptors.begin(), method_descriptors.end(), [&method_id](const experimental::method& method) + { + return method_id == details::parse_nc_method_id(nmos::fields::nc::id(std::get<0>(method))); + }); + if (method_descriptors.end() != found) + { + return *found; + } + + class_id.pop_back(); + } + + return experimental::method(); + }; + } + + // Example Receiver-Monitor Connection activation callback to perform application-specific operations to complete activation + control_protocol_connection_activation_handler make_receiver_monitor_connection_activation_handler(resources& resources) + { + return [&resources](const resource& connection_resource) + { + auto found = find_control_protocol_resource(resources, nmos::types::nc_receiver_monitor, connection_resource.id); + if (resources.end() != found && nc_receiver_monitor_class_id == details::parse_nc_class_id(nmos::fields::nc::class_id(found->data))) + { + // update receiver-monitor's connectionStatus propertry + + const auto active = nmos::fields::master_enable(nmos::fields::endpoint_active(connection_resource.data)); + const web::json::value value = active ? nc_connection_status::connected : nc_connection_status::disconnected; + + // hmm, maybe updating connectionStatusMessage, payloadStatus, and payloadStatusMessage too + + const auto property_changed_event = make_property_changed_event(nmos::fields::nc::oid(found->data), + { + { nc_receiver_monitor_connection_status_property_id, nc_property_change_type::type::value_changed, value } + }); + + modify_control_protocol_resource(resources, found->id, [&](nmos::resource& resource) + { + resource.data[nmos::fields::nc::connection_status] = value; + + }, property_changed_event); + } + }; + } +} diff --git a/Development/nmos/control_protocol_handlers.h b/Development/nmos/control_protocol_handlers.h new file mode 100644 index 000000000..89c6dd032 --- /dev/null +++ b/Development/nmos/control_protocol_handlers.h @@ -0,0 +1,82 @@ +#ifndef NMOS_CONTROL_PROTOCOL_HANDLERS_H +#define NMOS_CONTROL_PROTOCOL_HANDLERS_H + +#include +#include "nmos/control_protocol_typedefs.h" +#include "nmos/resources.h" + +namespace slog +{ + class base_gate; +} + +namespace nmos +{ + namespace experimental + { + struct control_protocol_state; + struct control_class_descriptor; + struct datatype_descriptor; + } + + // callback to retrieve a specific control protocol class descriptor + // this callback should not throw exceptions + typedef std::function get_control_protocol_class_descriptor_handler; + + // callback to add user control protocol class descriptor + // this callback should not throw exceptions + typedef std::function add_control_protocol_class_descriptor_handler; + + // callback to retrieve a control protocol datatype descriptor + // this callback should not throw exceptions + typedef std::function get_control_protocol_datatype_descriptor_handler; + + // a control_protocol_property_changed_handler is a notification that the specified (IS-12) property has changed + // index is set to -1 for non-sequence property + // this callback should not throw exceptions, as the relevant property will already has been changed and those changes will not be rolled back + typedef std::function control_protocol_property_changed_handler; + + namespace experimental + { + // standard method handler definition + typedef std::function standard_method_handler; + + // non-standard method handler definition + typedef std::function non_standard_method_handler; + + // method definition (NcMethodDescriptor vs method handler) + typedef std::tuple method; + + inline method make_control_class_standard_method(const web::json::value& nc_method_descriptor, standard_method_handler method_handler) + { + return std::make_tuple(nc_method_descriptor, method_handler, nullptr); + } + + inline method make_control_class_non_standard_method(const web::json::value& nc_method_descriptor, non_standard_method_handler method_handler) + { + return std::make_tuple(nc_method_descriptor, nullptr, method_handler); + } + } + + // callback to retrieve a specific method + // this callback should not throw exceptions + typedef std::function get_control_protocol_method_descriptor_handler; + + // construct callback to retrieve a specific control protocol class descriptor + get_control_protocol_class_descriptor_handler make_get_control_protocol_class_descriptor_handler(experimental::control_protocol_state& control_protocol_state); + + // construct callback to retrieve a specific datatype descriptor + get_control_protocol_datatype_descriptor_handler make_get_control_protocol_datatype_descriptor_handler(experimental::control_protocol_state& control_protocol_state); + + // construct callback to retrieve a specific method + get_control_protocol_method_descriptor_handler make_get_control_protocol_method_descriptor_handler(experimental::control_protocol_state& control_protocol_state); + + // a control_protocol_connection_activation_handler is a notification that the active parameters for the specified (IS-05) sender/connection_sender or receiver/connection_receiver have changed + // this callback should not throw exceptions + typedef std::function control_protocol_connection_activation_handler; + + // construct callback for receiver monitor to process connection (de)activation + control_protocol_connection_activation_handler make_receiver_monitor_connection_activation_handler(nmos::resources& resources); +} + +#endif diff --git a/Development/nmos/control_protocol_methods.cpp b/Development/nmos/control_protocol_methods.cpp new file mode 100644 index 000000000..8bdd673c8 --- /dev/null +++ b/Development/nmos/control_protocol_methods.cpp @@ -0,0 +1,628 @@ +#include "nmos/control_protocol_methods.h" + +#include "cpprest/json_utils.h" +#include "nmos/control_protocol_resource.h" +#include "nmos/control_protocol_resources.h" +#include "nmos/control_protocol_state.h" +#include "nmos/control_protocol_utils.h" +#include "nmos/json_fields.h" +#include "nmos/slog.h" + +namespace nmos +{ + // NcObject methods implementation + // Get property value + web::json::value get(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler, slog::base_gate& gate) + { + // note, model mutex is already locked by the outer function, so access to control_protocol_resources is OK... + + const auto& property_id = nmos::fields::nc::id(arguments); + + slog::log(gate, SLOG_FLF) << "Get property: " << property_id.serialize(); + + // find the relevant nc_property_descriptor + const auto& property = find_property_descriptor(details::parse_nc_property_id(property_id), details::parse_nc_class_id(nmos::fields::nc::class_id(resource.data)), get_control_protocol_class_descriptor); + if (!property.is_null()) + { + return make_control_protocol_message_response(handle, { is_deprecated ? nmos::nc_method_status::method_deprecated : nmos::fields::nc::is_deprecated(property) ? nc_method_status::property_deprecated : nc_method_status::ok }, resource.data.at(nmos::fields::nc::name(property))); + } + + // unknown property + utility::stringstream_t ss; + ss << U("unknown property: ") << property_id.serialize() << U(" to do Get"); + return make_control_protocol_error_response(handle, { nc_method_status::property_not_implemented }, ss.str()); + } + + // Set property value + web::json::value set(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, control_protocol_property_changed_handler property_changed, slog::base_gate& gate) + { + // note, model mutex is already locked by the outer function, so access to control_protocol_resources is OK... + + const auto& property_id = nmos::fields::nc::id(arguments); + const auto& val = nmos::fields::nc::value(arguments); + + slog::log(gate, SLOG_FLF) << "Set property: " << property_id.serialize() << " value: " << val.serialize(); + + // find the relevant nc_property_descriptor + const auto property_id_ = details::parse_nc_property_id(property_id); + const auto& property = find_property_descriptor(property_id_, details::parse_nc_class_id(nmos::fields::nc::class_id(resource.data)), get_control_protocol_class_descriptor); + if (!property.is_null()) + { + if (nmos::fields::nc::is_read_only(property)) + { + return make_control_protocol_message_response(handle, { nc_method_status::read_only }); + } + + if ((val.is_null() && !nmos::fields::nc::is_nullable(property)) + || (!val.is_array() && nmos::fields::nc::is_sequence(property)) + || (val.is_array() && !nmos::fields::nc::is_sequence(property))) + { + return make_control_protocol_message_response(handle, { nc_method_status::parameter_error }); + } + + try + { + // do property constraints validation + nmos::details::constraints_validation(val, details::get_runtime_property_constraints(property_id_, resource.data.at(nmos::fields::nc::runtime_property_constraints)), nmos::fields::nc::constraints(property), { details::get_datatype_descriptor(property.at(nmos::fields::nc::type_name), get_control_protocol_datatype_descriptor), get_control_protocol_datatype_descriptor }); + + // update property + modify_control_protocol_resource(resources, resource.id, [&](nmos::resource& resource) + { + resource.data[nmos::fields::nc::name(property)] = val; + + // do notification that the specified property has changed + if (property_changed) + { + property_changed(resource, nmos::fields::nc::name(property), -1); + } + + }, make_property_changed_event(nmos::fields::nc::oid(resource.data), { { property_id_, nc_property_change_type::type::value_changed, val } })); + + return make_control_protocol_message_response(handle, { is_deprecated ? nmos::nc_method_status::method_deprecated : nmos::fields::nc::is_deprecated(property) ? nc_method_status::property_deprecated : nc_method_status::ok }); + } + catch (const nmos::control_protocol_exception& e) + { + slog::log(gate, SLOG_FLF) << "Set property: " << property_id.serialize() << " value: " << val.serialize() << " error: " << e.what(); + + return make_control_protocol_message_response(handle, { nc_method_status::parameter_error }); + } + } + + // unknown property + utility::stringstream_t ss; + ss << U("unknown property: ") << property_id.serialize() << " to do Set"; + return make_control_protocol_error_response(handle, { nc_method_status::property_not_implemented }, ss.str()); + } + + // Get sequence item + web::json::value get_sequence_item(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler, slog::base_gate& gate) + { + // note, model mutex is already locked by the outer function, so access to control_protocol_resources is OK... + + const auto& property_id = nmos::fields::nc::id(arguments); + const auto& index = nmos::fields::nc::index(arguments); + + slog::log(gate, SLOG_FLF) << "Get sequence item: " << property_id.serialize() << " index: " << index; + + // find the relevant nc_property_descriptor + const auto& property = find_property_descriptor(details::parse_nc_property_id(property_id), details::parse_nc_class_id(nmos::fields::nc::class_id(resource.data)), get_control_protocol_class_descriptor); + if (!property.is_null()) + { + const auto& data = resource.data.at(nmos::fields::nc::name(property)); + + if (!nmos::fields::nc::is_sequence(property) || data.is_null() || !data.is_array()) + { + // property is not a sequence + utility::stringstream_t ss; + ss << U("property: ") << property_id.serialize() << U(" is not a sequence to do GetSequenceItem"); + return make_control_protocol_error_response(handle, { nc_method_status::invalid_request }, ss.str()); + } + + if (data.as_array().size() > (size_t)index) + { + return make_control_protocol_message_response(handle, { is_deprecated ? nmos::nc_method_status::method_deprecated : nmos::fields::nc::is_deprecated(property) ? nc_method_status::property_deprecated : nc_method_status::ok }, data.at(index)); + } + + // out of bound + utility::stringstream_t ss; + ss << U("property: ") << property_id.serialize() << U(" is outside the available range to do GetSequenceItem"); + return make_control_protocol_error_response(handle, { nc_method_status::index_out_of_bounds }, ss.str()); + } + + // unknown property + utility::stringstream_t ss; + ss << U("unknown property: ") << property_id.serialize() << U(" to do GetSequenceItem"); + return make_control_protocol_error_response(handle, { nc_method_status::property_not_implemented }, ss.str()); + } + + // Set sequence item + web::json::value set_sequence_item(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, control_protocol_property_changed_handler property_changed, slog::base_gate& gate) + { + // note, model mutex is already locked by the outer function, so access to control_protocol_resources is OK... + + const auto& property_id = nmos::fields::nc::id(arguments); + const auto& index = nmos::fields::nc::index(arguments); + const auto& val = nmos::fields::nc::value(arguments); + + slog::log(gate, SLOG_FLF) << "Set sequence item: " << property_id.serialize() << " index: " << index << " value: " << val.serialize(); + + // find the relevant nc_property_descriptor + const auto property_id_ = details::parse_nc_property_id(property_id); + const auto& property = find_property_descriptor(property_id_, details::parse_nc_class_id(nmos::fields::nc::class_id(resource.data)), get_control_protocol_class_descriptor); + if (!property.is_null()) + { + if (nmos::fields::nc::is_read_only(property)) + { + return make_control_protocol_message_response(handle, { nc_method_status::read_only }); + } + + auto& data = resource.data.at(nmos::fields::nc::name(property)); + + if (!nmos::fields::nc::is_sequence(property) || data.is_null() || !data.is_array()) + { + // property is not a sequence + utility::stringstream_t ss; + ss << U("property: ") << property_id.serialize() << U(" is not a sequence to do SetSequenceItem"); + return make_control_protocol_error_response(handle, { nc_method_status::invalid_request }, ss.str()); + } + + if (data.as_array().size() > (size_t)index) + { + try + { + // do property constraints validation + nmos::details::constraints_validation(val, details::get_runtime_property_constraints(property_id_, resource.data.at(nmos::fields::nc::runtime_property_constraints)), nmos::fields::nc::constraints(property), { details::get_datatype_descriptor(property.at(nmos::fields::nc::type_name), get_control_protocol_datatype_descriptor), get_control_protocol_datatype_descriptor }); + + // update property + modify_control_protocol_resource(resources, resource.id, [&](nmos::resource& resource) + { + resource.data[nmos::fields::nc::name(property)][index] = val; + + // do notification that the specified property has changed + if (property_changed) + { + property_changed(resource, nmos::fields::nc::name(property), index); + } + + }, make_property_changed_event(nmos::fields::nc::oid(resource.data), { { property_id_, nc_property_change_type::type::sequence_item_changed, val, nc_id(index) } })); + + return make_control_protocol_message_response(handle, { is_deprecated ? nmos::nc_method_status::method_deprecated : nmos::fields::nc::is_deprecated(property) ? nc_method_status::property_deprecated : nc_method_status::ok }); + } + catch (const nmos::control_protocol_exception& e) + { + slog::log(gate, SLOG_FLF) << "Set sequence item: " << property_id.serialize() << " index: " << index << " value: " << val.serialize() << " error: " << e.what(); + + return make_control_protocol_message_response(handle, { nc_method_status::parameter_error }); + } + } + + // out of bound + utility::stringstream_t ss; + ss << U("property: ") << property_id.serialize() << U(" is outside the available range to do SetSequenceItem"); + return make_control_protocol_error_response(handle, { nc_method_status::index_out_of_bounds }, ss.str()); + } + + // unknown property + utility::stringstream_t ss; + ss << U("unknown property: ") << property_id.serialize() << U(" to do SetSequenceItem"); + return make_control_protocol_error_response(handle, { nc_method_status::property_not_implemented }, ss.str()); + } + + // Add item to sequence + web::json::value add_sequence_item(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, control_protocol_property_changed_handler property_changed, slog::base_gate& gate) + { + // note, model mutex is already locked by the outer function, so access to control_protocol_resources is OK... + + using web::json::value; + + const auto& property_id = nmos::fields::nc::id(arguments); + const auto& val = nmos::fields::nc::value(arguments); + + slog::log(gate, SLOG_FLF) << "Add sequence item: " << property_id.serialize() << " value: " << val.serialize(); + + // find the relevant nc_property_descriptor + const auto property_id_ = details::parse_nc_property_id(property_id); + const auto& property = find_property_descriptor(property_id_, details::parse_nc_class_id(nmos::fields::nc::class_id(resource.data)), get_control_protocol_class_descriptor); + if (!property.is_null()) + { + if (nmos::fields::nc::is_read_only(property)) + { + return make_control_protocol_message_response(handle, { nc_method_status::read_only }); + } + + if (!nmos::fields::nc::is_sequence(property)) + { + // property is not a sequence + utility::stringstream_t ss; + ss << U("property: ") << property_id.serialize() << U(" is not a sequence to do AddSequenceItem"); + return make_control_protocol_error_response(handle, { nc_method_status::invalid_request }, ss.str()); + } + + auto& data = resource.data.at(nmos::fields::nc::name(property)); + + const nc_id sequence_item_index = data.is_null() ? 0 : nc_id(data.as_array().size()); + + try + { + // do property constraints validation + nmos::details::constraints_validation(val, details::get_runtime_property_constraints(property_id_, resource.data.at(nmos::fields::nc::runtime_property_constraints)), nmos::fields::nc::constraints(property), { details::get_datatype_descriptor(property.at(nmos::fields::nc::type_name), get_control_protocol_datatype_descriptor), get_control_protocol_datatype_descriptor }); + + // update property + modify_control_protocol_resource(resources, resource.id, [&](nmos::resource& resource) + { + auto& sequence = resource.data[nmos::fields::nc::name(property)]; + if (data.is_null()) { sequence = value::array(); } + web::json::push_back(sequence, val); + + // do notification that the specified property has changed + if (property_changed) + { + property_changed(resource, nmos::fields::nc::name(property), (int)sequence.as_array().size()-1); + } + + }, make_property_changed_event(nmos::fields::nc::oid(resource.data), { { property_id_, nc_property_change_type::type::sequence_item_added, val, sequence_item_index } })); + + return make_control_protocol_message_response(handle, { is_deprecated ? nmos::nc_method_status::method_deprecated : nmos::fields::nc::is_deprecated(property) ? nc_method_status::property_deprecated : nc_method_status::ok }, sequence_item_index); + } + catch (const nmos::control_protocol_exception& e) + { + slog::log(gate, SLOG_FLF) << "Add sequence item: " << property_id.serialize() << " value: " << val.serialize() << " error: " << e.what(); + + return make_control_protocol_message_response(handle, { nc_method_status::parameter_error }); + } + } + + // unknown property + utility::stringstream_t ss; + ss << U("unknown property: ") << property_id.serialize() << U(" to do AddSequenceItem"); + return make_control_protocol_error_response(handle, { nc_method_status::property_not_implemented }, ss.str()); + } + + // Delete sequence item + web::json::value remove_sequence_item(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler, slog::base_gate& gate) + { + // note, model mutex is already locked by the outer function, so access to control_protocol_resources is OK... + + const auto& property_id = nmos::fields::nc::id(arguments); + const auto& index = nmos::fields::nc::index(arguments); + + slog::log(gate, SLOG_FLF) << "Remove sequence item: " << property_id.serialize() << " index: " << index; + + // find the relevant nc_property_descriptor + const auto& property = find_property_descriptor(details::parse_nc_property_id(property_id), details::parse_nc_class_id(nmos::fields::nc::class_id(resource.data)), get_control_protocol_class_descriptor); + if (!property.is_null()) + { + const auto& data = resource.data.at(nmos::fields::nc::name(property)); + + if (!nmos::fields::nc::is_sequence(property) || data.is_null() || !data.is_array()) + { + // property is not a sequence + utility::stringstream_t ss; + ss << U("property: ") << property_id.serialize() << U(" is not a sequence to do RemoveSequenceItem"); + return make_control_protocol_error_response(handle, { nc_method_status::invalid_request }, ss.str()); + } + + if (data.as_array().size() > (size_t)index) + { + modify_control_protocol_resource(resources, resource.id, [&](nmos::resource& resource) + { + auto& sequence = resource.data[nmos::fields::nc::name(property)].as_array(); + sequence.erase(index); + + }, make_property_changed_event(nmos::fields::nc::oid(resource.data), { { details::parse_nc_property_id(property_id), nc_property_change_type::type::sequence_item_removed, nc_id(index) } })); + + return make_control_protocol_message_response(handle, { is_deprecated ? nmos::nc_method_status::method_deprecated : nmos::fields::nc::is_deprecated(property) ? nc_method_status::property_deprecated : nc_method_status::ok }); + } + + // out of bound + utility::stringstream_t ss; + ss << U("property: ") << property_id.serialize() << U(" is outside the available range to do RemoveSequenceItem"); + return make_control_protocol_error_response(handle, { nc_method_status::index_out_of_bounds }, ss.str()); + } + + // unknown property + utility::stringstream_t ss; + ss << U("unknown property: ") << property_id.serialize() << U(" to do RemoveSequenceItem"); + return make_control_protocol_error_response(handle, { nc_method_status::property_not_implemented }, ss.str()); + } + + // Get sequence length + web::json::value get_sequence_length(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler, slog::base_gate& gate) + { + // note, model mutex is already locked by the outer function, so access to control_protocol_resources is OK... + + using web::json::value; + + const auto& property_id = nmos::fields::nc::id(arguments); + + slog::log(gate, SLOG_FLF) << "Get sequence length: " << property_id.serialize(); + + // find the relevant nc_property_descriptor + const auto& property = find_property_descriptor(details::parse_nc_property_id(property_id), details::parse_nc_class_id(nmos::fields::nc::class_id(resource.data)), get_control_protocol_class_descriptor); + if (!property.is_null()) + { + if (!nmos::fields::nc::is_sequence(property)) + { + // property is not a sequence + utility::stringstream_t ss; + ss << U("property: ") << property_id.serialize() << U(" is not a sequence to do GetSequenceLength"); + return make_control_protocol_error_response(handle, { nc_method_status::invalid_request }, ss.str()); + } + + const auto& data = resource.data.at(nmos::fields::nc::name(property)); + + if (nmos::fields::nc::is_nullable(property)) + { + // can be null + if (data.is_null()) + { + // null + return make_control_protocol_message_response(handle, { is_deprecated ? nmos::nc_method_status::method_deprecated : nmos::fields::nc::is_deprecated(property) ? nc_method_status::property_deprecated : nc_method_status::ok }, value::null()); + } + } + else + { + // cannot be null + if (data.is_null()) + { + // null + utility::stringstream_t ss; + ss << U("property: ") << property_id.serialize() << " is a null sequence to do GetSequenceLength"; + return make_control_protocol_error_response(handle, { nc_method_status::invalid_request }, ss.str()); + } + } + return make_control_protocol_message_response(handle, { is_deprecated ? nmos::nc_method_status::method_deprecated : nmos::fields::nc::is_deprecated(property) ? nc_method_status::property_deprecated : nc_method_status::ok }, uint32_t(data.as_array().size())); + } + + // unknown property + utility::stringstream_t ss; + ss << U("unknown property: ") << property_id.serialize() << " to do GetSequenceLength"; + return make_control_protocol_error_response(handle, { nc_method_status::property_not_implemented }, ss.str()); + } + + // NcBlock methods implementation + // Gets descriptors of members of the block + web::json::value get_member_descriptors(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler, slog::base_gate& gate) + { + // note, model mutex is already locked by the outer function, so access to control_protocol_resources is OK... + + using web::json::value; + + const auto& recurse = nmos::fields::nc::recurse(arguments); // If recurse is set to true, nested members is to be retrieved + + slog::log(gate, SLOG_FLF) << "Get descriptors of members of the block: " << "recurse: " << recurse; + + auto descriptors = value::array(); + nmos::get_member_descriptors(resources, resource, recurse, descriptors.as_array()); + + return make_control_protocol_message_response(handle, { is_deprecated ? nmos::nc_method_status::method_deprecated : nc_method_status::ok }, descriptors); + } + + // Finds member(s) by path + web::json::value find_members_by_path(nmos::resources& resources, const nmos::resource& resource_, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler, slog::base_gate& gate) + { + // note, model mutex is already locked by the outer function, so access to control_protocol_resources is OK... + + using web::json::value; + + // Relative path to search for (MUST not include the role of the block targeted by oid) + const auto& path = arguments.at(nmos::fields::nc::path); + + slog::log(gate, SLOG_FLF) << "Find member(s) by path: " << "path: " << path.serialize(); + + if (0 == path.size()) + { + // empty path + return make_control_protocol_error_response(handle, { nc_method_status::parameter_error }, U("empty path to do FindMembersByPath")); + } + + auto descriptors = value::array(); + value descriptor; + + nmos::resource resource = resource_; + for (const auto& role : path.as_array()) + { + // look for the role in members + + if (resource.data.has_field(nmos::fields::nc::members)) + { + auto& members = nmos::fields::nc::members(resource.data); + auto member_found = std::find_if(members.begin(), members.end(), [&](const web::json::value& nc_block_member_descriptor) + { + return role.as_string() == nmos::fields::nc::role(nc_block_member_descriptor); + }); + + if (members.end() != member_found) + { + descriptor = *member_found; + + // use oid to look for the next resource + resource = *nmos::find_resource(resources, utility::s2us(std::to_string(nmos::fields::nc::oid(*member_found)))); + } + else + { + // no role + utility::stringstream_t ss; + ss << U("role: ") << role.as_string() << U(" not found to do FindMembersByPath"); + return make_control_protocol_error_response(handle, { nc_method_status::parameter_error }, ss.str()); + } + } + else + { + // no members + return make_control_protocol_error_response(handle, { nc_method_status::parameter_error }, U("no members to do FindMembersByPath")); + } + } + + web::json::push_back(descriptors, descriptor); + return make_control_protocol_message_response(handle, { is_deprecated ? nmos::nc_method_status::method_deprecated : nc_method_status::ok }, descriptors); + } + + // Finds members with given role name or fragment + web::json::value find_members_by_role(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler, slog::base_gate& gate) + { + // note, model mutex is already locked by the outer function, so access to control_protocol_resources is OK... + + using web::json::value; + + const auto& role = nmos::fields::nc::role(arguments); // Role text to search for + const auto& case_sensitive = nmos::fields::nc::case_sensitive(arguments); // Signals if the comparison should be case sensitive + const auto& match_whole_string = nmos::fields::nc::match_whole_string(arguments); // TRUE to only return exact matches + const auto& recurse = nmos::fields::nc::recurse(arguments); // TRUE to search nested blocks + + slog::log(gate, SLOG_FLF) << "Find members with given role name or fragment: " << "role: " << role; + + if (role.empty()) + { + // empty role + return make_control_protocol_error_response(handle, { nc_method_status::parameter_error }, U("empty role to do FindMembersByRole")); + } + + auto descriptors = value::array(); + nmos::find_members_by_role(resources, resource, role, match_whole_string, case_sensitive, recurse, descriptors.as_array()); + + return make_control_protocol_message_response(handle, { is_deprecated ? nmos::nc_method_status::method_deprecated : nc_method_status::ok }, descriptors); + } + + // Finds members with given class id + web::json::value find_members_by_class_id(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler, slog::base_gate& gate) + { + // note, model mutex is already locked by the outer function, so access to control_protocol_resources is OK... + + using web::json::value; + + const auto& class_id = details::parse_nc_class_id(nmos::fields::nc::class_id(arguments)); // Class id to search for + const auto& include_derived = nmos::fields::nc::include_derived(arguments); // If TRUE it will also include derived class descriptors + const auto& recurse = nmos::fields::nc::recurse(arguments); // TRUE to search nested blocks + + slog::log(gate, SLOG_FLF) << "Find members with given class id: " << "class_id: " << nmos::details::make_nc_class_id(class_id).serialize(); + + if (class_id.empty()) + { + // empty class_id + return make_control_protocol_error_response(handle, { nc_method_status::parameter_error }, U("empty classId to do FindMembersByClassId")); + } + + // note, model mutex is already locked by the outer function, so access to control_protocol_resources is OK... + + auto descriptors = value::array(); + nmos::find_members_by_class_id(resources, resource, class_id, include_derived, recurse, descriptors.as_array()); + + return make_control_protocol_message_response(handle, { is_deprecated ? nmos::nc_method_status::method_deprecated : nc_method_status::ok }, descriptors); + } + + // NcClassManager methods implementation + // Get a single class descriptor + web::json::value get_control_class(nmos::resources&, const nmos::resource&, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler, slog::base_gate& gate) + { + using web::json::value; + + const auto& class_id = details::parse_nc_class_id(nmos::fields::nc::class_id(arguments)); // Class id to search for + const auto& include_inherited = nmos::fields::nc::include_inherited(arguments); // If set the descriptor would contain all inherited elements + + slog::log(gate, SLOG_FLF) << "Get a single class descriptor: " << "class_id: " << nmos::details::make_nc_class_id(class_id).serialize(); + + if (class_id.empty()) + { + // empty class_id + return make_control_protocol_error_response(handle, { nc_method_status::parameter_error }, U("empty classId to do GetControlClass")); + } + + // note, model mutex is already locked by the outer function, so access to control_protocol_resources is OK... + + const auto& control_class = get_control_protocol_class_descriptor(class_id); + if (!control_class.class_id.empty()) + { + auto& description = control_class.description; + auto& name = control_class.name; + auto& fixed_role = control_class.fixed_role; + auto property_descriptors = control_class.property_descriptors; + auto method_descriptors = value::array(); + for (const auto& method_descriptor : control_class.method_descriptors) { web::json::push_back(method_descriptors, std::get<0>(method_descriptor)); } + auto event_descriptors = control_class.event_descriptors; + + if (include_inherited) + { + auto inherited_class_id = class_id; + inherited_class_id.pop_back(); + + while (!inherited_class_id.empty()) + { + const auto& inherited_control_class = get_control_protocol_class_descriptor(inherited_class_id); + { + for (const auto& property_descriptor : inherited_control_class.property_descriptors.as_array()) { web::json::push_back(property_descriptors, property_descriptor); } + for (const auto& method_descriptor : inherited_control_class.method_descriptors) { web::json::push_back(method_descriptors, std::get<0>(method_descriptor)); } + for (const auto& event_descriptor : inherited_control_class.event_descriptors.as_array()) { web::json::push_back(event_descriptors, event_descriptor); } + } + inherited_class_id.pop_back(); + } + } + const auto descriptor = fixed_role.is_null() + ? details::make_nc_class_descriptor(description, class_id, name, property_descriptors, method_descriptors, event_descriptors) + : details::make_nc_class_descriptor(description, class_id, name, fixed_role.as_string(), property_descriptors, method_descriptors, event_descriptors); + + return make_control_protocol_message_response(handle, { is_deprecated ? nmos::nc_method_status::method_deprecated : nc_method_status::ok }, descriptor); + } + + return make_control_protocol_error_response(handle, { nc_method_status::parameter_error }, U("classId not found")); + } + + // Get a single datatype descriptor + web::json::value get_datatype(nmos::resources&, const nmos::resource&, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, control_protocol_property_changed_handler, slog::base_gate& gate) + { + // note, model mutex is already locked by the outer function, so access to control_protocol_resources is OK... + + const auto& name = nmos::fields::nc::name(arguments); // name of datatype + const auto& include_inherited = nmos::fields::nc::include_inherited(arguments); // If set the descriptor would contain all inherited elements + + slog::log(gate, SLOG_FLF) << "Get a single datatype descriptor: " << "name: " << name; + + if (name.empty()) + { + // empty name + return make_control_protocol_error_response(handle, { nc_method_status::parameter_error }, U("empty name to do GetDatatype")); + } + + const auto& datatype = get_control_protocol_datatype_descriptor(name); + if (datatype.descriptor.size()) + { + auto descriptor = datatype.descriptor; + + if (include_inherited) + { + const auto& type = nmos::fields::nc::type(descriptor); + if (nc_datatype_type::Struct == type) + { + auto descriptor_ = descriptor; + + for (;;) + { + const auto& parent_type = descriptor_.at(nmos::fields::nc::parent_type); + if (!parent_type.is_null()) + { + const auto& parent_datatype = get_control_protocol_datatype_descriptor(parent_type.as_string()); + if (parent_datatype.descriptor.size()) + { + descriptor_ = parent_datatype.descriptor; + + const auto& fields = nmos::fields::nc::fields(descriptor_); + for (const auto& field : fields) + { + web::json::push_back(descriptor.at(nmos::fields::nc::fields), field); + } + } + } + else + { + break; + } + } + } + } + + return make_control_protocol_message_response(handle, { is_deprecated ? nmos::nc_method_status::method_deprecated : nc_method_status::ok }, descriptor); + } + + return make_control_protocol_error_response(handle, { nc_method_status::parameter_error }, U("name not found")); + } +} diff --git a/Development/nmos/control_protocol_methods.h b/Development/nmos/control_protocol_methods.h new file mode 100644 index 000000000..fe44d77c7 --- /dev/null +++ b/Development/nmos/control_protocol_methods.h @@ -0,0 +1,47 @@ +#ifndef NMOS_CONTROL_PROTOCOL_METHODS_H +#define NMOS_CONTROL_PROTOCOL_METHODS_H + +#include "nmos/control_protocol_handlers.h" +#include "nmos/resources.h" + +namespace slog +{ + class base_gate; +} + +namespace nmos +{ + // NcObject methods implementation + // Get property value + web::json::value get(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler, slog::base_gate& gate); + // Set property value + web::json::value set(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler property_changed, slog::base_gate& gate); + // Get sequence item + web::json::value get_sequence_item(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler, slog::base_gate& gate); + // Set sequence item + web::json::value set_sequence_item(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler property_changed, slog::base_gate& gate); + // Add item to sequence + web::json::value add_sequence_item(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler property_changed, slog::base_gate& gate); + // Delete sequence item + web::json::value remove_sequence_item(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler, slog::base_gate& gate); + // Get sequence length + web::json::value get_sequence_length(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler, slog::base_gate& gate); + + // NcBlock methods implementation + // Get descriptors of members of the block + web::json::value get_member_descriptors(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler, slog::base_gate& gate); + // Finds member(s) by path + web::json::value find_members_by_path(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler, slog::base_gate& gate); + // Finds members with given role name or fragment + web::json::value find_members_by_role(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler, slog::base_gate& gate); + // Finds members with given class id + web::json::value find_members_by_class_id(nmos::resources& resources, const nmos::resource& resource, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler, slog::base_gate& gate); + + // NcClassManager methods implementation + // Get a single class descriptor + web::json::value get_control_class(nmos::resources&, const nmos::resource&, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler, control_protocol_property_changed_handler, slog::base_gate& gate); + // Get a single datatype descriptor + web::json::value get_datatype(nmos::resources&, const nmos::resource&, int32_t handle, const web::json::value& arguments, bool is_deprecated, get_control_protocol_class_descriptor_handler, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype, control_protocol_property_changed_handler, slog::base_gate& gate); +} + +#endif diff --git a/Development/nmos/control_protocol_nmos_channel_mapping_resource_type.h b/Development/nmos/control_protocol_nmos_channel_mapping_resource_type.h new file mode 100644 index 000000000..8e31c96cf --- /dev/null +++ b/Development/nmos/control_protocol_nmos_channel_mapping_resource_type.h @@ -0,0 +1,19 @@ +#ifndef NMOS_CONTROL_PROTOCOL_NMOS_CHANNEL_MAPPING_RESOURCE_TYPE_H +#define NMOS_CONTROL_PROTOCOL_NMOS_CHANNEL_MAPPING_RESOURCE_TYPE_H + +#include "cpprest/basic_utils.h" +#include "nmos/string_enum.h" + +namespace nmos +{ + // resourceType + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nctouchpointresourcenmoschannelmapping + DEFINE_STRING_ENUM(ncp_nmos_channel_mapping_resource_type) + namespace ncp_nmos_channel_mapping_resource_types + { + const ncp_nmos_channel_mapping_resource_type input{ U("input") }; + const ncp_nmos_channel_mapping_resource_type output{ U("output") }; + } +} + +#endif diff --git a/Development/nmos/control_protocol_nmos_resource_type.h b/Development/nmos/control_protocol_nmos_resource_type.h new file mode 100644 index 000000000..436b72257 --- /dev/null +++ b/Development/nmos/control_protocol_nmos_resource_type.h @@ -0,0 +1,23 @@ +#ifndef NMOS_CONTROL_PROTOCOL_NMOS_RESOURCE_TYPE_H +#define NMOS_CONTROL_PROTOCOL_NMOS_RESOURCE_TYPE_H + +#include "cpprest/basic_utils.h" +#include "nmos/string_enum.h" + +namespace nmos +{ + // resourceType + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nctouchpointresourcenmos + DEFINE_STRING_ENUM(ncp_nmos_resource_type) + namespace ncp_nmos_resource_types + { + const ncp_nmos_resource_type node{ U("node") }; + const ncp_nmos_resource_type device{ U("device") }; + const ncp_nmos_resource_type source{ U("source") }; + const ncp_nmos_resource_type flow{ U("flow") }; + const ncp_nmos_resource_type sender{ U("sender") }; + const ncp_nmos_resource_type receiver{ U("receiver") }; + } +} + +#endif diff --git a/Development/nmos/control_protocol_resource.cpp b/Development/nmos/control_protocol_resource.cpp new file mode 100644 index 000000000..890e13631 --- /dev/null +++ b/Development/nmos/control_protocol_resource.cpp @@ -0,0 +1,2076 @@ +#include "nmos/control_protocol_resource.h" + +#include "cpprest/base_uri.h" +#include "nmos/control_protocol_state.h" // for nmos::experimental::control_classes definitions +#include "nmos/json_fields.h" + +namespace nmos +{ + namespace details + { + web::json::value make_nc_method_result(const nc_method_result& method_result) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::nc::status, method_result.status } + }); + } + + web::json::value make_nc_method_result_error(const nc_method_result& method_result, const utility::string_t& error_message) + { + auto result = make_nc_method_result(method_result); + if (!error_message.empty()) { result[nmos::fields::nc::error_message] = web::json::value::string(error_message); } + return result; + } + + web::json::value make_nc_method_result(const nc_method_result& method_result, const web::json::value& value) + { + auto result = make_nc_method_result(method_result); + result[nmos::fields::nc::value] = value; + return result; + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncelementid + web::json::value make_nc_element_id(uint16_t level, uint16_t index) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::nc::level, level }, + { nmos::fields::nc::index, index } + }); + } + web::json::value make_nc_element_id(const nc_element_id& id) + { + return make_nc_element_id(id.level, id.index); + } + nc_element_id parse_nc_element_id(const web::json::value& id) + { + return { uint16_t(nmos::fields::nc::level(id)), uint16_t(nmos::fields::nc::index(id)) }; + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nceventid + web::json::value make_nc_event_id(const nc_event_id& id) + { + return make_nc_element_id(id); + } + nc_event_id parse_nc_event_id(const web::json::value& id) + { + return parse_nc_element_id(id); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncmethodid + web::json::value make_nc_method_id(const nc_method_id& id) + { + return make_nc_element_id(id); + } + nc_method_id parse_nc_method_id(const web::json::value& id) + { + return parse_nc_element_id(id); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncpropertyid + web::json::value make_nc_property_id(const nc_property_id& id) + { + return make_nc_element_id(id); + } + nc_property_id parse_nc_property_id(const web::json::value& id) + { + return parse_nc_element_id(id); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncclassid + web::json::value make_nc_class_id(const nc_class_id& class_id) + { + using web::json::value; + + auto nc_class_id = value::array(); + for (const auto class_id_item : class_id) { web::json::push_back(nc_class_id, class_id_item); } + return nc_class_id; + } + nc_class_id parse_nc_class_id(const web::json::array& class_id_) + { + nc_class_id class_id; + for (auto& element : class_id_) + { + class_id.push_back(element.as_integer()); + } + return class_id; + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncmanufacturer + web::json::value make_nc_manufacturer(const utility::string_t& name, const web::json::value& organization_id, const web::json::value& website) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::nc::name, name }, + { nmos::fields::nc::organization_id, organization_id }, + { nmos::fields::nc::website, website } + }); + } + web::json::value make_nc_manufacturer(const utility::string_t& name, nc_organization_id organization_id, const web::uri& website) + { + using web::json::value; + + return make_nc_manufacturer(name, organization_id, value::string(website.to_string())); + } + web::json::value make_nc_manufacturer(const utility::string_t& name, nc_organization_id organization_id) + { + using web::json::value; + + return make_nc_manufacturer(name, organization_id, value::null()); + } + web::json::value make_nc_manufacturer(const utility::string_t& name) + { + using web::json::value; + + return make_nc_manufacturer(name, value::null(), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncproduct + // brand_name can be null + // uuid can be null + // description can be null + web::json::value make_nc_product(const utility::string_t& name, const utility::string_t& key, const utility::string_t& revision_level, + const web::json::value& brand_name, const web::json::value& uuid, const web::json::value& description) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::nc::name, name }, + { nmos::fields::nc::key, key }, + { nmos::fields::nc::revision_level, revision_level }, + { nmos::fields::nc::brand_name, brand_name }, + { nmos::fields::nc::uuid, uuid }, + { nmos::fields::nc::description, description } + }); + } + web::json::value make_nc_product(const utility::string_t& name, const utility::string_t& key, const utility::string_t& revision_level, + const utility::string_t& brand_name, const nc_uuid& uuid, const utility::string_t& description) + { + using web::json::value; + + return make_nc_product(name, key, revision_level, value::string(brand_name), value::string(uuid), value::string(description)); + } + web::json::value make_nc_product(const utility::string_t& name, const utility::string_t& key, const utility::string_t& revision_level, + const utility::string_t& brand_name, const nc_uuid& uuid) + { + using web::json::value; + + return make_nc_product(name, key, revision_level, value::string(brand_name), value::string(uuid), value::null()); + } + web::json::value make_nc_product(const utility::string_t& name, const utility::string_t& key, const utility::string_t& revision_level, + const utility::string_t& brand_name) + { + using web::json::value; + + return make_nc_product(name, key, revision_level, value::string(brand_name), value::null(), value::null()); + } + web::json::value make_nc_product(const utility::string_t& name, const utility::string_t& key, const utility::string_t& revision_level) + { + using web::json::value; + + return make_nc_product(name, key, revision_level, value::null(), value::null(), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdeviceoperationalstate + // device_specific_details can be null + web::json::value make_nc_device_operational_state(nc_device_generic_state::state generic_state, const web::json::value& device_specific_details) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::nc::generic_state, generic_state }, + { nmos::fields::nc::device_specific_details, device_specific_details } + }); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdescriptor + // description can be null + web::json::value make_nc_descriptor(const web::json::value& description) + { + using web::json::value_of; + + return value_of({ { nmos::fields::nc::description, description } }); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncblockmemberdescriptor + // description can be null + // user_label can be null + web::json::value make_nc_block_member_descriptor(const web::json::value& description, const utility::string_t& role, nc_oid oid, bool constant_oid, const nc_class_id& class_id, const web::json::value& user_label, nc_oid owner) + { + using web::json::value; + + auto data = make_nc_descriptor(description); + data[nmos::fields::nc::role] = value::string(role); + data[nmos::fields::nc::oid] = oid; + data[nmos::fields::nc::constant_oid] = value::boolean(constant_oid); + data[nmos::fields::nc::class_id] = make_nc_class_id(class_id); + data[nmos::fields::nc::user_label] = user_label; + data[nmos::fields::nc::owner] = owner; + + return data; + } + web::json::value make_nc_block_member_descriptor(const utility::string_t& description, const utility::string_t& role, nc_oid oid, bool constant_oid, const nc_class_id& class_id, const utility::string_t& user_label, nc_oid owner) + { + using web::json::value; + + return make_nc_block_member_descriptor(value::string(description), role, oid, constant_oid, class_id, value::string(user_label), owner); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncclassdescriptor + // description can be null + // fixedRole can be null + web::json::value make_nc_class_descriptor(const web::json::value& description, const nc_class_id& class_id, const nc_name& name, const web::json::value& fixed_role, const web::json::value& properties, const web::json::value& methods, const web::json::value& events) + { + using web::json::value; + + auto data = make_nc_descriptor(description); + data[nmos::fields::nc::class_id] = make_nc_class_id(class_id); + data[nmos::fields::nc::name] = value::string(name); + data[nmos::fields::nc::fixed_role] = fixed_role; + data[nmos::fields::nc::properties] = properties; + data[nmos::fields::nc::methods] = methods; + data[nmos::fields::nc::events] = events; + + return data; + } + web::json::value make_nc_class_descriptor(const utility::string_t& description, const nc_class_id& class_id, const nc_name& name, const utility::string_t& fixed_role, const web::json::value& properties, const web::json::value& methods, const web::json::value& events) + { + using web::json::value; + + return make_nc_class_descriptor(value::string(description), class_id, name, value::string(fixed_role), properties, methods, events); + } + web::json::value make_nc_class_descriptor(const utility::string_t& description, const nc_class_id& class_id, const nc_name& name, const web::json::value& properties, const web::json::value& methods, const web::json::value& events) + { + using web::json::value; + + return make_nc_class_descriptor(value::string(description), class_id, name, value::null(), properties, methods, events); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncenumitemdescriptor + // description can be null + web::json::value make_nc_enum_item_descriptor(const web::json::value& description, const nc_name& name, uint16_t val) + { + using web::json::value; + + auto data = make_nc_descriptor(description); + data[nmos::fields::nc::name] = value::string(name); + data[nmos::fields::nc::value] = val; + + return data; + } + web::json::value make_nc_enum_item_descriptor(const utility::string_t& description, const nc_name& name, uint16_t val) + { + using web::json::value; + + return make_nc_enum_item_descriptor(value::string(description), name, val); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nceventdescriptor + // description can be null + // id = make_nc_event_id(level, index) + web::json::value make_nc_event_descriptor(const web::json::value& description, const nc_event_id& id, const nc_name& name, const utility::string_t& event_datatype, bool is_deprecated) + { + using web::json::value; + + auto data = make_nc_descriptor(description); + data[nmos::fields::nc::id] = make_nc_event_id(id); + data[nmos::fields::nc::name] = value::string(name); + data[nmos::fields::nc::event_datatype] = value::string(event_datatype); + data[nmos::fields::nc::is_deprecated] = value::boolean(is_deprecated); + + return data; + } + web::json::value make_nc_event_descriptor(const utility::string_t& description, const nc_event_id& id, const nc_name& name, const utility::string_t& event_datatype, bool is_deprecated) + { + using web::json::value; + + return make_nc_event_descriptor(value::string(description), id, name, event_datatype, is_deprecated); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncfielddescriptor + // description can be null + // type_name can be null + // constraints can be null + web::json::value make_nc_field_descriptor(const web::json::value& description, const nc_name& name, const web::json::value& type_name, bool is_nullable, bool is_sequence, const web::json::value& constraints) + { + using web::json::value; + + auto data = make_nc_descriptor(description); + data[nmos::fields::nc::name] = value::string(name); + data[nmos::fields::nc::type_name] = type_name; + data[nmos::fields::nc::is_nullable] = value::boolean(is_nullable); + data[nmos::fields::nc::is_sequence] = value::boolean(is_sequence); + data[nmos::fields::nc::constraints] = constraints; + + return data; + } + web::json::value make_nc_field_descriptor(const utility::string_t& description, const nc_name& name, const utility::string_t& type_name, bool is_nullable, bool is_sequence, const web::json::value& constraints) + { + using web::json::value; + + return make_nc_field_descriptor(value::string(description), name, value::string(type_name), is_nullable, is_sequence, constraints); + } + web::json::value make_nc_field_descriptor(const utility::string_t& description, const nc_name& name, bool is_nullable, bool is_sequence, const web::json::value& constraints) + { + using web::json::value; + + return make_nc_field_descriptor(value::string(description), name, value::null(), is_nullable, is_sequence, constraints); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncmethoddescriptor + // description can be null + // id = make_nc_method_id(level, index) + // sequence parameters + web::json::value make_nc_method_descriptor(const web::json::value& description, const nc_method_id& id, const nc_name& name, const utility::string_t& result_datatype, const web::json::value& parameters, bool is_deprecated) + { + using web::json::value; + + auto data = make_nc_descriptor(description); + data[nmos::fields::nc::id] = make_nc_method_id(id); + data[nmos::fields::nc::name] = value::string(name); + data[nmos::fields::nc::result_datatype] = value::string(result_datatype); + data[nmos::fields::nc::parameters] = parameters; + data[nmos::fields::nc::is_deprecated] = value::boolean(is_deprecated); + + return data; + } + web::json::value make_nc_method_descriptor(const utility::string_t& description, const nc_method_id& id, const nc_name& name, const utility::string_t& result_datatype, const web::json::value& parameters, bool is_deprecated) + { + using web::json::value; + + return make_nc_method_descriptor(value::string(description), id, name, result_datatype, parameters, is_deprecated); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncparameterdescriptor + // description can be null + // type_name can be null + web::json::value make_nc_parameter_descriptor(const web::json::value& description, const nc_name& name, const web::json::value& type_name, bool is_nullable, bool is_sequence, const web::json::value& constraints) + { + using web::json::value; + + auto data = make_nc_descriptor(description); + data[nmos::fields::nc::name] = value::string(name); + data[nmos::fields::nc::type_name] = type_name; + data[nmos::fields::nc::is_nullable] = value::boolean(is_nullable); + data[nmos::fields::nc::is_sequence] = value::boolean(is_sequence); + data[nmos::fields::nc::constraints] = constraints; + + return data; + } + web::json::value make_nc_parameter_descriptor(const utility::string_t& description, const nc_name& name, bool is_nullable, bool is_sequence, const web::json::value& constraints) + { + using web::json::value; + + return make_nc_parameter_descriptor(value::string(description), name, value::null(), is_nullable, is_sequence, constraints); + } + web::json::value make_nc_parameter_descriptor(const utility::string_t& description, const nc_name& name, const utility::string_t& type_name, bool is_nullable, bool is_sequence, const web::json::value& constraints) + { + using web::json::value; + + return make_nc_parameter_descriptor(value::string(description), name, value::string(type_name), is_nullable, is_sequence, constraints); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncpropertydescriptor + // description can be null + // constraints can be null + web::json::value make_nc_property_descriptor(const web::json::value& description, const nc_property_id& id, const nc_name& name, const web::json::value& type_name, + bool is_read_only, bool is_nullable, bool is_sequence, bool is_deprecated, const web::json::value& constraints) + { + using web::json::value; + + auto data = make_nc_descriptor(description); + data[nmos::fields::nc::id] = make_nc_property_id(id); + data[nmos::fields::nc::name] = value::string(name); + data[nmos::fields::nc::type_name] = type_name; + data[nmos::fields::nc::is_read_only] = value::boolean(is_read_only); + data[nmos::fields::nc::is_nullable] = value::boolean(is_nullable); + data[nmos::fields::nc::is_sequence] = value::boolean(is_sequence); + data[nmos::fields::nc::is_deprecated] = value::boolean(is_deprecated); + data[nmos::fields::nc::constraints] = constraints; + + return data; + } + web::json::value make_nc_property_descriptor(const utility::string_t& description, const nc_property_id& id, const nc_name& name, const utility::string_t& type_name, + bool is_read_only, bool is_nullable, bool is_sequence, bool is_deprecated, const web::json::value& constraints) + { + using web::json::value; + + return nmos::details::make_nc_property_descriptor(value::string(description), id, name, value::string(type_name), is_read_only, is_nullable, is_sequence, is_deprecated, constraints); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdatatypedescriptor + // description can be null + // constraints can be null + web::json::value make_nc_datatype_descriptor(const web::json::value& description, const nc_name& name, nc_datatype_type::type type, const web::json::value& constraints) + { + using web::json::value; + + auto data = make_nc_descriptor(description); + data[nmos::fields::nc::name] = value::string(name); + data[nmos::fields::nc::type] = type; + data[nmos::fields::nc::constraints] = constraints; + + return data; + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdatatypedescriptorenum + // description can be null + // constraints can be null + // items: sequence + web::json::value make_nc_datatype_descriptor_enum(const web::json::value& description, const nc_name& name, const web::json::value& items, const web::json::value& constraints) + { + auto data = make_nc_datatype_descriptor(description, name, nc_datatype_type::Enum, constraints); + data[nmos::fields::nc::items] = items; + + return data; + } + web::json::value make_nc_datatype_descriptor_enum(const utility::string_t& description, const nc_name& name, const web::json::value& items, const web::json::value& constraints) + { + using web::json::value; + + return make_nc_datatype_descriptor_enum(value::string(description), name, items, constraints); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdatatypedescriptorprimitive + // description can be null + // constraints can be null + web::json::value make_nc_datatype_descriptor_primitive(const web::json::value& description, const nc_name& name, const web::json::value& constraints) + { + return make_nc_datatype_descriptor(description, name, nc_datatype_type::Primitive, constraints); + } + web::json::value make_nc_datatype_descriptor_primitive(const utility::string_t& description, const nc_name& name, const web::json::value& constraints) + { + using web::json::value; + + return make_nc_datatype_descriptor_primitive(value::string(description), name, constraints); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdatatypedescriptorstruct + // description can be null + // constraints can be null + // fields: sequence + // parent_type can be null + web::json::value make_nc_datatype_descriptor_struct(const web::json::value& description, const nc_name& name, const web::json::value& fields, const web::json::value& parent_type, const web::json::value& constraints) + { + auto data = make_nc_datatype_descriptor(description, name, nc_datatype_type::Struct, constraints); + data[nmos::fields::nc::fields] = fields; + data[nmos::fields::nc::parent_type] = parent_type; + + return data; + } + web::json::value make_nc_datatype_descriptor_struct(const utility::string_t& description, const nc_name& name, const web::json::value& fields, const utility::string_t& parent_type, const web::json::value& constraints) + { + using web::json::value; + + return make_nc_datatype_descriptor_struct(value::string(description), name, fields, value::string(parent_type), constraints); + } + web::json::value make_nc_datatype_descriptor_struct(const utility::string_t& description, const nc_name& name, const web::json::value& fields, const web::json::value& constraints) + { + using web::json::value; + + return make_nc_datatype_descriptor_struct(value::string(description), name, fields, value::null(), constraints); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdatatypedescriptortypedef + // description can be null + // constraints can be null + web::json::value make_nc_datatype_typedef(const web::json::value& description, const nc_name& name, bool is_sequence, const utility::string_t& parent_type, const web::json::value& constraints) + { + using web::json::value; + + auto data = make_nc_datatype_descriptor(description, name, nc_datatype_type::Typedef, constraints); + data[nmos::fields::nc::parent_type] = value::string(parent_type); + data[nmos::fields::nc::is_sequence] = value::boolean(is_sequence); + + return data; + } + web::json::value make_nc_datatype_typedef(const utility::string_t& description, const nc_name& name, bool is_sequence, const utility::string_t& parent_type, const web::json::value& constraints) + { + using web::json::value; + + return make_nc_datatype_typedef(value::string(description), name, is_sequence, parent_type, constraints); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncpropertyconstraints + web::json::value make_nc_property_constraints(const nc_property_id& property_id, const web::json::value& default_value) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::nc::property_id, make_nc_property_id(property_id) }, + { nmos::fields::nc::default_value, default_value } + }); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncpropertyconstraintsnumber + web::json::value make_nc_property_constraints_number(const nc_property_id& property_id, const web::json::value& default_value, const web::json::value& minimum, const web::json::value& maximum, const web::json::value& step) + { + using web::json::value; + + auto data = make_nc_property_constraints(property_id, default_value); + data[nmos::fields::nc::minimum] = minimum; + data[nmos::fields::nc::maximum] = maximum; + data[nmos::fields::nc::step] = step; + + return data; + } + web::json::value make_nc_property_constraints_number(const nc_property_id& property_id, uint64_t default_value, uint64_t minimum, uint64_t maximum, uint64_t step) + { + using web::json::value; + + return make_nc_property_constraints_number(property_id, value(default_value), value(minimum), value(maximum), value(step)); + } + web::json::value make_nc_property_constraints_number(const nc_property_id& property_id, uint64_t minimum, uint64_t maximum, uint64_t step) + { + using web::json::value; + + return make_nc_property_constraints_number(property_id, value::null(), minimum, maximum, step); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncpropertyconstraintsstring + web::json::value make_nc_property_constraints_string(const nc_property_id& property_id, const web::json::value& default_value, const web::json::value& max_characters, const web::json::value& pattern) + { + using web::json::value; + + auto data = make_nc_property_constraints(property_id, default_value); + data[nmos::fields::nc::max_characters] = max_characters; + data[nmos::fields::nc::pattern] = pattern; + + return data; + } + web::json::value make_nc_property_constraints_string(const nc_property_id& property_id, const utility::string_t& default_value, uint32_t max_characters, const nc_regex& pattern) + { + using web::json::value; + + return make_nc_property_constraints_string(property_id, value::string(default_value), max_characters, value::string(pattern)); + } + web::json::value make_nc_property_constraints_string(const nc_property_id& property_id, uint32_t max_characters, const nc_regex& pattern) + { + using web::json::value; + + return make_nc_property_constraints_string(property_id, value::null(), max_characters, value::string(pattern)); + } + web::json::value make_nc_property_constraints_string(const nc_property_id& property_id, uint32_t max_characters) + { + using web::json::value; + + return make_nc_property_constraints_string(property_id, value::null(), max_characters, value::null()); + } + web::json::value make_nc_property_constraints_string(const nc_property_id& property_id, const nc_regex& pattern) + { + using web::json::value; + + return make_nc_property_constraints_string(property_id, value::null(), value::null(), value::string(pattern)); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncparameterconstraints + web::json::value make_nc_parameter_constraints(const web::json::value& default_value) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::nc::default_value, default_value } + }); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncparameterconstraintsnumber + web::json::value make_nc_parameter_constraints_number(const web::json::value& default_value, const web::json::value& minimum, const web::json::value& maximum, const web::json::value& step) + { + using web::json::value; + + auto data = make_nc_parameter_constraints(default_value); + data[nmos::fields::nc::minimum] = minimum; + data[nmos::fields::nc::maximum] = maximum; + data[nmos::fields::nc::step] = step; + + return data; + } + web::json::value make_nc_parameter_constraints_number(uint64_t default_value, uint64_t minimum, uint64_t maximum, uint64_t step) + { + using web::json::value; + + return make_nc_parameter_constraints_number(value(default_value), value(minimum), value(maximum), value(step)); + } + web::json::value make_nc_parameter_constraints_number(uint64_t minimum, uint64_t maximum, uint64_t step) + { + using web::json::value; + + return make_nc_parameter_constraints_number(value::null(), minimum, maximum, step); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncparameterconstraintsstring + web::json::value make_nc_parameter_constraints_string(const web::json::value& default_value, const web::json::value& max_characters, const web::json::value& pattern) + { + auto data = make_nc_parameter_constraints(default_value); + data[nmos::fields::nc::max_characters] = max_characters; + data[nmos::fields::nc::pattern] = pattern; + + return data; + } + web::json::value make_nc_parameter_constraints_string(const utility::string_t& default_value, uint32_t max_characters, const nc_regex& pattern) + { + using web::json::value; + + return make_nc_parameter_constraints_string(value::string(default_value), max_characters, value::string(pattern)); + } + web::json::value make_nc_parameter_constraints_string(uint32_t max_characters, const nc_regex& pattern) + { + using web::json::value; + + return make_nc_parameter_constraints_string(value::null(), max_characters, value::string(pattern)); + } + web::json::value make_nc_parameter_constraints_string(uint32_t max_characters) + { + using web::json::value; + + return make_nc_parameter_constraints_string(value::null(), max_characters, value::null()); + } + web::json::value make_nc_parameter_constraints_string(const nc_regex& pattern) + { + using web::json::value; + + return make_nc_parameter_constraints_string(value::null(), value::null(), value::string(pattern)); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nctouchpointresource + web::json::value make_nc_touchpoint_resource(const nc_touchpoint_resource& resource) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::nc::resource_type, resource.resource_type } + }); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nctouchpointresourcenmos + web::json::value make_nc_touchpoint_resource_nmos(const nc_touchpoint_resource_nmos& resource) + { + using web::json::value; + + auto data = make_nc_touchpoint_resource(resource); + data[nmos::fields::nc::id] = value::string(resource.id); + + return data; + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nctouchpointresourcenmoschannelmapping + web::json::value make_nc_touchpoint_resource_nmos_channel_mapping(const nc_touchpoint_resource_nmos_channel_mapping& resource) + { + using web::json::value; + + auto data = make_nc_touchpoint_resource_nmos(resource); + data[nmos::fields::nc::io_id] = value::string(resource.io_id); + + return data; + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nctouchpoint + web::json::value make_nc_touchpoint(const utility::string_t& context_namespace) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::nc::context_namespace, context_namespace } + }); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nctouchpointnmos + web::json::value make_nc_touchpoint_nmos(const nc_touchpoint_resource_nmos& resource) + { + auto data = make_nc_touchpoint(U("x-nmos")); + data[nmos::fields::nc::resource] = make_nc_touchpoint_resource_nmos(resource); + + return data; + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nctouchpointnmoschannelmapping + web::json::value make_nc_touchpoint_nmos_channel_mapping(const nc_touchpoint_resource_nmos_channel_mapping& resource) + { + auto data = make_nc_touchpoint(U("x-nmos/channelmapping")); + data[nmos::fields::nc::resource] = make_nc_touchpoint_resource_nmos_channel_mapping(resource); + + return data; + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncobject + web::json::value make_nc_object(const nc_class_id& class_id, nc_oid oid, bool constant_oid, const web::json::value& owner, const utility::string_t& role, const web::json::value& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints) + { + using web::json::value; + + const auto id = utility::conversions::details::to_string_t(oid); + auto data = make_resource_core(id, user_label.is_null() ? U("") : user_label.as_string(), description); // required for nmos::resource + data[nmos::fields::nc::class_id] = make_nc_class_id(class_id); + data[nmos::fields::nc::oid] = oid; + data[nmos::fields::nc::constant_oid] = value::boolean(constant_oid); + data[nmos::fields::nc::owner] = owner; + data[nmos::fields::nc::role] = value::string(role); + data[nmos::fields::nc::user_label] = user_label; + data[nmos::fields::nc::touchpoints] = touchpoints; + data[nmos::fields::nc::runtime_property_constraints] = runtime_property_constraints; // level 2 runtime constraints. See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Constraints.html + + return data; + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncblock + web::json::value make_nc_block(const nc_class_id& class_id, nc_oid oid, bool constant_oid, const web::json::value& owner, const utility::string_t& role, const web::json::value& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints, bool enabled, const web::json::value& members) + { + using web::json::value; + + auto data = details::make_nc_object(class_id, oid, constant_oid, owner, role, user_label, description, touchpoints, runtime_property_constraints); + data[nmos::fields::nc::enabled] = value::boolean(enabled); + data[nmos::fields::nc::members] = members; + + return data; + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncworker + web::json::value make_nc_worker(const nc_class_id& class_id, nc_oid oid, bool constant_oid, nc_oid owner, const utility::string_t& role, const web::json::value& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints, bool enabled) + { + using web::json::value; + + auto data = details::make_nc_object(class_id, oid, constant_oid, owner, role, user_label, description, touchpoints, runtime_property_constraints); + data[nmos::fields::nc::enabled] = value::boolean(enabled); + + return data; + } + + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncreceivermonitor + web::json::value make_receiver_monitor(const nc_class_id& class_id, nc_oid oid, bool constant_oid, nc_oid owner, const utility::string_t& role, const utility::string_t& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints, bool enabled, + nc_connection_status::status connection_status, const utility::string_t& connection_status_message, nc_payload_status::status payload_status, const utility::string_t& payload_status_message) + { + using web::json::value; + + auto data = make_nc_worker(class_id, oid, constant_oid, owner, role, value::string(user_label), description, touchpoints, runtime_property_constraints, enabled); + data[nmos::fields::nc::connection_status] = value::number(connection_status); + data[nmos::fields::nc::connection_status_message] = value::string(connection_status_message); + data[nmos::fields::nc::payload_status] = value::number(payload_status); + data[nmos::fields::nc::payload_status_message] = value::string(payload_status_message); + + return data; + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncmanager + web::json::value make_nc_manager(const nc_class_id& class_id, nc_oid oid, bool constant_oid, const web::json::value& owner, const utility::string_t& role, const web::json::value& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints) + { + return make_nc_object(class_id, oid, constant_oid, owner, role, user_label, description, touchpoints, runtime_property_constraints); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdevicemanager + web::json::value make_nc_device_manager(nc_oid oid, nc_oid owner, const web::json::value& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints, + const web::json::value& manufacturer, const web::json::value& product, const utility::string_t& serial_number, + const web::json::value& user_inventory_code, const web::json::value& device_name, const web::json::value& device_role, const web::json::value& operational_state, nc_reset_cause::cause reset_cause) + { + using web::json::value; + + auto data = details::make_nc_manager(nc_device_manager_class_id, oid, true, owner, U("DeviceManager"), user_label, description, touchpoints, runtime_property_constraints); + data[nmos::fields::nc::nc_version] = value::string(U("v1.0.0")); + data[nmos::fields::nc::manufacturer] = manufacturer; + data[nmos::fields::nc::product] = product; + data[nmos::fields::nc::serial_number] = value::string(serial_number); + data[nmos::fields::nc::user_inventory_code] = user_inventory_code; + data[nmos::fields::nc::device_name] = device_name; + data[nmos::fields::nc::device_role] = device_role; + data[nmos::fields::nc::operational_state] = operational_state; + data[nmos::fields::nc::reset_cause] = reset_cause; + data[nmos::fields::nc::message] = value::null(); + + return data; + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncclassmanager + web::json::value make_nc_class_manager(nc_oid oid, nc_oid owner, const web::json::value& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints, const nmos::experimental::control_protocol_state& control_protocol_state) + { + using web::json::value; + + auto data = make_nc_manager(nc_class_manager_class_id, oid, true, owner, U("ClassManager"), user_label, description, touchpoints, runtime_property_constraints); + + auto lock = control_protocol_state.read_lock(); + + // add control classes + data[nmos::fields::nc::control_classes] = value::array(); + auto& control_classes = data[nmos::fields::nc::control_classes]; + for (const auto& control_class : control_protocol_state.control_class_descriptors) + { + auto& ctl_class = control_class.second; + + auto method_descriptors = value::array(); + for (const auto& method_descriptor : ctl_class.method_descriptors) { web::json::push_back(method_descriptors, std::get<0>(method_descriptor)); } + + const auto class_description = ctl_class.fixed_role.is_null() + ? make_nc_class_descriptor(ctl_class.description, ctl_class.class_id, ctl_class.name, ctl_class.property_descriptors, method_descriptors, ctl_class.event_descriptors) + : make_nc_class_descriptor(ctl_class.description, ctl_class.class_id, ctl_class.name, ctl_class.fixed_role.as_string(), ctl_class.property_descriptors, method_descriptors, ctl_class.event_descriptors); + web::json::push_back(control_classes, class_description); + } + + // add datatypes + data[nmos::fields::nc::datatypes] = value::array(); + auto& datatypes = data[nmos::fields::nc::datatypes]; + for (const auto& datatype_descriptor : control_protocol_state.datatype_descriptors) + { + web::json::push_back(datatypes, datatype_descriptor.second.descriptor); + } + + return data; + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncpropertychangedeventdata + web::json::value make_nc_property_changed_event_data(const nc_property_changed_event_data& property_changed_event_data) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::nc::property_id, details::make_nc_property_id(property_changed_event_data.property_id) }, + { nmos::fields::nc::change_type, property_changed_event_data.change_type }, + { nmos::fields::nc::value, property_changed_event_data.value }, + { nmos::fields::nc::sequence_item_index, property_changed_event_data.sequence_item_index } + }); + } + } + + // message response + // See https://specs.amwa.tv/is-12/branches/v1.0.x/docs/Protocol_messaging.html#command-response-message-type + web::json::value make_control_protocol_error_response(int32_t handle, const nc_method_result& method_result, const utility::string_t& error_message) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::nc::handle, handle }, + { nmos::fields::nc::result, details::make_nc_method_result_error(method_result, error_message) } + }); + } + web::json::value make_control_protocol_message_response(int32_t handle, const nc_method_result& method_result) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::nc::handle, handle }, + { nmos::fields::nc::result, details::make_nc_method_result(method_result) } + }); + } + web::json::value make_control_protocol_message_response(int32_t handle, const nc_method_result& method_result, const web::json::value& value) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::nc::handle, handle }, + { nmos::fields::nc::result, details::make_nc_method_result(method_result, value) } + }); + } + web::json::value make_control_protocol_message_response(int32_t handle, const nc_method_result& method_result, uint32_t value_) + { + using web::json::value; + + return make_control_protocol_message_response(handle, method_result, value(value_)); + } + web::json::value make_control_protocol_message_response(const web::json::value& responses) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::nc::message_type, ncp_message_type::command_response }, + { nmos::fields::nc::responses, responses } + }); + } + + // subscription response + // See https://specs.amwa.tv/is-12/branches/v1.0.x/docs/Protocol_messaging.html#subscription-response-message-type + web::json::value make_control_protocol_subscription_response(const web::json::value& subscriptions) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::nc::message_type, ncp_message_type::subscription_response }, + { nmos::fields::nc::subscriptions, subscriptions } + }); + } + + // notification + // See https://specs.amwa.tv/ms-05-01/branches/v1.0.x/docs/Core_Mechanisms.html#notification-messages + // See https://specs.amwa.tv/is-12/branches/v1.0.x/docs/Protocol_messaging.html#notification-message-type + web::json::value make_control_protocol_notification(nc_oid oid, const nc_event_id& event_id, const nc_property_changed_event_data& property_changed_event_data) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::nc::oid, oid }, + { nmos::fields::nc::event_id, details::make_nc_event_id(event_id)}, + { nmos::fields::nc::event_data, details::make_nc_property_changed_event_data(property_changed_event_data) } + }); + } + web::json::value make_control_protocol_notification_message(const web::json::value& notifications) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::nc::message_type, ncp_message_type::notification }, + { nmos::fields::nc::notifications, notifications } + }); + } + + // property changed notification event + // See https://specs.amwa.tv/ms-05-01/branches/v1.0.x/docs/Core_Mechanisms.html#the-propertychanged-event + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/NcObject.html#propertychanged-event + web::json::value make_property_changed_event(nc_oid oid, const std::vector& property_changed_event_data_list) + { + using web::json::value; + + auto notifications = value::array(); + for (auto& property_changed_event_data : property_changed_event_data_list) + { + web::json::push_back(notifications, make_control_protocol_notification(oid, nc_object_property_changed_event_id, property_changed_event_data)); + } + return make_control_protocol_notification_message(notifications); + } + + // error message + // See https://specs.amwa.tv/is-12/branches/v1.0.x/docs/Protocol_messaging.html#error-messages + web::json::value make_control_protocol_error_message(const nc_method_result& method_result, const utility::string_t& error_message) + { + using web::json::value_of; + + return value_of({ + { nmos::fields::nc::message_type, ncp_message_type::error }, + { nmos::fields::nc::status, method_result.status}, + { nmos::fields::nc::error_message, error_message } + }); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncobject + web::json::value make_nc_object_properties() + { + using web::json::value; + + auto properties = value::array(); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Static value. All instances of the same class will have the same identity value"), nc_object_class_id_property_id, nmos::fields::nc::class_id, U("NcClassId"), true, false, false, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Object identifier"), nc_object_oid_property_id, nmos::fields::nc::oid, U("NcOid"), true, false, false, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("TRUE iff OID is hardwired into device"), nc_object_constant_oid_property_id, nmos::fields::nc::constant_oid, U("NcBoolean"), true, false, false, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("OID of containing block. Can only ever be null for the root block"), nc_object_owner_property_id, nmos::fields::nc::owner, U("NcOid"), true, true, false, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Role of object in the containing block"), nc_object_role_property_id, nmos::fields::nc::role, U("NcString"), true, false, false, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Scribble strip"), nc_object_user_label_property_id, nmos::fields::nc::user_label, U("NcString"), false, true, false, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Touchpoints to other contexts"), nc_object_touchpoints_property_id, nmos::fields::nc::touchpoints, U("NcTouchpoint"), true, true, true, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Runtime property constraints"), nc_object_runtime_property_constraints_property_id, nmos::fields::nc::runtime_property_constraints, U("NcPropertyConstraints"), true, true, true, false, value::null())); + + return properties; + } + web::json::value make_nc_object_methods() + { + using web::json::value; + + auto methods = value::array(); + { + auto parameters = value::array(); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("Property id"), nmos::fields::nc::id, U("NcPropertyId"), false, false, value::null())); + web::json::push_back(methods, details::make_nc_method_descriptor(U("Get property value"), nc_object_get_method_id, U("Get"), U("NcMethodResultPropertyValue"), parameters, false)); + } + { + auto parameters = value::array(); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("Property id"), nmos::fields::nc::id, U("NcPropertyId"), false, false, value::null())); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("Property value"), nmos::fields::nc::value, true, false, value::null())); + web::json::push_back(methods, details::make_nc_method_descriptor(U("Set property value"), nc_object_set_method_id, U("Set"), U("NcMethodResult"), parameters, false)); + } + { + auto parameters = value::array(); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("Property id"), nmos::fields::nc::id, U("NcPropertyId"), false, false, value::null())); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("Index of item in the sequence"), nmos::fields::nc::index, U("NcId"), false, false, value::null())); + web::json::push_back(methods, details::make_nc_method_descriptor(U("Get sequence item"), nc_object_get_sequence_item_method_id, U("GetSequenceItem"), U("NcMethodResultPropertyValue"), parameters, false)); + } + { + auto parameters = value::array(); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("Property id"), nmos::fields::nc::id, U("NcPropertyId"), false, false, value::null())); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("Index of item in the sequence"), nmos::fields::nc::index, U("NcId"), false, false, value::null())); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("Value"), nmos::fields::nc::value, true, false, value::null())); + web::json::push_back(methods, details::make_nc_method_descriptor(U("Set sequence item value"), nc_object_set_sequence_item_method_id, U("SetSequenceItem"), U("NcMethodResult"), parameters, false)); + } + { + auto parameters = value::array(); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("Property id"), nmos::fields::nc::id,U("NcPropertyId"), false, false, value::null())); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("Value"), nmos::fields::nc::value, true, false, value::null())); + web::json::push_back(methods, details::make_nc_method_descriptor(U("Add item to sequence"), nc_object_add_sequence_item_method_id, U("AddSequenceItem"), U("NcMethodResultId"), parameters, false)); + } + { + auto parameters = value::array(); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("Property id"), nmos::fields::nc::id, U("NcPropertyId"), false, false, value::null())); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("Index of item in the sequence"), nmos::fields::nc::index, U("NcId"), false, false, value::null())); + web::json::push_back(methods, details::make_nc_method_descriptor(U("Delete sequence item"), nc_object_remove_sequence_item_method_id, U("RemoveSequenceItem"), U("NcMethodResult"), parameters, false)); + } + { + auto parameters = value::array(); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("Property id"), nmos::fields::nc::id, U("NcPropertyId"), false, false, value::null())); + web::json::push_back(methods, details::make_nc_method_descriptor(U("Get sequence length"), nc_object_get_sequence_length_method_id, U("GetSequenceLength"), U("NcMethodResultLength"), parameters, false)); + } + + return methods; + } + web::json::value make_nc_object_events() + { + using web::json::value; + + auto events = value::array(); + web::json::push_back(events, details::make_nc_event_descriptor(U("Property changed event"), nc_object_property_changed_event_id, U("PropertyChanged"), U("NcPropertyChangedEventData"), false)); + + return events; + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncblock + web::json::value make_nc_block_properties() + { + using web::json::value; + + auto properties = value::array(); + web::json::push_back(properties, details::make_nc_property_descriptor(U("TRUE if block is functional"), nc_block_enabled_property_id, nmos::fields::nc::enabled, U("NcBoolean"), true, false, false, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Descriptors of this block's members"), nc_block_members_property_id, nmos::fields::nc::members, U("NcBlockMemberDescriptor"), true, false, true, false, value::null())); + + return properties; + } + web::json::value make_nc_block_methods() + { + using web::json::value; + + auto methods = value::array(); + { + auto parameters = value::array(); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("If recurse is set to true, nested members can be retrieved"), nmos::fields::nc::recurse, U("NcBoolean"), false, false, value::null())); + web::json::push_back(methods, details::make_nc_method_descriptor(U("Gets descriptors of members of the block"), nc_block_get_member_descriptors_method_id, U("GetMemberDescriptors"), U("NcMethodResultBlockMemberDescriptors"), parameters, false)); + } + { + auto parameters = value::array(); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("Relative path to search for (MUST not include the role of the block targeted by oid)"), nmos::fields::nc::path, U("NcRolePath"), false, false, value::null())); + web::json::push_back(methods, details::make_nc_method_descriptor(U("Finds member(s) by path"), nc_block_find_members_by_path_method_id, U("FindMembersByPath"), U("NcMethodResultBlockMemberDescriptors"), parameters, false)); + } + { + auto parameters = value::array(); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("Role text to search for"), nmos::fields::nc::role, U("NcString"), false, false, value::null())); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("Signals if the comparison should be case sensitive"), nmos::fields::nc::case_sensitive, U("NcBoolean"), false, false, value::null())); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("TRUE to only return exact matches"), nmos::fields::nc::match_whole_string, U("NcBoolean"), false, false, value::null())); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("TRUE to search nested blocks"), nmos::fields::nc::recurse, U("NcBoolean"), false, false, value::null())); + web::json::push_back(methods, details::make_nc_method_descriptor(U("Finds members with given role name or fragment"), nc_block_find_members_by_role_method_id, U("FindMembersByRole"), U("NcMethodResultBlockMemberDescriptors"), parameters, false)); + } + { + auto parameters = value::array(); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("Class id to search for"), nmos::fields::nc::class_id, U("NcClassId"), false, false, value::null())); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("If TRUE it will also include derived class descriptors"), nmos::fields::nc::include_derived, U("NcBoolean"), false, false, value::null())); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("TRUE to search nested blocks"), nmos::fields::nc::recurse,U("NcBoolean"), false, false, value::null())); + web::json::push_back(methods, details::make_nc_method_descriptor(U("Finds members with given class id"), nc_block_find_members_by_class_id_method_id, U("FindMembersByClassId"), U("NcMethodResultBlockMemberDescriptors"), parameters, false)); + } + + return methods; + } + web::json::value make_nc_block_events() + { + using web::json::value; + + return value::array(); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncworker + web::json::value make_nc_worker_properties() + { + using web::json::value; + + auto properties = value::array(); + web::json::push_back(properties, details::make_nc_property_descriptor(U("TRUE iff worker is enabled"), nc_worker_enabled_property_id, nmos::fields::nc::enabled, U("NcBoolean"), false, false, false, false, value::null())); + + return properties; + } + web::json::value make_nc_worker_methods() + { + using web::json::value; + + return value::array(); + } + web::json::value make_nc_worker_events() + { + using web::json::value; + + return value::array(); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncmanager + web::json::value make_nc_manager_properties() + { + using web::json::value; + + return value::array(); + } + web::json::value make_nc_manager_methods() + { + using web::json::value; + + return value::array(); + } + web::json::value make_nc_manager_events() + { + using web::json::value; + + return value::array(); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdevicemanager + web::json::value make_nc_device_manager_properties() + { + using web::json::value; + + auto properties = value::array(); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Version of MS-05-02 that this device uses"), nc_device_manager_nc_version_property_id, nmos::fields::nc::nc_version, U("NcVersionCode"), true, false, false, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Manufacturer descriptor"), nc_device_manager_manufacturer_property_id, nmos::fields::nc::manufacturer, U("NcManufacturer"), true, false, false, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Product descriptor"), nc_device_manager_product_property_id, nmos::fields::nc::product, U("NcProduct"), true, false, false, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Serial number"), nc_device_manager_serial_number_property_id, nmos::fields::nc::serial_number, U("NcString"), true, false, false, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Asset tracking identifier (user specified)"), nc_device_manager_user_inventory_code_property_id, nmos::fields::nc::user_inventory_code, U("NcString"), false, true, false, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Name of this device in the application. Instance name, not product name"), nc_device_manager_device_name_property_id, nmos::fields::nc::device_name, U("NcString"), false, true, false, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Role of this device in the application"), nc_device_manager_device_role_property_id, nmos::fields::nc::device_role, U("NcString"), false, true, false, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Device operational state"), nc_device_manager_operational_state_property_id, nmos::fields::nc::operational_state, U("NcDeviceOperationalState"), true, false, false, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Reason for most recent reset"), nc_device_manager_reset_cause_property_id, nmos::fields::nc::reset_cause, U("NcResetCause"), true, false, false, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Arbitrary message from dev to controller"), nc_device_manager_message_property_id, nmos::fields::nc::message, U("NcString"), true, true, false, false, value::null())); + + return properties; + } + web::json::value make_nc_device_manager_methods() + { + using web::json::value; + + return value::array(); + } + web::json::value make_nc_device_manager_events() + { + using web::json::value; + + return value::array(); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncclassmanager + web::json::value make_nc_class_manager_properties() + { + using web::json::value; + + auto properties = value::array(); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Descriptions of all control classes in the device (descriptors do not contain inherited elements)"), nc_class_manager_control_classes_property_id, nmos::fields::nc::control_classes, U("NcClassDescriptor"), true, false, true, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Descriptions of all data types in the device (descriptors do not contain inherited elements)"), nc_class_manager_datatypes_property_id, nmos::fields::nc::datatypes, U("NcDatatypeDescriptor"), true, false, true, false, value::null())); + + return properties; + } + web::json::value make_nc_class_manager_methods() + { + using web::json::value; + + auto methods = value::array(); + { + auto parameters = value::array(); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("class ID"), nmos::fields::nc::class_id, U("NcClassId"), false, false, value::null())); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("If set the descriptor would contain all inherited elements"), nmos::fields::nc::include_inherited, U("NcBoolean"), false, false, value::null())); + web::json::push_back(methods, details::make_nc_method_descriptor(U("Get a single class descriptor"), nc_class_manager_get_control_class_method_id, U("GetControlClass"), U("NcMethodResultClassDescriptor"), parameters, false)); + } + { + auto parameters = value::array(); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("name of datatype"), nmos::fields::nc::name, U("NcName"), false, false, value::null())); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("If set the descriptor would contain all inherited elements"), nmos::fields::nc::include_inherited, U("NcBoolean"), false, false, value::null())); + web::json::push_back(methods, details::make_nc_method_descriptor(U("Get a single datatype descriptor"), nc_class_manager_get_datatype_method_id, U("GetDatatype"), U("NcMethodResultDatatypeDescriptor"), parameters, false)); + } + + return methods; + } + web::json::value make_nc_class_manager_events() + { + using web::json::value; + + return value::array(); + } + + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncreceivermonitor + web::json::value make_nc_receiver_monitor_properties() + { + using web::json::value; + + auto properties = value::array(); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Connection status property"), nc_receiver_monitor_connection_status_property_id, nmos::fields::nc::connection_status, U("NcConnectionStatus"), true, false, false, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Connection status message property"), nc_receiver_monitor_connection_status_message_property_id, nmos::fields::nc::connection_status_message, U("NcString"), true, true, false, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Payload status property"), nc_receiver_monitor_payload_status_property_id, nmos::fields::nc::payload_status, U("NcPayloadStatus"), true, false, false, false, value::null())); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Payload status message property"), nc_receiver_monitor_payload_status_message_property_id, nmos::fields::nc::payload_status_message, U("NcString"), true, true, false, false, value::null())); + + return properties; + } + web::json::value make_nc_receiver_monitor_methods() + { + using web::json::value; + + return value::array(); + } + web::json::value make_nc_receiver_monitor_events() + { + using web::json::value; + + return value::array(); + } + + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncreceivermonitorprotected + web::json::value make_nc_receiver_monitor_protected_properties() + { + using web::json::value; + + auto properties = value::array(); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Indicates if signal protection is active"), nc_receiver_monitor_protected_signal_protection_status_property_id, nmos::fields::nc::signal_protection_status, U("NcBoolean"), true, false, false, false, value::null())); + + return properties; + } + web::json::value make_nc_receiver_monitor_protected_methods() + { + using web::json::value; + + return value::array(); + } + web::json::value make_nc_receiver_monitor_protected_events() + { + using web::json::value; + + return value::array(); + } + + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/identification/#ncidentbeacon + web::json::value make_nc_ident_beacon_properties() + { + using web::json::value; + + auto properties = value::array(); + web::json::push_back(properties, details::make_nc_property_descriptor(U("Indicator active state"), nc_ident_beacon_active_property_id, nmos::fields::nc::active, U("NcBoolean"), false, false, false, false, value::null())); + + return properties; + } + web::json::value make_nc_ident_beacon_methods() + { + using web::json::value; + + return value::array(); + } + web::json::value make_nc_ident_beacon_events() + { + using web::json::value; + + return value::array(); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/classes/1.html + web::json::value make_nc_object_class() + { + using web::json::value; + + return details::make_nc_class_descriptor(U("NcObject class descriptor"), nc_object_class_id, U("NcObject"), make_nc_object_properties(), make_nc_object_methods(), make_nc_object_events()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/classes/1.1.html + web::json::value make_nc_block_class() + { + using web::json::value; + + return details::make_nc_class_descriptor(U("NcBlock class descriptor"), nc_block_class_id, U("NcBlock"), make_nc_block_properties(), make_nc_block_methods(), make_nc_block_events()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/classes/1.2.html + web::json::value make_nc_worker_class() + { + using web::json::value; + + return details::make_nc_class_descriptor(U("NcWorker class descriptor"), nc_worker_class_id, U("NcWorker"), make_nc_worker_properties(), make_nc_worker_methods(), make_nc_worker_events()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/classes/1.3.html + web::json::value make_nc_manager_class() + { + using web::json::value; + + return details::make_nc_class_descriptor(U("NcManager class descriptor"), nc_manager_class_id, U("NcManager"), make_nc_manager_properties(), make_nc_manager_methods(), make_nc_manager_events()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/classes/1.3.1.html + web::json::value make_nc_device_manager_class() + { + using web::json::value; + + return details::make_nc_class_descriptor(U("NcDeviceManager class descriptor"), nc_device_manager_class_id, U("NcDeviceManager"), U("DeviceManager"), make_nc_device_manager_properties(), make_nc_device_manager_methods(), make_nc_device_manager_events()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/classes/1.3.2.html + web::json::value make_nc_class_manager_class() + { + using web::json::value; + + return details::make_nc_class_descriptor(U("NcClassManager class descriptor"), nc_class_manager_class_id, U("NcClassManager"), U("ClassManager"), make_nc_class_manager_properties(), make_nc_class_manager_methods(), make_nc_class_manager_events()); + } + + // Identification feature set control classes + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/identification/#ncidentbeacon + web::json::value make_nc_ident_beacon_class() + { + using web::json::value; + + return details::make_nc_class_descriptor(U("NcIdentBeacon class descriptor"), nc_ident_beacon_class_id, U("NcIdentBeacon"), make_nc_ident_beacon_properties(), make_nc_ident_beacon_methods(), make_nc_ident_beacon_events()); + } + + // Monitoring feature set control classes + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncreceivermonitor + web::json::value make_nc_receiver_monitor_class() + { + using web::json::value; + + return details::make_nc_class_descriptor(U("NcReceiverMonitor class descriptor"), nc_receiver_monitor_class_id, U("NcReceiverMonitor"), make_nc_receiver_monitor_properties(), make_nc_receiver_monitor_methods(), make_nc_receiver_monitor_events()); + } + + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncreceivermonitorprotected + web::json::value make_nc_receiver_monitor_protected_class() + { + using web::json::value; + + return details::make_nc_class_descriptor(U("NcReceiverMonitorProtected class descriptor"), nc_receiver_monitor_protected_class_id, U("NcReceiverMonitorProtected"), make_nc_receiver_monitor_protected_properties(), make_nc_receiver_monitor_protected_methods(), make_nc_receiver_monitor_protected_events()); + } + + // Primitive datatypes + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_boolean_datatype() + { + using web::json::value; + + return details::make_nc_datatype_descriptor_primitive(U("Boolean primitive type"), U("NcBoolean"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_int16_datatype() + { + using web::json::value; + + return details::make_nc_datatype_descriptor_primitive(U("short"), U("NcInt16"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_int32_datatype() + { + using web::json::value; + + return details::make_nc_datatype_descriptor_primitive(U("long"), U("NcInt32"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_int64_datatype() + { + using web::json::value; + + return details::make_nc_datatype_descriptor_primitive(U("longlong"), U("NcInt64"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_uint16_datatype() + { + using web::json::value; + + return details::make_nc_datatype_descriptor_primitive(U("unsignedshort"), U("NcUint16"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_uint32_datatype() + { + using web::json::value; + + return details::make_nc_datatype_descriptor_primitive(U("unsignedlong"), U("NcUint32"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_uint64_datatype() + { + using web::json::value; + + return details::make_nc_datatype_descriptor_primitive(U("unsignedlonglong"), U("NcUint64"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_float32_datatype() + { + using web::json::value; + + return details::make_nc_datatype_descriptor_primitive(U("unrestrictedfloat"), U("NcFloat32"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_float64_datatype() + { + using web::json::value; + + return details::make_nc_datatype_descriptor_primitive(U("unrestricteddouble"), U("NcFloat64"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_string_datatype() + { + using web::json::value; + + return details::make_nc_datatype_descriptor_primitive(U("UTF-8 string"), U("NcString"), value::null()); + } + + + // Standard datatypes + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcBlockMemberDescriptor.html + web::json::value make_nc_block_member_descriptor_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Role of member in its containing block"), nmos::fields::nc::role, U("NcString"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("OID of member"), nmos::fields::nc::oid, U("NcOid"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("TRUE iff member's OID is hardwired into device"), nmos::fields::nc::constant_oid, U("NcBoolean"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Class ID"), nmos::fields::nc::class_id, U("NcClassId"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("User label"), nmos::fields::nc::user_label, U("NcString"), true, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Containing block's OID"), nmos::fields::nc::owner, U("NcOid"), false, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Descriptor which is specific to a block member"), U("NcBlockMemberDescriptor"), fields, U("NcDescriptor"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcClassDescriptor.html + web::json::value make_nc_class_descriptor_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Identity of the class"), nmos::fields::nc::class_id, U("NcClassId"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Name of the class"), nmos::fields::nc::name, U("NcName"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Role if the class has fixed role (manager classes)"), nmos::fields::nc::fixed_role, U("NcString"), true, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Property descriptors"), nmos::fields::nc::properties, U("NcPropertyDescriptor"), false, true, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Method descriptors"), nmos::fields::nc::methods, U("NcMethodDescriptor"), false, true, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Event descriptors"), nmos::fields::nc::events, U("NcEventDescriptor"), false, true, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Descriptor of a class"), U("NcClassDescriptor"), fields, U("NcDescriptor"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcClassId.html + web::json::value make_nc_class_id_datatype() + { + using web::json::value; + + return details::make_nc_datatype_typedef(U("Sequence of class ID fields"), U("NcClassId"), true, U("NcInt32"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcDatatypeDescriptor.html + web::json::value make_nc_datatype_descriptor_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Datatype name"), nmos::fields::nc::name, U("NcName"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Type: Primitive, Typedef, Struct, Enum"), nmos::fields::nc::type, U("NcDatatypeType"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Optional constraints on top of the underlying data type"), nmos::fields::nc::constraints, U("NcParameterConstraints"), true, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Base datatype descriptor"), U("NcDatatypeDescriptor"), fields, U("NcDescriptor"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcDatatypeDescriptorEnum.html + web::json::value make_nc_datatype_descriptor_enum_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("One item descriptor per enum option"), nmos::fields::nc::items, U("NcEnumItemDescriptor"), false, true, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Enum datatype descriptor"), U("NcDatatypeDescriptorEnum"), fields, U("NcDatatypeDescriptor"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcDatatypeDescriptorPrimitive.html + web::json::value make_nc_datatype_descriptor_primitive_datatype() + { + using web::json::value; + + auto fields = value::array(); + return details::make_nc_datatype_descriptor_struct(U("Primitive datatype descriptor"), U("NcDatatypeDescriptorPrimitive"), fields, U("NcDatatypeDescriptor"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcDatatypeDescriptorStruct.html + web::json::value make_nc_datatype_descriptor_struct_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("One item descriptor per field of the struct"), nmos::fields::nc::fields, U("NcFieldDescriptor"), false, true, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Name of the parent type if any or null if it has no parent"), nmos::fields::nc::parent_type, U("NcName"), true, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Struct datatype descriptor"), U("NcDatatypeDescriptorStruct"), fields, U("NcDatatypeDescriptor"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcDatatypeDescriptorTypeDef.html + web::json::value make_nc_datatype_descriptor_type_def_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Original typedef datatype name"), nmos::fields::nc::parent_type, U("NcName"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("TRUE iff type is a typedef sequence of another type"), nmos::fields::nc::is_sequence, U("NcBoolean"), false, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Type def datatype descriptor"), U("NcDatatypeDescriptorTypeDef"), fields, U("NcDatatypeDescriptor"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcDatatypeType.html + web::json::value make_nc_datatype_type_datatype() + { + using web::json::value; + + auto items = value::array(); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Primitive datatype"), U("Primitive"), 0)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Simple alias of another datatype"), U("Typedef"), 1)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Data structure"), U("Struct"), 2)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Enum datatype"), U("Enum"), 3)); + return details::make_nc_datatype_descriptor_enum(U("Datatype type"), U("NcDatatypeType"), items, value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcDescriptor.html + web::json::value make_nc_descriptor_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Optional user facing description"), nmos::fields::nc::description, U("NcString"), true, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Base descriptor"), U("NcDescriptor"), fields, value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcDeviceGenericState.html + web::json::value make_nc_device_generic_state_datatype() + { + using web::json::value; + + auto items = value::array(); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Unknown"), U("Unknown"), 0)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Normal operation"), U("NormalOperation"), 1)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Device is initializing"), U("Initializing"), 2)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Device is performing a software or firmware update"), U("Updating"), 3)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Device is experiencing a licensing error"), U("LicensingError"), 4)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Device is experiencing an internal error"), U("InternalError"), 5)); + return details::make_nc_datatype_descriptor_enum(U("Device generic operational state"), U("NcDeviceGenericState"), items, value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcDeviceOperationalState.html + web::json::value make_nc_device_operational_state_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Generic operational state"), nmos::fields::nc::generic_state, U("NcDeviceGenericState"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Specific device details"), nmos::fields::nc::device_specific_details, U("NcString"), true, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Device operational state"), U("NcDeviceOperationalState"), fields, value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcElementId.html + web::json::value make_nc_element_id_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Level of the element"), nmos::fields::nc::level, U("NcUint16"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Index of the element"), nmos::fields::nc::index, U("NcUint16"), false, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Class element id which contains the level and index"), U("NcElementId"), fields, value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcEnumItemDescriptor.html + web::json::value make_nc_enum_item_descriptor_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Name of option"), nmos::fields::nc::name, U("NcName"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Enum item numerical value"), nmos::fields::nc::value, U("NcUint16"), false, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Descriptor of an enum item"), U("NcEnumItemDescriptor"), fields, U("NcDescriptor"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcEventDescriptor.html + web::json::value make_nc_event_descriptor_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Event id with level and index"), nmos::fields::nc::id, U("NcEventId"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Name of event"), nmos::fields::nc::name, U("NcName"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Name of event data's datatype"), nmos::fields::nc::event_datatype, U("NcName"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("TRUE iff property is marked as deprecated"), nmos::fields::nc::is_deprecated, U("NcBoolean"), false, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Descriptor of a class event"), U("NcEventDescriptor"), fields, U("NcDescriptor"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcEventId.html + web::json::value make_nc_event_id_datatype() + { + using web::json::value; + + return details::make_nc_datatype_descriptor_struct(U("Event id which contains the level and index"), U("NcEventId"), value::array(), U("NcElementId"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcFieldDescriptor.html + web::json::value make_nc_field_descriptor_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Name of field"), nmos::fields::nc::name, U("NcName"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Name of field's datatype. Can only ever be null if the type is any"), nmos::fields::nc::type_name, U("NcName"), true, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("TRUE iff field is nullable"), nmos::fields::nc::is_nullable, U("NcBoolean"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("TRUE iff field is a sequence"), nmos::fields::nc::is_sequence, U("NcBoolean"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Optional constraints on top of the underlying data type"), nmos::fields::nc::constraints, U("NcParameterConstraints"), true, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Descriptor of a field of a struct"), U("NcFieldDescriptor"), fields, U("NcDescriptor"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcId.html + web::json::value make_nc_id_datatype() + { + using web::json::value; + + return details::make_nc_datatype_typedef(U("Identity handler"), U("NcId"), false, U("NcUint32"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcManufacturer.html + web::json::value make_nc_manufacturer_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Manufacturer's name"), nmos::fields::nc::name, U("NcString"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("IEEE OUI or CID of manufacturer"), nmos::fields::nc::organization_id, U("NcOrganizationId"), true, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("URL of the manufacturer's website"), nmos::fields::nc::website, U("NcUri"), true, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Manufacturer descriptor"), U("NcManufacturer"), fields, value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodDescriptor.html + web::json::value make_nc_method_descriptor_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Method id with level and index"), nmos::fields::nc::id, U("NcMethodId"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Name of method"), nmos::fields::nc::name, U("NcName"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Name of method result's datatype"), nmos::fields::nc::result_datatype, U("NcName"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Parameter descriptors if any"), nmos::fields::nc::parameters, U("NcParameterDescriptor"), false, true, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("TRUE iff property is marked as deprecated"), nmos::fields::nc::is_deprecated, U("NcBoolean"), false, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Descriptor of a class method"), U("NcMethodDescriptor"), fields, U("NcDescriptor"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodId.html + web::json::value make_nc_method_id_datatype() + { + using web::json::value; + + return details::make_nc_datatype_descriptor_struct(U("Method id which contains the level and index"), U("NcMethodId"), value::array(), U("NcElementId"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodResult.html + web::json::value make_nc_method_result_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Status for the invoked method"), nmos::fields::nc::status, U("NcMethodStatus"), false, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Base result of the invoked method"), U("NcMethodResult"), fields, value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodResultBlockMemberDescriptors.html + web::json::value make_nc_method_result_block_member_descriptors_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Block member descriptors method result value"), nmos::fields::nc::value, U("NcBlockMemberDescriptor"), false, true, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Method result containing block member descriptors as the value"), U("NcMethodResultBlockMemberDescriptors"), fields, U("NcMethodResult"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodResultClassDescriptor.html + web::json::value make_nc_method_result_class_descriptor_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Class descriptor method result value"), nmos::fields::nc::value, U("NcClassDescriptor"), false, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Method result containing a class descriptor as the value"), U("NcMethodResultClassDescriptor"), fields, U("NcMethodResult"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodResultDatatypeDescriptor.html + web::json::value make_nc_method_result_datatype_descriptor_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Datatype descriptor method result value"), nmos::fields::nc::value, U("NcDatatypeDescriptor"), false, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Method result containing a datatype descriptor as the value"), U("NcMethodResultDatatypeDescriptor"), fields, U("NcMethodResult"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodResultError.html + web::json::value make_nc_method_result_error_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Error message"), nmos::fields::nc::error_message, U("NcString"), false, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Error result - to be used when the method call encounters an error"), U("NcMethodResultError"), fields, U("NcMethodResult"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodResultId.html + web::json::value make_nc_method_result_id_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Id result value"), nmos::fields::nc::value, U("NcId"), false, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Id method result"), U("NcMethodResultId"), fields, U("NcMethodResult"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodResultLength.html + web::json::value make_nc_method_result_length_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Length result value"), nmos::fields::nc::value, U("NcUint32"), true, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Length method result"), U("NcMethodResultLength"), fields, U("NcMethodResult"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodResultPropertyValue.html + web::json::value make_nc_method_result_property_value_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Getter method value for the associated property"), nmos::fields::nc::value, true, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Result when invoking the getter method associated with a property"), U("NcMethodResultPropertyValue"), fields, U("NcMethodResult"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodStatus.html + web::json::value make_nc_method_status_datatype() + { + using web::json::value; + + auto items = value::array(); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Method call was successful"), U("Ok"), 200)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Method call was successful but targeted property is deprecated"), U("PropertyDeprecated"), 298)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Method call was successful but method is deprecated"), U("MethodDeprecated"), 299)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Badly-formed command (e.g. the incoming command has invalid message encoding and cannot be parsed by the underlying protocol)"), U("BadCommandFormat"), 400)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Client is not authorized"), U("Unauthorized"), 401)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Command addresses a nonexistent object"), U("BadOid"), 404)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Attempt to change read-only state"), U("Readonly"), 405)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Method call is invalid in current operating context (e.g. attempting to invoke a method when the object is disabled)"), U("InvalidRequest"), 406)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("There is a conflict with the current state of the device"), U("Conflict"), 409)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Something was too big"), U("BufferOverflow"), 413)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Index is outside the available range"), U("IndexOutOfBounds"), 414)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Method parameter does not meet expectations (e.g. attempting to invoke a method with an invalid type for one of its parameters)"), U("ParameterError"), 417)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Addressed object is locked"), U("Locked"), 423)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Internal device error"), U("DeviceError"), 500)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Addressed method is not implemented by the addressed object"), U("MethodNotImplemented"), 501)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Addressed property is not implemented by the addressed object"), U("PropertyNotImplemented"), 502)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("The device is not ready to handle any commands"), U("NotReady"), 503)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Method call did not finish within the allotted time"), U("Timeout"), 504)); + return details::make_nc_datatype_descriptor_enum(U("Method invokation status"), U("NcMethodStatus"), items, value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcName.html + web::json::value make_nc_name_datatype() + { + using web::json::value; + + return details::make_nc_datatype_typedef(U("Programmatically significant name, alphanumerics + underscore, no spaces"), U("NcName"), false, U("NcString"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcOid.html + web::json::value make_nc_oid_datatype() + { + using web::json::value; + + return details::make_nc_datatype_typedef(U("Object id"), U("NcOid"), false, U("NcUint32"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcOrganizationId.html + web::json::value make_nc_organization_id_datatype() + { + using web::json::value; + + return details::make_nc_datatype_typedef(U("Unique 24-bit organization id"), U("NcOrganizationId"), false, U("NcInt32"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcParameterConstraints.html + web::json::value make_nc_parameter_constraints_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Default value"), nmos::fields::nc::default_value, true, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Abstract parameter constraints class"), U("NcParameterConstraints"), fields, value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcParameterConstraintsNumber.html + web::json::value make_nc_parameter_constraints_number_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Optional minimum"), nmos::fields::nc::minimum, true, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Optional maximum"), nmos::fields::nc::maximum, true, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Optional step"), nmos::fields::nc::step, true, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Number parameter constraints class"), U("NcParameterConstraintsNumber"), fields, U("NcParameterConstraints"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcParameterConstraintsString.html + web::json::value make_nc_parameter_constraints_string_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Maximum characters allowed"), nmos::fields::nc::max_characters, U("NcUint32"), true, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Regex pattern"), nmos::fields::nc::pattern, U("NcRegex"), true, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("String parameter constraints class"), U("NcParameterConstraintsString"), fields, U("NcParameterConstraints"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcParameterDescriptor.html + web::json::value make_nc_parameter_descriptor_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Name of parameter"), nmos::fields::nc::name, U("NcName"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Name of parameter's datatype. Can only ever be null if the type is any"), nmos::fields::nc::type_name, U("NcName"), true, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("TRUE iff property is nullable"), nmos::fields::nc::is_nullable, U("NcBoolean"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("TRUE iff property is a sequence"), nmos::fields::nc::is_sequence, U("NcBoolean"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Optional constraints on top of the underlying data type"), nmos::fields::nc::constraints, U("NcParameterConstraints"), true, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Descriptor of a method parameter"), U("NcParameterDescriptor"), fields, U("NcDescriptor"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcProduct.html + web::json::value make_nc_product_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Product name"), nmos::fields::nc::name, U("NcString"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Manufacturer's unique key to product - model number, SKU, etc"), nmos::fields::nc::key, U("NcString"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Manufacturer's product revision level code"), nmos::fields::nc::revision_level, U("NcString"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Brand name under which product is sold"), nmos::fields::nc::brand_name, U("NcString"), true, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Unique UUID of product (not product instance)"), nmos::fields::nc::uuid, U("NcUuid"), true, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Text description of product"), nmos::fields::nc::description, U("NcString"), true, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Product descriptor"), U("NcProduct"), fields, value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcPropertyChangeType.html + web::json::value make_nc_property_change_type_datatype() + { + using web::json::value; + + auto items = value::array(); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Current value changed"), U("ValueChanged"), 0)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Sequence item added"), U("SequenceItemAdded"), 1)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Sequence item changed"), U("SequenceItemChanged"), 2)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Sequence item removed"), U("SequenceItemRemoved"), 3)); + return details::make_nc_datatype_descriptor_enum(U("Type of property change"), U("NcPropertyChangeType"), items, value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcPropertyChangedEventData.html + web::json::value make_nc_property_changed_event_data_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("The id of the property that changed"), nmos::fields::nc::property_id, U("NcPropertyId"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Information regarding the change type"), nmos::fields::nc::change_type, U("NcPropertyChangeType"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Property-type specific value"), nmos::fields::nc::value, true, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Index of sequence item if the property is a sequence"), nmos::fields::nc::sequence_item_index,U("NcId"), true, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Payload of property-changed event"), U("NcPropertyChangedEventData"), fields, value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcPropertyConstraints.html + web::json::value make_nc_property_contraints_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("The id of the property being constrained"), nmos::fields::nc::property_id, U("NcPropertyId"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Optional default value"), nmos::fields::nc::default_value, true, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Property constraints class"), U("NcPropertyConstraints"), fields, value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcPropertyConstraintsNumber.html + web::json::value make_nc_property_constraints_number_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Optional minimum"), nmos::fields::nc::minimum, true, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Optional maximum"), nmos::fields::nc::maximum, true, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Optional step"), nmos::fields::nc::step, true, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Number property constraints class"), U("NcPropertyConstraintsNumber"), fields, U("NcPropertyConstraints"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcPropertyConstraintsString.html + web::json::value make_nc_property_constraints_string_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Maximum characters allowed"), nmos::fields::nc::max_characters, U("NcUint32"), true, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Regex pattern"), nmos::fields::nc::pattern, U("NcRegex"), true, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("String property constraints class"), U("NcPropertyConstraintsString"), fields, U("NcPropertyConstraints"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcPropertyDescriptor.html + web::json::value make_nc_property_descriptor_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Property id with level and index"), nmos::fields::nc::id, U("NcPropertyId"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Name of property"), nmos::fields::nc::name, U("NcName"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Name of property's datatype. Can only ever be null if the type is any"), nmos::fields::nc::type_name, U("NcName"), true, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("TRUE iff property is read-only"), nmos::fields::nc::is_read_only, U("NcBoolean"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("TRUE iff property is nullable"), nmos::fields::nc::is_nullable, U("NcBoolean"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("TRUE iff property is a sequence"), nmos::fields::nc::is_sequence, U("NcBoolean"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("TRUE iff property is marked as deprecated"), nmos::fields::nc::is_deprecated, U("NcBoolean"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Optional constraints on top of the underlying data type"), nmos::fields::nc::constraints, U("NcParameterConstraints"), true, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Descriptor of a class property"), U("NcPropertyDescriptor"), fields, U("NcDescriptor"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcPropertyId.html + web::json::value make_nc_property_id_datatype() + { + using web::json::value; + + return details::make_nc_datatype_descriptor_struct(U("Property id which contains the level and index"), U("NcPropertyId"), value::array(), U("NcElementId"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcRegex.html + web::json::value make_nc_regex_datatype() + { + using web::json::value; + + return details::make_nc_datatype_typedef(U("Regex pattern"), U("NcRegex"), false, U("NcString"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcResetCause.html + web::json::value make_nc_reset_cause_datatype() + { + using web::json::value; + + auto items = value::array(); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Unknown"), U("Unknown"), 0)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Power on"), U("PowerOn"), 1)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Internal error"), U("InternalError"), 2)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Upgrade"), U("Upgrade"), 3)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Controller request"), U("ControllerRequest"), 4)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Manual request from the front panel"), U("ManualReset"), 5)); + return details::make_nc_datatype_descriptor_enum(U("Reset cause enum"), U("NcResetCause"), items, value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcRolePath.html + web::json::value make_nc_role_path_datatype() + { + using web::json::value; + + return details::make_nc_datatype_typedef(U("Role path"), U("NcRolePath"), true, U("NcString"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcTimeInterval.html + web::json::value make_nc_time_interval_datatype() + { + using web::json::value; + + return details::make_nc_datatype_typedef(U("Time interval described in nanoseconds"), U("NcTimeInterval"), false, U("NcInt64"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcTouchpoint.html + web::json::value make_nc_touchpoint_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Context namespace"), nmos::fields::nc::context_namespace, U("NcString"), false, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Base touchpoint class"), U("NcTouchpoint"), fields, value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcTouchpointNmos.html + web::json::value make_nc_touchpoint_nmos_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Context NMOS resource"), nmos::fields::nc::resource, U("NcTouchpointResourceNmos"), false, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Touchpoint class for NMOS resources"), U("NcTouchpointNmos"), fields, U("NcTouchpoint"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcTouchpointNmosChannelMapping.html + web::json::value make_nc_touchpoint_nmos_channel_mapping_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Context Channel Mapping resource"), nmos::fields::nc::resource,U("NcTouchpointResourceNmosChannelMapping"), false, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Touchpoint class for NMOS IS-08 resources"), U("NcTouchpointNmosChannelMapping"), fields, U("NcTouchpoint"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcTouchpointResource.html + web::json::value make_nc_touchpoint_resource_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("The type of the resource"), nmos::fields::nc::resource_type, U("NcString"), false, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Touchpoint resource class"), U("NcTouchpointResource"), fields, value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcTouchpointResourceNmos.html + web::json::value make_nc_touchpoint_resource_nmos_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("NMOS resource UUID"), nmos::fields::nc::id, U("NcUuid"), false, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Touchpoint resource class for NMOS resources"), U("NcTouchpointResourceNmos"), fields, U("NcTouchpointResource"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcTouchpointResourceNmosChannelMapping.html + web::json::value make_nc_touchpoint_resource_nmos_channel_mapping_datatype() + { + using web::json::value; + + auto fields = value::array(); + web::json::push_back(fields, details::make_nc_field_descriptor(U("IS-08 Audio Channel Mapping input or output id"), nmos::fields::nc::io_id, U("NcString"), false, false, value::null())); + return details::make_nc_datatype_descriptor_struct(U("Touchpoint resource class for NMOS resources"), U("NcTouchpointResourceNmosChannelMapping"), fields, U("NcTouchpointResourceNmos"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcUri.html + web::json::value make_nc_uri_datatype() + { + using web::json::value; + + return details::make_nc_datatype_typedef(U("Uniform resource identifier"), U("NcUri"), false, U("NcString"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcUuid.html + web::json::value make_nc_uuid_datatype() + { + using web::json::value; + + return details::make_nc_datatype_typedef(U("UUID"), U("NcUuid"), false, U("NcString"), value::null()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcVersionCode.html + web::json::value make_nc_version_code_datatype() + { + using web::json::value; + + return details::make_nc_datatype_typedef(U("Version code in semantic versioning format"), U("NcVersionCode"), false, U("NcString"), value::null()); + } + + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncconnectionstatus + web::json::value make_nc_connection_status_datatype() + { + using web::json::value; + + auto items = value::array(); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("This is the value when there is no receiver"), U("Undefined"), 0)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Connected to a stream"), U("Connected"), 1)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Not connected to a stream"), U("Disconnected"), 2)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("A connection error was encountered"), U("ConnectionError"), 3)); + return details::make_nc_datatype_descriptor_enum(U("Connection status enum data typee"), U("NcConnectionStatus"), items, value::null()); + } + + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncpayloadstatus + web::json::value make_nc_payload_status_datatype() + { + using web::json::value; + + auto items = value::array(); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("This is the value when there's no connection"), U("Undefined"), 0)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Payload is being received without errors and is the correct type"), U("PayloadOK"), 1)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Payload is being received but is of an unsupported type"), U("PayloadFormatUnsupported"), 2)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("A payload error was encountered"), U("PayloadError"), 3)); + return details::make_nc_datatype_descriptor_enum(U("Connection status enum data typee"), U("NcPayloadStatus"), items, value::null()); + } +} diff --git a/Development/nmos/control_protocol_resource.h b/Development/nmos/control_protocol_resource.h new file mode 100644 index 000000000..a03542264 --- /dev/null +++ b/Development/nmos/control_protocol_resource.h @@ -0,0 +1,431 @@ +#ifndef NMOS_CONTROL_PROTOCOL_RESOURCE_H +#define NMOS_CONTROL_PROTOCOL_RESOURCE_H + +#include "cpprest/json_utils.h" +#include "nmos/control_protocol_typedefs.h" +#include "nmos/resource.h" + +namespace web +{ + namespace json + { + class value; + } + class uri; +} + +namespace nmos +{ + struct control_protocol_resource : resource + { + control_protocol_resource(api_version version, nmos::type type, web::json::value&& data, nmos::id id, bool never_expire) + : resource(version, type, std::move(data), id, never_expire) + {} + + control_protocol_resource(api_version version, nmos::type type, web::json::value data, bool never_expire) + : resource(version, type, data, never_expire) + {} + + // temporary storage to hold the resources until they are moved to the model resources + std::vector resources; + }; +} + +namespace nmos +{ + namespace experimental + { + struct control_protocol_state; + } + + namespace details + { + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncelementid + web::json::value make_nc_element_id(const nc_element_id& element_id); + nc_element_id parse_nc_element_id(const web::json::value& element_id); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nceventid + web::json::value make_nc_event_id(const nc_event_id& event_id); + nc_event_id parse_nc_event_id(const web::json::value& event_id); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncmethodid + web::json::value make_nc_method_id(const nc_method_id& method_id); + nc_method_id parse_nc_method_id(const web::json::value& method_id); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncpropertyid + web::json::value make_nc_property_id(const nc_property_id& property_id); + nc_property_id parse_nc_property_id(const web::json::value& property_id); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncclassid + web::json::value make_nc_class_id(const nc_class_id& class_id); + nc_class_id parse_nc_class_id(const web::json::array& class_id); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncmanufacturer + web::json::value make_nc_manufacturer(const utility::string_t& name, nc_organization_id organization_id, const web::uri& website); + web::json::value make_nc_manufacturer(const utility::string_t& name, nc_organization_id organization_id); + web::json::value make_nc_manufacturer(const utility::string_t& name); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncproduct + web::json::value make_nc_product(const utility::string_t& name, const utility::string_t& key, const utility::string_t& revision_level, + const utility::string_t& brand_name, const nc_uuid& uuid, const utility::string_t& description); + web::json::value make_nc_product(const utility::string_t& name, const utility::string_t& key, const utility::string_t& revision_level, + const utility::string_t& brand_name, const nc_uuid& uuid); + web::json::value make_nc_product(const utility::string_t& name, const utility::string_t& key, const utility::string_t& revision_level, + const utility::string_t& brand_name); + web::json::value make_nc_product(const utility::string_t& name, const utility::string_t& key, const utility::string_t& revision_level); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdeviceoperationalstate + // device_specific_details can be null + web::json::value make_nc_device_operational_state(nc_device_generic_state::state generic_state, const web::json::value& device_specific_details); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncblockmemberdescriptor + web::json::value make_nc_block_member_descriptor(const utility::string_t& description, const utility::string_t& role, nc_oid oid, bool constant_oid, const nc_class_id& class_id, const utility::string_t& user_label, nc_oid owner); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncclassdescriptor + web::json::value make_nc_class_descriptor(const utility::string_t& description, const nc_class_id& class_id, const nc_name& name, const utility::string_t& fixed_role, const web::json::value& properties, const web::json::value& methods, const web::json::value& events); + web::json::value make_nc_class_descriptor(const utility::string_t& description, const nc_class_id& class_id, const nc_name& name, const web::json::value& properties, const web::json::value& methods, const web::json::value& events); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncenumitemdescriptor + web::json::value make_nc_enum_item_descriptor(const utility::string_t& description, const nc_name& name, uint16_t val); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nceventdescriptor + web::json::value make_nc_event_descriptor(const utility::string_t& description, const nc_event_id& id, const nc_name& name, const utility::string_t& event_datatype, bool is_deprecated); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncfielddescriptor + // constraints can be null + web::json::value make_nc_field_descriptor(const utility::string_t& description, const nc_name& name, const utility::string_t& type_name, bool is_nullable, bool is_sequence, const web::json::value& constraints); + web::json::value make_nc_field_descriptor(const utility::string_t& description, const nc_name& name, bool is_nullable, bool is_sequence, const web::json::value& constraints); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncmethoddescriptor + // sequence parameters + web::json::value make_nc_method_descriptor(const utility::string_t& description, const nc_method_id& id, const nc_name& name, const utility::string_t& result_datatype, const web::json::value& parameters, bool is_deprecated); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncparameterdescriptor + // constraints can be null + web::json::value make_nc_parameter_descriptor(const utility::string_t& description, const nc_name& name, bool is_nullable, bool is_sequence, const web::json::value& constraints); + web::json::value make_nc_parameter_descriptor(const utility::string_t& description, const nc_name& name, const utility::string_t& type_name, bool is_nullable, bool is_sequence, const web::json::value& constraints); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncpropertydescriptor + // constraints can be null + web::json::value make_nc_property_descriptor(const utility::string_t& description, const nc_property_id& id, const nc_name& name, const utility::string_t& type_name, + bool is_read_only, bool is_nullable, bool is_sequence, bool is_deprecated, const web::json::value& constraints); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdatatypedescriptorenum + // constraints can be null + // items: sequence + web::json::value make_nc_datatype_descriptor_enum(const utility::string_t& description, const nc_name& name, const web::json::value& items, const web::json::value& constraints); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdatatypedescriptorprimitive + // constraints can be null + web::json::value make_nc_datatype_descriptor_primitive(const utility::string_t& description, const nc_name& name, const web::json::value& constraints); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdatatypedescriptorstruct + // constraints can be null + // fields: sequence + web::json::value make_nc_datatype_descriptor_struct(const utility::string_t& description, const nc_name& name, const web::json::value& fields, const utility::string_t& parent_type, const web::json::value& constraints); + web::json::value make_nc_datatype_descriptor_struct(const utility::string_t& description, const nc_name& name, const web::json::value& fields, const web::json::value& constraints); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdatatypedescriptortypedef + web::json::value make_nc_datatype_typedef(const utility::string_t& description, const nc_name& name, bool is_sequence, const utility::string_t& parent_type, const web::json::value& constraints); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncpropertyconstraints + web::json::value make_nc_property_constraints(const nc_property_id& property_id, const web::json::value& default_value); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncpropertyconstraintsnumber + web::json::value make_nc_property_constraints_number(const nc_property_id& property_id, uint64_t default_value, uint64_t minimum, uint64_t maximum, uint64_t step); + web::json::value make_nc_property_constraints_number(const nc_property_id& property_id, uint64_t minimum, uint64_t maximum, uint64_t step); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncpropertyconstraintsstring + web::json::value make_nc_property_constraints_string(const nc_property_id& property_id, const utility::string_t& default_value, uint32_t max_characters, const nc_regex& pattern); + web::json::value make_nc_property_constraints_string(const nc_property_id& property_id, uint32_t max_characters, const nc_regex& pattern); + web::json::value make_nc_property_constraints_string(const nc_property_id& property_id, uint32_t max_characters); + web::json::value make_nc_property_constraints_string(const nc_property_id& property_id, const nc_regex& pattern); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncparameterconstraints + web::json::value make_nc_parameter_constraints(const web::json::value& default_value); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncparameterconstraintsnumber + web::json::value make_nc_parameter_constraints_number(uint64_t default_value, uint64_t minimum, uint64_t maximum, uint64_t step); + web::json::value make_nc_parameter_constraints_number(uint64_t minimum, uint64_t maximum, uint64_t step); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncparameterconstraintsstring + web::json::value make_nc_parameter_constraints_string(const utility::string_t& default_value, uint32_t max_characters, const nc_regex& pattern); + web::json::value make_nc_parameter_constraints_string(uint32_t max_characters, const nc_regex& pattern); + web::json::value make_nc_parameter_constraints_string(uint32_t max_characters); + web::json::value make_nc_parameter_constraints_string(const nc_regex& pattern); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nctouchpoint + web::json::value make_nc_touchpoint(const utility::string_t& context_namespace); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nctouchpointnmos + web::json::value make_nc_touchpoint_nmos(const nc_touchpoint_resource_nmos& resource); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nctouchpointnmoschannelmapping + web::json::value make_nc_touchpoint_nmos_channel_mapping(const nc_touchpoint_resource_nmos_channel_mapping& resource); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncobject + web::json::value make_nc_object(const nc_class_id& class_id, nc_oid oid, bool constant_oid, const web::json::value& owner, const utility::string_t& role, const web::json::value& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncblock + web::json::value make_nc_block(const nc_class_id& class_id, nc_oid oid, bool constant_oid, const web::json::value& owner, const utility::string_t& role, const web::json::value& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints, bool enabled, const web::json::value& members); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncworker + web::json::value make_nc_worker(const nc_class_id& class_id, nc_oid oid, bool constant_oid, nc_oid owner, const utility::string_t& role, const web::json::value& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints, bool enabled); + + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncreceivermonitor + web::json::value make_receiver_monitor(const nc_class_id& class_id, nc_oid oid, bool constant_oid, nc_oid owner, const utility::string_t& role, const utility::string_t& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints, bool enabled, + nc_connection_status::status connection_status, const utility::string_t& connection_status_message, nc_payload_status::status payload_status, const utility::string_t& payload_status_message); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncmanager + web::json::value make_nc_manager(const nc_class_id& class_id, nc_oid oid, bool constant_oid, const web::json::value& owner, const utility::string_t& role, const web::json::value& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdevicemanager + web::json::value make_nc_device_manager(nc_oid oid, nc_oid owner, const web::json::value& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints, + const web::json::value& manufacturer, const web::json::value& product, const utility::string_t& serial_number, + const web::json::value& user_inventory_code, const web::json::value& device_name, const web::json::value& device_role, const web::json::value& operational_state, nc_reset_cause::cause reset_cause); + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncclassmanager + web::json::value make_nc_class_manager(nc_oid oid, nc_oid owner, const web::json::value& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints, const nmos::experimental::control_protocol_state& control_protocol_state); + } + + // message response + // See https://specs.amwa.tv/is-12/branches/v1.0.x/docs/Protocol_messaging.html#command-response-message-type + web::json::value make_control_protocol_error_response(int32_t handle, const nc_method_result& method_result, const utility::string_t& error_message); + web::json::value make_control_protocol_message_response(int32_t handle, const nc_method_result& method_result); + web::json::value make_control_protocol_message_response(int32_t handle, const nc_method_result& method_result, const web::json::value& value); // value can be sequence, NcClassDescriptor, NcDatatypeDescriptor + web::json::value make_control_protocol_message_response(int32_t handle, const nc_method_result& method_result, uint32_t value); + web::json::value make_control_protocol_message_response(const web::json::value& responses); + + // subscription response + // See https://specs.amwa.tv/is-12/branches/v1.0.x/docs/Protocol_messaging.html#subscription-response-message-type + web::json::value make_control_protocol_subscription_response(const web::json::value& subscriptions); + + // notification + // See https://specs.amwa.tv/ms-05-01/branches/v1.0.x/docs/Core_Mechanisms.html#notification-messages + // See https://specs.amwa.tv/is-12/branches/v1.0.x/docs/Protocol_messaging.html#notification-message-type + web::json::value make_control_protocol_notification(nc_oid oid, const nc_event_id& event_id, const nc_property_changed_event_data& property_changed_event_data); + web::json::value make_control_protocol_notification_message(const web::json::value& notifications); + + // property changed notification event + // See https://specs.amwa.tv/ms-05-01/branches/v1.0.x/docs/Core_Mechanisms.html#the-propertychanged-event + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/NcObject.html#propertychanged-event + web::json::value make_property_changed_event(nc_oid oid, const std::vector& property_changed_event_data_list); + + // error message + // See https://specs.amwa.tv/is-12/branches/v1.0.x/docs/Protocol_messaging.html#error-messages + web::json::value make_control_protocol_error_message(const nc_method_result& method_result, const utility::string_t& error_message); + + // Control class models + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/classes/#control-class-models-for-branch-v10-dev + // + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/classes/1.html + web::json::value make_nc_object_class(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/classes/1.1.html + web::json::value make_nc_block_class(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/classes/1.2.html + web::json::value make_nc_worker_class(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/classes/1.3.html + web::json::value make_nc_manager_class(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/classes/1.3.1.html + web::json::value make_nc_device_manager_class(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/classes/1.3.2.html + web::json::value make_nc_class_manager_class(); + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/identification/#ncidentbeacon + web::json::value make_nc_ident_beacon_class(); + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncreceivermonitor + web::json::value make_nc_receiver_monitor_class(); + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncreceivermonitorprotected + web::json::value make_nc_receiver_monitor_protected_class(); + + // control classes proprties/methods/events + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncobject + web::json::value make_nc_object_properties(); + web::json::value make_nc_object_methods(); + web::json::value make_nc_object_events(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncblock + web::json::value make_nc_block_properties(); + web::json::value make_nc_block_methods(); + web::json::value make_nc_block_events(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncworker + web::json::value make_nc_worker_properties(); + web::json::value make_nc_worker_methods(); + web::json::value make_nc_worker_events(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncmanager + web::json::value make_nc_manager_properties(); + web::json::value make_nc_manager_methods(); + web::json::value make_nc_manager_events(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdevicemanager + web::json::value make_nc_device_manager_properties(); + web::json::value make_nc_device_manager_methods(); + web::json::value make_nc_device_manager_events(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncclassmanager + web::json::value make_nc_class_manager_properties(); + web::json::value make_nc_class_manager_methods(); + web::json::value make_nc_class_manager_events(); + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncreceivermonitor + web::json::value make_nc_receiver_monitor_properties(); + web::json::value make_nc_receiver_monitor_methods(); + web::json::value make_nc_receiver_monitor_events(); + + // Monitoring feature set control classes + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncreceivermonitorprotected + web::json::value make_nc_receiver_monitor_protected_properties(); + web::json::value make_nc_receiver_monitor_protected_methods(); + web::json::value make_nc_receiver_monitor_protected_events(); + + // Identification feature set control classes + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/identification/#ncidentbeacon + web::json::value make_nc_ident_beacon_properties(); + web::json::value make_nc_ident_beacon_methods(); + web::json::value make_nc_ident_beacon_events(); + + // Datatype models + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/#datatype-models-for-branch-v10-dev + // + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_boolean_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_int16_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_int32_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_int64_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_uint16_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_uint32_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_uint64_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_float32_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_float64_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#primitives + web::json::value make_nc_string_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcBlockMemberDescriptor.html + web::json::value make_nc_block_member_descriptor_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcClassDescriptor.html + web::json::value make_nc_class_descriptor_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcClassId.html + web::json::value make_nc_class_id_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcDatatypeDescriptor.html + web::json::value make_nc_datatype_descriptor_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcDatatypeDescriptorEnum.html + web::json::value make_nc_datatype_descriptor_enum_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcDatatypeDescriptorPrimitive.html + web::json::value make_nc_datatype_descriptor_primitive_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcDatatypeDescriptorStruct.html + web::json::value make_nc_datatype_descriptor_struct_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcDatatypeDescriptorTypeDef.html + web::json::value make_nc_datatype_descriptor_type_def_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcDatatypeType.html + web::json::value make_nc_datatype_type_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcDescriptor.html + web::json::value make_nc_descriptor_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcDeviceGenericState.html + web::json::value make_nc_device_generic_state_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcDeviceOperationalState.html + web::json::value make_nc_device_operational_state_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcElementId.html + web::json::value make_nc_element_id_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcEnumItemDescriptor.html + web::json::value make_nc_enum_item_descriptor_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcEventDescriptor.html + web::json::value make_nc_event_descriptor_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcEventId.html + web::json::value make_nc_event_id_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcFieldDescriptor.html + web::json::value make_nc_field_descriptor_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcId.html + web::json::value make_nc_id_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcManufacturer.html + web::json::value make_nc_manufacturer_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodDescriptor.html + web::json::value make_nc_method_descriptor_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodId.html + web::json::value make_nc_method_id_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodResult.html + web::json::value make_nc_method_result_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodResultBlockMemberDescriptors.html + web::json::value make_nc_method_result_block_member_descriptors_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodResultClassDescriptor.html + web::json::value make_nc_method_result_class_descriptor_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodResultDatatypeDescriptor.html + web::json::value make_nc_method_result_datatype_descriptor_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodResultError.html + web::json::value make_nc_method_result_error_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodResultId.html + web::json::value make_nc_method_result_id_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodResultLength.html + web::json::value make_nc_method_result_length_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodResultPropertyValue.html + web::json::value make_nc_method_result_property_value_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcMethodStatus.html + web::json::value make_nc_method_status_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcName.html + web::json::value make_nc_name_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcOid.html + web::json::value make_nc_oid_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcOrganizationId.html + web::json::value make_nc_organization_id_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcParameterConstraints.html + web::json::value make_nc_parameter_constraints_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcParameterConstraintsNumber.html + web::json::value make_nc_parameter_constraints_number_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcParameterConstraintsString.html + web::json::value make_nc_parameter_constraints_string_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcParameterDescriptor.html + web::json::value make_nc_parameter_descriptor_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcProduct.html + web::json::value make_nc_product_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcPropertyChangeType.html + web::json::value make_nc_property_change_type_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcPropertyChangedEventData.html + web::json::value make_nc_property_changed_event_data_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcPropertyConstraints.html + web::json::value make_nc_property_contraints_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcPropertyConstraintsNumber.html + web::json::value make_nc_property_constraints_number_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcPropertyConstraintsString.html + web::json::value make_nc_property_constraints_string_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcPropertyDescriptor.html + web::json::value make_nc_property_descriptor_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcPropertyId.html + web::json::value make_nc_property_id_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcRegex.html + web::json::value make_nc_regex_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcResetCause.html + web::json::value make_nc_reset_cause_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcRolePath.html + web::json::value make_nc_role_path_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcTimeInterval.html + web::json::value make_nc_time_interval_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcTouchpoint.html + web::json::value make_nc_touchpoint_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcTouchpointNmos.html + web::json::value make_nc_touchpoint_nmos_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcTouchpointNmosChannelMapping.html + web::json::value make_nc_touchpoint_nmos_channel_mapping_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcTouchpointResource.html + web::json::value make_nc_touchpoint_resource_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcTouchpointResourceNmos.html + web::json::value make_nc_touchpoint_resource_nmos_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcTouchpointResourceNmosChannelMapping.html + web::json::value make_nc_touchpoint_resource_nmos_channel_mapping_datatype(); + // See // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcUri.html + web::json::value make_nc_uri_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcUuid.html + web::json::value make_nc_uuid_datatype(); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcVersionCode.html + web::json::value make_nc_version_code_datatype(); + + // Monitoring feature set datatypes + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#datatypes + // + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncconnectionstatus + web::json::value make_nc_connection_status_datatype(); + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncpayloadstatus + web::json::value make_nc_payload_status_datatype(); +} + +#endif diff --git a/Development/nmos/control_protocol_resources.cpp b/Development/nmos/control_protocol_resources.cpp new file mode 100644 index 000000000..0fde54c33 --- /dev/null +++ b/Development/nmos/control_protocol_resources.cpp @@ -0,0 +1,101 @@ +#include "nmos/control_protocol_resources.h" + +#include "nmos/control_protocol_resource.h" +#include "nmos/control_protocol_utils.h" +#include "nmos/is12_versions.h" + +namespace nmos +{ + namespace details + { + // create block resource + control_protocol_resource make_block(nmos::nc_oid oid, const web::json::value& owner, const utility::string_t& role, const utility::string_t& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints, const web::json::value& members) + { + using web::json::value; + + auto data = details::make_nc_block(nc_block_class_id, oid, true, owner, role, value::string(user_label), description, touchpoints, runtime_property_constraints, true, members); + + return{ is12_versions::v1_0, types::nc_block, std::move(data), true }; + } + } + + // create block resource + control_protocol_resource make_block(nc_oid oid, nc_oid owner, const utility::string_t& role, const utility::string_t& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints, const web::json::value& members) + { + using web::json::value; + + return details::make_block(oid, value(owner), role, user_label, description, touchpoints, runtime_property_constraints, members); + } + + // create Root block resource + control_protocol_resource make_root_block() + { + using web::json::value; + + return details::make_block(1, value::null(), U("root"), U("Root"), U("Root block"), value::null(), value::null(), value::array()); + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdevicemanager + control_protocol_resource make_device_manager(nc_oid oid, const nmos::settings& settings) + { + using web::json::value; + + const auto& manufacturer = details::make_nc_manufacturer(nmos::experimental::fields::manufacturer_name(settings)); + const auto& product = details::make_nc_product(nmos::experimental::fields::product_name(settings), nmos::experimental::fields::product_key(settings), nmos::experimental::fields::product_key(settings)); + const auto& serial_number = nmos::experimental::fields::serial_number(settings); + const auto device_name = value::null(); + const auto device_role = value::null(); + const auto& operational_state = details::make_nc_device_operational_state(nc_device_generic_state::normal_operation, value::null()); + + auto data = details::make_nc_device_manager(oid, root_block_oid, value::string(U("Device manager")), U("The device manager offers information about the product this device is representing"), value::null(), value::null(), + manufacturer, product, serial_number, value::null(), device_name, device_role, operational_state, nc_reset_cause::unknown); + + return{ is12_versions::v1_0, types::nc_device_manager, std::move(data), true }; + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncclassmanager + control_protocol_resource make_class_manager(nc_oid oid, const nmos::experimental::control_protocol_state& control_protocol_state) + { + using web::json::value; + + auto data = details::make_nc_class_manager(oid, root_block_oid, value::string(U("Class manager")), U("The class manager offers access to control class and data type descriptors"), value::null(), value::null(), control_protocol_state); + + return{ is12_versions::v1_0, types::nc_class_manager, std::move(data), true }; + } + + // Monitoring feature set control classes + // + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncreceivermonitor + control_protocol_resource make_receiver_monitor(nc_oid oid, bool constant_oid, nc_oid owner, const utility::string_t& role, const utility::string_t& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints, bool enabled, + nc_connection_status::status connection_status, const utility::string_t& connection_status_message, nc_payload_status::status payload_status, const utility::string_t& payload_status_message) + { + auto data = details::make_receiver_monitor(nc_receiver_monitor_class_id, oid, constant_oid, owner, role, user_label, description, touchpoints, runtime_property_constraints, enabled, connection_status, connection_status_message, payload_status, payload_status_message); + + return{ is12_versions::v1_0, types::nc_receiver_monitor, std::move(data), true }; + } + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncreceivermonitorprotected + control_protocol_resource make_receiver_monitor_protected(nc_oid oid, bool constant_oid, nc_oid owner, const utility::string_t& role, const utility::string_t& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints, bool enabled, + nc_connection_status::status connection_status, const utility::string_t& connection_status_message, nc_payload_status::status payload_status, const utility::string_t& payload_status_message, bool signal_protection_status) + { + using web::json::value; + + auto data = details::make_receiver_monitor(nc_receiver_monitor_protected_class_id, oid, constant_oid, owner, role, user_label, description, touchpoints, runtime_property_constraints, enabled, connection_status, connection_status_message, payload_status, payload_status_message); + data[nmos::fields::nc::signal_protection_status] = value::boolean(signal_protection_status); + + return{ is12_versions::v1_0, types::nc_receiver_monitor_protected, std::move(data), true }; + } + + // Identification feature set control classes + // + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/identification/#ncidentbeacon + control_protocol_resource make_ident_beacon(nc_oid oid, bool constant_oid, nc_oid owner, const utility::string_t& role, const utility::string_t& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints, bool enabled, + bool active) + { + using web::json::value; + + auto data = nmos::details::make_nc_worker(nc_ident_beacon_class_id, oid, constant_oid, owner, role, value::string(user_label), description, touchpoints, runtime_property_constraints, enabled); + data[nmos::fields::nc::active] = value::boolean(active); + + return{ is12_versions::v1_0, types::nc_ident_beacon, std::move(data), true }; + } +} diff --git a/Development/nmos/control_protocol_resources.h b/Development/nmos/control_protocol_resources.h new file mode 100644 index 000000000..4ad7d85da --- /dev/null +++ b/Development/nmos/control_protocol_resources.h @@ -0,0 +1,54 @@ +#ifndef NMOS_CONTROL_PROTOCOL_RESOURCES_H +#define NMOS_CONTROL_PROTOCOL_RESOURCES_H + +#include "nmos/control_protocol_typedefs.h" // for details::nc_oid definition +#include "nmos/settings.h" + +namespace nmos +{ + namespace experimental + { + struct control_protocol_state; + } + + struct control_protocol_resource; + + // create block resource + control_protocol_resource make_block(nc_oid oid, nc_oid owner, const utility::string_t& role, const utility::string_t& user_label, const utility::string_t& description, const web::json::value& touchpoints = web::json::value::null(), const web::json::value& runtime_property_constraints = web::json::value::null(), const web::json::value& members = web::json::value::array()); + + // create Root block resource + control_protocol_resource make_root_block(); + + // create Device manager resource + control_protocol_resource make_device_manager(nc_oid oid, const nmos::settings& settings); + + // create Class manager resource + control_protocol_resource make_class_manager(nc_oid oid, const nmos::experimental::control_protocol_state& control_protocol_state); + + // Monitoring feature set control classes + // + // create Receiver Monitor resource + control_protocol_resource make_receiver_monitor(nc_oid oid, bool constant_oid, nmos::nc_oid owner, const utility::string_t& role, const utility::string_t& user_label, const utility::string_t& description, const web::json::value& touchpoints = web::json::value::null(), const web::json::value& runtime_property_constraints = web::json::value::null(), bool enabled = true, + nc_connection_status::status connection_status = nc_connection_status::status::undefined, + const utility::string_t& connection_status_message = U(""), + nc_payload_status::status payload_status = nc_payload_status::status::undefined, + const utility::string_t& payload_status_message = U("") + ); + // create Receiver Monitor Protected resource + control_protocol_resource make_receiver_monitor_protected(nc_oid oid, bool constant_oid, nc_oid owner, const utility::string_t& role, const utility::string_t& user_label, const utility::string_t& description, const web::json::value& touchpoints, const web::json::value& runtime_property_constraints = web::json::value::null(), bool enabled = true, + nc_connection_status::status connection_status = nc_connection_status::status::undefined, + const utility::string_t& connection_status_message = U(""), + nc_payload_status::status payload_status = nc_payload_status::status::undefined, + const utility::string_t& payload_status_message = U(""), + bool signal_protection_status = true + ); + + // Identification feature set control classes + // + // create Ident Beacon resource + control_protocol_resource make_ident_beacon(nc_oid oid, bool constant_oid, nc_oid owner, const utility::string_t& role, const utility::string_t& user_label, const utility::string_t& description, const web::json::value& touchpoints = web::json::value::null(), const web::json::value& runtime_property_constraints = web::json::value::null(), bool enabled = true, + bool active = false + ); +} + +#endif diff --git a/Development/nmos/control_protocol_state.cpp b/Development/nmos/control_protocol_state.cpp new file mode 100644 index 000000000..b611c2ed1 --- /dev/null +++ b/Development/nmos/control_protocol_state.cpp @@ -0,0 +1,361 @@ +#include "nmos/control_protocol_state.h" + +#include "nmos/control_protocol_methods.h" +#include "nmos/control_protocol_resource.h" + +namespace nmos +{ + namespace experimental + { + namespace details + { + // create control class descriptor + // where + // properties: vector of NcPropertyDescriptor where NcPropertyDescriptor can be constructed using make_control_class_property + // methods: vector of NcMethodDescriptor vs assoicated method handler where NcMethodDescriptor can be constructed using make_nc_method_descriptor + // events: vector of NcEventDescriptor where NcEventDescriptor can be constructed using make_nc_event_descriptor + control_class_descriptor make_control_class_descriptor(const utility::string_t& description, const nc_class_id& class_id, const nc_name& name, const web::json::value& fixed_role, const std::vector& properties_, const std::vector& methods_, const std::vector& events_) + { + using web::json::value; + + web::json::value properties = value::array(); + for (const auto& property : properties_) { web::json::push_back(properties, property); } + web::json::value events = value::array(); + for (const auto& event : events_) { web::json::push_back(events, event); } + + return { description, class_id, name, fixed_role, properties, methods_, events }; + } + } + // create control class descriptor with fixed role + // where + // properties: vector of NcPropertyDescriptor where NcPropertyDescriptor can be constructed using make_control_class_property + // methods: vector of NcMethodDescriptor where NcMethodDescriptor can be constructed using make_nc_method_descriptor and the assoicated method handler + // events: vector of NcEventDescriptor where NcEventDescriptor can be constructed using make_nc_event_descriptor + control_class_descriptor make_control_class_descriptor(const utility::string_t& description, const nc_class_id& class_id, const nc_name& name, const utility::string_t& fixed_role, const std::vector& properties, const std::vector& methods, const std::vector& events) + { + using web::json::value; + + return details::make_control_class_descriptor(description, class_id, name, value::string(fixed_role), properties, methods, events); + } + // create control class descriptor without fixed role + // where + // properties: vector of NcPropertyDescriptor where NcPropertyDescriptor can be constructed using make_control_class_property + // methods: vector of NcMethodDescriptor where NcMethodDescriptor can be constructed using make_nc_method_descriptor and the assoicated method handler + // events: vector of NcEventDescriptor where NcEventDescriptor can be constructed using make_nc_event_descriptor + control_class_descriptor make_control_class_descriptor(const utility::string_t& description, const nc_class_id& class_id, const nc_name& name, const std::vector& properties, const std::vector& methods, const std::vector& events) + { + using web::json::value; + + return details::make_control_class_descriptor(description, class_id, name, value::null(), properties, methods, events); + } + + // create control class property descriptor + web::json::value make_control_class_property_descriptor(const utility::string_t& description, const nc_property_id& id, const nc_name& name, const utility::string_t& type_name, bool is_read_only, bool is_nullable, bool is_sequence, bool is_deprecated, const web::json::value& constraints) + { + return nmos::details::make_nc_property_descriptor(description, id, name, type_name, is_read_only, is_nullable, is_sequence, is_deprecated, constraints); + } + + // create control class method parameter descriptor + web::json::value make_control_class_method_parameter_descriptor(const utility::string_t& description, const nc_name& name, const utility::string_t& type_name, bool is_nullable, bool is_sequence, const web::json::value& constraints) + { + return nmos::details::make_nc_parameter_descriptor(description, name, type_name, is_nullable, is_sequence, constraints); + } + + namespace details + { + web::json::value make_control_class_method_descriptor(const utility::string_t& description, const nc_method_id& id, const nc_name& name, const utility::string_t& result_datatype, const std::vector& parameters_, bool is_deprecated) + { + using web::json::value; + + value parameters = value::array(); + for (const auto& parameter : parameters_) { web::json::push_back(parameters, parameter); } + + return nmos::details::make_nc_method_descriptor(description, id, name, result_datatype, parameters, is_deprecated); + } + } + // create standard control class method descriptor + method make_control_class_method_descriptor(const utility::string_t& description, const nc_method_id& id, const nc_name& name, const utility::string_t& result_datatype, const std::vector& parameters, bool is_deprecated, standard_method_handler method_handler) + { + return make_control_class_standard_method(details::make_control_class_method_descriptor(description, id, name, result_datatype, parameters, is_deprecated), method_handler); + } + // create non-standard control class method descriptor + method make_control_class_method_descriptor(const utility::string_t& description, const nc_method_id& id, const nc_name& name, const utility::string_t& result_datatype, const std::vector& parameters, bool is_deprecated, non_standard_method_handler method_handler) + { + return make_control_class_non_standard_method(details::make_control_class_method_descriptor(description, id, name, result_datatype, parameters, is_deprecated), method_handler); + } + + // create control class event descriptor + web::json::value make_control_class_event_descriptor(const utility::string_t& description, const nc_event_id& id, const nc_name& name, const utility::string_t& event_datatype, bool is_deprecated) + { + return nmos::details::make_nc_event_descriptor(description, id, name, event_datatype, is_deprecated); + } + + control_protocol_state::control_protocol_state() + { + using web::json::value; + + auto to_vector = [](const web::json::value& data) + { + if (!data.is_null()) + { + return std::vector(data.as_array().begin(), data.as_array().end()); + } + return std::vector{}; + }; + + auto to_methods_vector = [](const web::json::value& nc_method_descriptors, const std::map& method_handlers) + { + // NcMethodDescriptor method handler array + std::vector methods; + + if (!nc_method_descriptors.is_null()) + { + for (const auto& nc_method_descriptor : nc_method_descriptors.as_array()) + { + methods.push_back(make_control_class_standard_method(nc_method_descriptor, method_handlers.at(nmos::details::parse_nc_method_id(nmos::fields::nc::id(nc_method_descriptor))))); + } + } + return methods; + }; + + // setup the standard control classes + control_class_descriptors = + { + // Control class models + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/classes/ + + // NcObject + { nc_object_class_id, make_control_class_descriptor(U("NcObject class descriptor"), nc_object_class_id, U("NcObject"), + // NcObject properties + to_vector(make_nc_object_properties()), + // NcObject methods + to_methods_vector(make_nc_object_methods(), + { + // link NcObject method_ids with method functions + { nc_object_get_method_id, get }, + { nc_object_set_method_id, set }, + { nc_object_get_sequence_item_method_id, get_sequence_item }, + { nc_object_set_sequence_item_method_id, set_sequence_item }, + { nc_object_add_sequence_item_method_id, add_sequence_item }, + { nc_object_remove_sequence_item_method_id, remove_sequence_item }, + { nc_object_get_sequence_length_method_id, get_sequence_length } + }), + // NcObject events + to_vector(make_nc_object_events())) }, + // NcBlock + { nc_block_class_id, make_control_class_descriptor(U("NcBlock class descriptor"), nc_block_class_id, U("NcBlock"), + // NcBlock properties + to_vector(make_nc_block_properties()), + // NcBlock methods + to_methods_vector(make_nc_block_methods(), + { + // link NcBlock method_ids with method functions + { nc_block_get_member_descriptors_method_id, get_member_descriptors }, + { nc_block_find_members_by_path_method_id, find_members_by_path }, + { nc_block_find_members_by_role_method_id, find_members_by_role }, + { nc_block_find_members_by_class_id_method_id, find_members_by_class_id } + }), + // NcBlock events + to_vector(make_nc_block_events())) }, + // NcWorker + { nc_worker_class_id, make_control_class_descriptor(U("NcWorker class descriptor"), nc_worker_class_id, U("NcWorker"), + // NcWorker properties + to_vector(make_nc_worker_properties()), + // NcWorker methods + to_methods_vector(make_nc_worker_methods(), {}), + // NcWorker events + to_vector(make_nc_worker_events())) }, + // NcManager + { nc_manager_class_id, make_control_class_descriptor(U("NcManager class descriptor"), nc_manager_class_id, U("NcManager"), + // NcManager properties + to_vector(make_nc_manager_properties()), + // NcManager methods + to_methods_vector(make_nc_manager_methods(), {}), + // NcManager events + to_vector(make_nc_manager_events())) }, + // NcDeviceManager + { nc_device_manager_class_id, make_control_class_descriptor(U("NcDeviceManager class descriptor"), nc_device_manager_class_id, U("NcDeviceManager"), U("DeviceManager"), + // NcDeviceManager properties + to_vector(make_nc_device_manager_properties()), + // NcDeviceManager methods + to_methods_vector(make_nc_device_manager_methods(), {}), + // NcDeviceManager events + to_vector(make_nc_device_manager_events())) }, + // NcClassManager + { nc_class_manager_class_id, make_control_class_descriptor(U("NcClassManager class descriptor"), nc_class_manager_class_id, U("NcClassManager"), U("ClassManager"), + // NcClassManager properties + to_vector(make_nc_class_manager_properties()), + // NcClassManager methods + to_methods_vector(make_nc_class_manager_methods(), + { + // link NcClassManager method_ids with method functions + { nc_class_manager_get_control_class_method_id, get_control_class }, + { nc_class_manager_get_datatype_method_id, get_datatype } + }), + // NcClassManager events + to_vector(make_nc_class_manager_events())) }, + // Identification feature set + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/identification/#control-classes + // NcIdentBeacon + { nc_ident_beacon_class_id, make_control_class_descriptor(U("NcIdentBeacon class descriptor"), nc_ident_beacon_class_id, U("NcIdentBeacon"), + // NcIdentBeacon properties + to_vector(make_nc_ident_beacon_properties()), + // NcIdentBeacon methods + to_methods_vector(make_nc_ident_beacon_methods(), {}), + // NcIdentBeacon events + to_vector(make_nc_ident_beacon_events())) }, + // Monitoring feature set + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#control-classes + // NcReceiverMonitor + { nc_receiver_monitor_class_id, make_control_class_descriptor(U("NcReceiverMonitor class descriptor"), nc_receiver_monitor_class_id, U("NcReceiverMonitor"), + // NcReceiverMonitor properties + to_vector(make_nc_receiver_monitor_properties()), + // NcReceiverMonitor methods + to_methods_vector(make_nc_receiver_monitor_methods(), {}), + // NcReceiverMonitor events + to_vector(make_nc_receiver_monitor_events())) }, + // NcReceiverMonitorProtected + { nc_receiver_monitor_protected_class_id, make_control_class_descriptor(U("NcReceiverMonitorProtected class descriptor"), nc_receiver_monitor_protected_class_id, U("NcReceiverMonitorProtected"), + // NcReceiverMonitorProtected properties + to_vector(make_nc_receiver_monitor_protected_properties()), + // NcReceiverMonitorProtected methods + to_methods_vector(make_nc_receiver_monitor_protected_methods(), {}), + // NcReceiverMonitorProtected events + to_vector(make_nc_receiver_monitor_protected_events())) } + }; + + // setup the standard datatypes + datatype_descriptors = + { + // Datatype models + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/ + { U("NcBoolean"), {make_nc_boolean_datatype()} }, + { U("NcInt16"), {make_nc_int16_datatype()} }, + { U("NcInt32"), {make_nc_int32_datatype()} }, + { U("NcInt64"), {make_nc_int64_datatype()} }, + { U("NcUint16"), {make_nc_uint16_datatype()} }, + { U("NcUint32"), {make_nc_uint32_datatype()} }, + { U("NcUint64"), {make_nc_uint64_datatype()} }, + { U("NcFloat32"), {make_nc_float32_datatype()} }, + { U("NcFloat64"), {make_nc_float64_datatype()} }, + { U("NcString"), {make_nc_string_datatype()} }, + { U("NcClassId"), {make_nc_class_id_datatype()} }, + { U("NcOid"), {make_nc_oid_datatype()} }, + { U("NcTouchpoint"), {make_nc_touchpoint_datatype()} }, + { U("NcElementId"), {make_nc_element_id_datatype()} }, + { U("NcPropertyId"), {make_nc_property_id_datatype()} }, + { U("NcPropertyConstraints"), {make_nc_property_contraints_datatype()} }, + { U("NcMethodResultPropertyValue"), {make_nc_method_result_property_value_datatype()} }, + { U("NcMethodStatus"), {make_nc_method_status_datatype()} }, + { U("NcMethodResult"), {make_nc_method_result_datatype()} }, + { U("NcId"), {make_nc_id_datatype()} }, + { U("NcMethodResultId"), {make_nc_method_result_id_datatype()} }, + { U("NcMethodResultLength"), {make_nc_method_result_length_datatype()} }, + { U("NcPropertyChangeType"), {make_nc_property_change_type_datatype()} }, + { U("NcPropertyChangedEventData"), {make_nc_property_changed_event_data_datatype()} }, + { U("NcDescriptor"), {make_nc_descriptor_datatype()} }, + { U("NcBlockMemberDescriptor"), {make_nc_block_member_descriptor_datatype()} }, + { U("NcMethodResultBlockMemberDescriptors"), {make_nc_method_result_block_member_descriptors_datatype()} }, + { U("NcVersionCode"), {make_nc_version_code_datatype()} }, + { U("NcOrganizationId"), {make_nc_organization_id_datatype()} }, + { U("NcUri"), {make_nc_uri_datatype()} }, + { U("NcManufacturer"), {make_nc_manufacturer_datatype()} }, + { U("NcUuid"), {make_nc_uuid_datatype()} }, + { U("NcProduct"), {make_nc_product_datatype()} }, + { U("NcDeviceGenericState"), {make_nc_device_generic_state_datatype()} }, + { U("NcDeviceOperationalState"), {make_nc_device_operational_state_datatype()} }, + { U("NcResetCause"), {make_nc_reset_cause_datatype()} }, + { U("NcName"), {make_nc_name_datatype()} }, + { U("NcPropertyDescriptor"), {make_nc_property_descriptor_datatype()} }, + { U("NcParameterDescriptor"), {make_nc_parameter_descriptor_datatype()} }, + { U("NcMethodId"), {make_nc_method_id_datatype()} }, + { U("NcMethodDescriptor"), {make_nc_method_descriptor_datatype()} }, + { U("NcEventId"), {make_nc_event_id_datatype()} }, + { U("NcEventDescriptor"), {make_nc_event_descriptor_datatype()} }, + { U("NcClassDescriptor"), {make_nc_class_descriptor_datatype()} }, + { U("NcParameterConstraints"), {make_nc_parameter_constraints_datatype()} }, + { U("NcDatatypeType"), {make_nc_datatype_type_datatype()} }, + { U("NcDatatypeDescriptor"), {make_nc_datatype_descriptor_datatype()} }, + { U("NcMethodResultClassDescriptor"), {make_nc_method_result_class_descriptor_datatype()} }, + { U("NcMethodResultDatatypeDescriptor"), {make_nc_method_result_datatype_descriptor_datatype()} }, + { U("NcMethodResultError"), {make_nc_method_result_error_datatype()} }, + { U("NcDatatypeDescriptorEnum"), {make_nc_datatype_descriptor_enum_datatype()} }, + { U("NcDatatypeDescriptorPrimitive"), {make_nc_datatype_descriptor_primitive_datatype()} }, + { U("NcDatatypeDescriptorStruct"), {make_nc_datatype_descriptor_struct_datatype()} }, + { U("NcDatatypeDescriptorTypeDef"), {make_nc_datatype_descriptor_type_def_datatype()} }, + { U("NcEnumItemDescriptor"), {make_nc_enum_item_descriptor_datatype()} }, + { U("NcFieldDescriptor"), {make_nc_field_descriptor_datatype()} }, + { U("NcPropertyConstraintsNumber"), {make_nc_property_constraints_number_datatype()} }, + { U("NcPropertyConstraintsString"), {make_nc_property_constraints_string_datatype()} }, + { U("NcRegex"), {make_nc_regex_datatype()} }, + { U("NcRolePath"), {make_nc_role_path_datatype()} }, + { U("NcParameterConstraintsNumber"), {make_nc_parameter_constraints_number_datatype()} }, + { U("NcParameterConstraintsString"), {make_nc_parameter_constraints_string_datatype()} }, + { U("NcTimeInterval"), {make_nc_time_interval_datatype()} }, + { U("NcTouchpointNmos"), {make_nc_touchpoint_nmos_datatype()} }, + { U("NcTouchpointNmosChannelMapping"), {make_nc_touchpoint_nmos_channel_mapping_datatype()} }, + { U("NcTouchpointResource"), {make_nc_touchpoint_resource_datatype()} }, + { U("NcTouchpointResourceNmos"), {make_nc_touchpoint_resource_nmos_datatype()} }, + { U("NcTouchpointResourceNmosChannelMapping"), {make_nc_touchpoint_resource_nmos_channel_mapping_datatype()} }, + // Monitoring feature set + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#datatypes + { U("NcConnectionStatus"), {make_nc_connection_status_datatype()} }, + { U("NcPayloadStatus"), {make_nc_payload_status_datatype()} } + }; + } + + // insert control class descriptor, false if class descriptor already inserted + bool control_protocol_state::insert(const experimental::control_class_descriptor& control_class_descriptor) + { + auto lock = write_lock(); + + if (control_class_descriptors.end() == control_class_descriptors.find(control_class_descriptor.class_id)) + { + control_class_descriptors[control_class_descriptor.class_id] = control_class_descriptor; + return true; + } + return false; + } + + // erase control class descriptor of the given class id, false if the required class descriptor not found + bool control_protocol_state::erase(nc_class_id class_id) + { + auto lock = write_lock(); + + if (control_class_descriptors.end() != control_class_descriptors.find(class_id)) + { + control_class_descriptors.erase(class_id); + return true; + } + return false; + } + + // insert datatype descriptor, false if datatype descriptor already inserted + bool control_protocol_state::insert(const experimental::datatype_descriptor& datatype_descriptor) + { + const auto& name = nmos::fields::nc::name(datatype_descriptor.descriptor); + + auto lock = write_lock(); + + if (datatype_descriptors.end() == datatype_descriptors.find(name)) + { + datatype_descriptors[name] = datatype_descriptor; + return true; + } + return false; + } + + // erase datatype descriptor of the given datatype name, false if the required datatype descriptor not found + bool control_protocol_state::erase(const utility::string_t& datatype_name) + { + auto lock = write_lock(); + + if (datatype_descriptors.end() != datatype_descriptors.find(datatype_name)) + { + datatype_descriptors.erase(datatype_name); + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/Development/nmos/control_protocol_state.h b/Development/nmos/control_protocol_state.h new file mode 100644 index 000000000..121a75ae7 --- /dev/null +++ b/Development/nmos/control_protocol_state.h @@ -0,0 +1,99 @@ +#ifndef NMOS_CONTROL_PROTOCOL_STATE_H +#define NMOS_CONTROL_PROTOCOL_STATE_H + +#include +#include "cpprest/json_utils.h" +#include "nmos/control_protocol_handlers.h" +#include "nmos/control_protocol_typedefs.h" +#include "nmos/mutex.h" + +namespace slog { class base_gate; } + +namespace nmos +{ + namespace experimental + { + struct control_class_descriptor // NcClassDescriptor + { + utility::string_t description; + nmos::nc_class_id class_id; + nmos::nc_name name; + web::json::value fixed_role; + + web::json::value property_descriptors = web::json::value::array(); // NcPropertyDescriptor array + std::vector method_descriptors; // NcMethodDescriptor method handler array + web::json::value event_descriptors = web::json::value::array(); // NcEventDescriptor array + + control_class_descriptor() + : class_id({ 0 }) + {} + + control_class_descriptor(utility::string_t description, nmos::nc_class_id class_id, nmos::nc_name name, web::json::value fixed_role, web::json::value property_descriptors, std::vector method_descriptors, web::json::value event_descriptors) + : description(std::move(description)) + , class_id(std::move(class_id)) + , name(std::move(name)) + , fixed_role(std::move(fixed_role)) + , property_descriptors(std::move(property_descriptors)) + , method_descriptors(std::move(method_descriptors)) + , event_descriptors(std::move(event_descriptors)) + {} + }; + + struct datatype_descriptor // NcDatatypeDescriptorEnum/NcDatatypeDescriptorPrimitive/NcDatatypeDescriptorStruct/NcDatatypeDescriptorTypeDef + { + web::json::value descriptor; + }; + + typedef std::map control_class_descriptors; + typedef std::map datatype_descriptors; + + struct control_protocol_state + { + // mutex to be used to protect the members from simultaneous access by multiple threads + mutable nmos::mutex mutex; + + experimental::control_class_descriptors control_class_descriptors; + experimental::datatype_descriptors datatype_descriptors; + + nmos::read_lock read_lock() const { return nmos::read_lock{ mutex }; } + nmos::write_lock write_lock() const { return nmos::write_lock{ mutex }; } + + control_protocol_state(); + + // insert control class descriptor, false if class descriptor already inserted + bool insert(const experimental::control_class_descriptor& control_class_descriptor); + // erase control class of the given class id, false if the required class not found + bool erase(nc_class_id class_id); + + // insert datatype descriptor, false if datatype descriptor already inserted + bool insert(const experimental::datatype_descriptor& datatype_descriptor); + // erase datatype descriptor of the given datatype name, false if the required datatype descriptor not found + bool erase(const utility::string_t& datatype_name); + }; + + // helper functions to create non-standard control class + // + + // create control class descriptor with fixed role + control_class_descriptor make_control_class_descriptor(const utility::string_t& description, const nc_class_id& class_id, const nc_name& name, const utility::string_t& fixed_role, const std::vector& properties = {}, const std::vector& methods = {}, const std::vector& events = {}); + // create control class descriptor with no fixed role + control_class_descriptor make_control_class_descriptor(const utility::string_t& description, const nc_class_id& class_id, const nc_name& name, const std::vector& properties = {}, const std::vector& methods = {}, const std::vector& events = {}); + + // create control class property descriptor + web::json::value make_control_class_property_descriptor(const utility::string_t& description, const nc_property_id& id, const nc_name& name, const utility::string_t& type_name, + bool is_read_only = false, bool is_nullable = false, bool is_sequence = false, bool is_deprecated = false, const web::json::value& constraints = web::json::value::null()); + + // create control class method parameter descriptor + web::json::value make_control_class_method_parameter_descriptor(const utility::string_t& description, const nc_name& name, const utility::string_t& type_name, + bool is_nullable = false, bool is_sequence = false, const web::json::value& constraints = web::json::value::null()); + // create control class method descriptor + method make_control_class_method_descriptor(const utility::string_t& description, const nc_method_id& id, const nc_name& name, const utility::string_t& result_datatype, + const std::vector& parameters, bool is_deprecated, non_standard_method_handler method_handler); + + // create control class event descriptor + web::json::value make_control_class_event_descriptor(const utility::string_t& description, const nc_event_id& id, const nc_name& name, const utility::string_t& event_datatype, + bool is_deprecated = false); + } +} + +#endif diff --git a/Development/nmos/control_protocol_typedefs.h b/Development/nmos/control_protocol_typedefs.h new file mode 100644 index 000000000..7afcefedb --- /dev/null +++ b/Development/nmos/control_protocol_typedefs.h @@ -0,0 +1,386 @@ +#ifndef NMOS_CONTROL_PROTOCOL_TYPEDEFS_H +#define NMOS_CONTROL_PROTOCOL_TYPEDEFS_H + +#include "cpprest/basic_utils.h" +#include "cpprest/json_utils.h" +#include "nmos/control_protocol_nmos_channel_mapping_resource_type.h" +#include "nmos/control_protocol_nmos_resource_type.h" + +namespace nmos +{ + // See https://specs.amwa.tv/is-12/branches/v1.0.x/docs/Protocol_messaging.html + namespace ncp_message_type + { + enum type + { + command = 0, + command_response = 1, + notification = 2, + subscription = 3, + subscription_response = 4, + error = 5 + }; + } + + // Method invokation status + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncmethodstatus + namespace nc_method_status + { + enum status + { + ok = 200, // Method call was successful + property_deprecated = 298, // Method call was successful but targeted property is deprecated + method_deprecated = 299, // Method call was successful but method is deprecated + bad_command_format = 400, // Badly-formed command + unauthorized = 401, // Client is not authorized + bad_oid = 404, // Command addresses a nonexistent object + read_only = 405, // Attempt to change read-only state + invalid_request = 406, // Method call is invalid in current operating context + conflict = 409, // There is a conflict with the current state of the device + buffer_overflow = 413, // Something was too big + index_out_of_bounds = 414, // Index is outside the available range + parameter_error = 417, // Method parameter does not meet expectations + locked = 423, // Addressed object is locked + device_error = 500, // Internal device error + method_not_implemented = 501, // Addressed method is not implemented by the addressed object + property_not_implemented = 502, // Addressed property is not implemented by the addressed object + not_ready = 503, // The device is not ready to handle any commands + timeout = 504, // Method call did not finish within the allotted time + }; + } + + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncmethodresult + struct nc_method_result + { + nc_method_status::status status; + }; + + // Datatype type + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdatatypetype + namespace nc_datatype_type + { + enum type + { + Primitive = 0, + Typedef = 1, + Struct = 2, + Enum = 3 + }; + } + + // Device generic operational state + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdevicegenericstate + namespace nc_device_generic_state + { + enum state + { + unknown = 0, // Unknown + normal_operation = 1, // Normal operation + initializing = 2, // Device is initializing + updating = 3, // Device is performing a software or firmware update + licensing_error = 4, // Device is experiencing a licensing error + internal_error = 5 // Device is experiencing an internal error + }; + } + + // Reset cause enum + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncresetcause + namespace nc_reset_cause + { + enum cause + { + unknown = 0, // Unknown + power_on = 1, // Power on + internal_error = 2, // Internal error + upgrade = 3, // Upgrade + controller_request = 4, // Controller request + manual_reset = 5 // Manual request from the front panel + }; + } + + // NcConnectionStatus + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncconnectionstatus + namespace nc_connection_status + { + enum status + { + undefined = 0, // This is the value when there is no receiver + connected = 1, // Connected to a stream + disconnected = 2, // Not connected to a stream + connection_error = 3 // A connection error was encountered + }; + } + + // NcPayloadStatus + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncpayloadstatus + namespace nc_payload_status + { + enum status + { + undefined = 0, // This is the value when there's no connection. + payload_ok = 1, // Payload is being received without errors and is the correct type + payload_format_unsupported = 2, // Payload is being received but is of an unsupported type + payloadError = 3 // A payload error was encountered + }; + } + + // NcElementId + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncelementid + struct nc_element_id + { + uint16_t level; + uint16_t index; + + nc_element_id(uint16_t level, uint16_t index) + : level(level) + , index(index) + {} + + auto tied() const -> decltype(std::tie(level, index)) { return std::tie(level, index); } + friend bool operator==(const nc_element_id& lhs, const nc_element_id& rhs) { return lhs.tied() == rhs.tied(); } + friend bool operator!=(const nc_element_id& lhs, const nc_element_id& rhs) { return !(lhs == rhs); } + friend bool operator<(const nc_element_id& lhs, const nc_element_id& rhs) { return lhs.tied() < rhs.tied(); } + }; + + // NcEventId + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nceventid + typedef nc_element_id nc_event_id; + // NcEventIds for NcObject + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncobject + const nc_event_id nc_object_property_changed_event_id(1, 1); + + // NcMethodId + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncmethodid + typedef nc_element_id nc_method_id; + // NcMethodIds for NcObject + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncobject + const nc_method_id nc_object_get_method_id(1, 1); + const nc_method_id nc_object_set_method_id(1, 2); + const nc_method_id nc_object_get_sequence_item_method_id(1, 3); + const nc_method_id nc_object_set_sequence_item_method_id(1, 4); + const nc_method_id nc_object_add_sequence_item_method_id(1, 5); + const nc_method_id nc_object_remove_sequence_item_method_id(1, 6); + const nc_method_id nc_object_get_sequence_length_method_id(1, 7); + // NcMethodIds for NcBlock + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncblock + const nc_method_id nc_block_get_member_descriptors_method_id(2, 1); + const nc_method_id nc_block_find_members_by_path_method_id(2, 2); + const nc_method_id nc_block_find_members_by_role_method_id(2, 3); + const nc_method_id nc_block_find_members_by_class_id_method_id(2, 4); + // NcMethodIds for NcClassManager + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncclassmanager + const nc_method_id nc_class_manager_get_control_class_method_id(3, 1); + const nc_method_id nc_class_manager_get_datatype_method_id(3, 2); + + // NcPropertyId + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncpropertyid + typedef nc_element_id nc_property_id; + // NcPropertyIds for NcObject + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncobject + const nc_property_id nc_object_class_id_property_id(1, 1); + const nc_property_id nc_object_oid_property_id(1, 2); + const nc_property_id nc_object_constant_oid_property_id(1, 3); + const nc_property_id nc_object_owner_property_id(1, 4); + const nc_property_id nc_object_role_property_id(1, 5); + const nc_property_id nc_object_user_label_property_id(1, 6); + const nc_property_id nc_object_touchpoints_property_id(1, 7); + const nc_property_id nc_object_runtime_property_constraints_property_id(1, 8); + // NcPropertyIds for NcBlock + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncblock + const nc_property_id nc_block_enabled_property_id(2, 1); + const nc_property_id nc_block_members_property_id(2, 2); + // NcPropertyIds for NcWorker + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncworker + const nc_property_id nc_worker_enabled_property_id(2, 1); + // NcPropertyIds for NcDeviceManager + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdevicemanager + const nc_property_id nc_device_manager_nc_version_property_id(3, 1); + const nc_property_id nc_device_manager_manufacturer_property_id(3, 2); + const nc_property_id nc_device_manager_product_property_id(3, 3); + const nc_property_id nc_device_manager_serial_number_property_id(3, 4); + const nc_property_id nc_device_manager_user_inventory_code_property_id(3, 5); + const nc_property_id nc_device_manager_device_name_property_id(3, 6); + const nc_property_id nc_device_manager_device_role_property_id(3, 7); + const nc_property_id nc_device_manager_operational_state_property_id(3, 8); + const nc_property_id nc_device_manager_reset_cause_property_id(3, 9); + const nc_property_id nc_device_manager_message_property_id(3, 10); + // NcPropertyIds for NcClassManager + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncclassmanager + const nc_property_id nc_class_manager_control_classes_property_id(3, 1); + const nc_property_id nc_class_manager_datatypes_property_id(3, 2); + // NcPropertyids for NcReceiverMonitor + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncreceivermonitor + const nc_property_id nc_receiver_monitor_connection_status_property_id(3, 1); + const nc_property_id nc_receiver_monitor_connection_status_message_property_id(3, 2); + const nc_property_id nc_receiver_monitor_payload_status_property_id(3, 3); + const nc_property_id nc_receiver_monitor_payload_status_message_property_id(3, 4); + // NcPropertyids for NcReceiverMonitorProtected + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncreceivermonitorprotected + const nc_property_id nc_receiver_monitor_protected_signal_protection_status_property_id(4, 1); + // NcPropertyids for NcIdentBeacon + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/identification/#ncidentbeacon + const nc_property_id nc_ident_beacon_active_property_id(3, 1); + + // NcId + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncid + typedef uint32_t nc_id; + + // NcName + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncname + typedef utility::string_t nc_name; + + // NcOid + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncoid + typedef uint32_t nc_oid; + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Blocks.html + const nc_oid root_block_oid{ 1 }; + + // NcUri + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncuri + typedef utility::string_t nc_uri; + + // NcUuid + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncuuid + typedef utility::string_t nc_uuid; + + // NcRegex + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncregex + typedef utility::string_t nc_regex; + + // NcOrganizationId + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncorganizationid + typedef int32_t nc_organization_id; + + // NcClassId + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncclassid + typedef std::vector nc_class_id; + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncobject + const nc_class_id nc_object_class_id({ 1 }); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncblock + const nc_class_id nc_block_class_id({ 1, 1 }); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncworker + const nc_class_id nc_worker_class_id({ 1, 2 }); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncmanager + const nc_class_id nc_manager_class_id({ 1, 3 }); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdevicemanager + const nc_class_id nc_device_manager_class_id({ 1, 3, 1 }); + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncclassmanager + const nc_class_id nc_class_manager_class_id({ 1, 3, 2 }); + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/identification/#ncidentbeacon + const nc_class_id nc_ident_beacon_class_id({ 1, 2, 2 }); + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncreceivermonitor + const nc_class_id nc_receiver_monitor_class_id({ 1, 2, 3 }); + // See https://specs.amwa.tv/nmos-control-feature-sets/branches/main/monitoring/#ncreceivermonitorprotected + const nc_class_id nc_receiver_monitor_protected_class_id({ 1, 2, 3, 1 }); + + // NcTouchpoint + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nctouchpoint + typedef utility::string_t nc_touch_point; + + // NcPropertyChangeType + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncpropertychangetype + namespace nc_property_change_type + { + enum type + { + value_changed = 0, // Current value changed + sequence_item_added = 1, // Sequence item added + sequence_item_changed = 2, // Sequence item changed + sequence_item_removed = 3 // Sequence item removed + }; + } + + // NcPropertyChangedEventData + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncpropertychangedeventdata + struct nc_property_changed_event_data + { + nc_property_id property_id; + nc_property_change_type::type change_type; + web::json::value value; + web::json::value sequence_item_index; // nc_id, can be null + + nc_property_changed_event_data(nc_property_id property_id, nc_property_change_type::type change_type, web::json::value value, nc_id sequence_item_index) + : property_id(std::move(property_id)) + , change_type(change_type) + , value(std::move(value)) + , sequence_item_index(sequence_item_index) + {} + + nc_property_changed_event_data(nc_property_id property_id, nc_property_change_type::type change_type, web::json::value value) + : property_id(std::move(property_id)) + , change_type(change_type) + , value(std::move(value)) + , sequence_item_index(web::json::value::null()) + {} + + nc_property_changed_event_data(nc_property_id property_id, nc_property_change_type::type change_type, nc_id sequence_item_index) + : property_id(std::move(property_id)) + , change_type(change_type) + , value(web::json::value::null()) + , sequence_item_index(sequence_item_index) + {} + + auto tied() const -> decltype(std::tie(property_id, change_type, value, sequence_item_index)) { return std::tie(property_id, change_type, value, sequence_item_index); } + friend bool operator==(const nc_property_changed_event_data& lhs, const nc_property_changed_event_data& rhs) { return lhs.tied() == rhs.tied(); } + friend bool operator!=(const nc_property_changed_event_data& lhs, const nc_property_changed_event_data& rhs) { return !(lhs == rhs); } + friend bool operator<(const nc_property_changed_event_data& lhs, const nc_property_changed_event_data& rhs) { return lhs.tied() < rhs.tied(); } + }; + + // NcTouchpointResource + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nctouchpointresource + struct nc_touchpoint_resource + { + utility::string_t resource_type; + + nc_touchpoint_resource(const utility::string_t& resource_type) + : resource_type(resource_type) + {} + + auto tied() const -> decltype(std::tie(resource_type)) { return std::tie(resource_type); } + friend bool operator==(const nc_touchpoint_resource& lhs, const nc_touchpoint_resource& rhs) { return lhs.tied() == rhs.tied(); } + friend bool operator!=(const nc_touchpoint_resource& lhs, const nc_touchpoint_resource& rhs) { return !(lhs == rhs); } + friend bool operator<(const nc_touchpoint_resource& lhs, const nc_touchpoint_resource& rhs) { return lhs.tied() < rhs.tied(); } + }; + + // NcTouchpointResourceNmos + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nctouchpointresourcenmos + struct nc_touchpoint_resource_nmos : nc_touchpoint_resource + { + nc_uuid id; + + nc_touchpoint_resource_nmos(const utility::string_t& resource_type, nc_uuid id) + : nc_touchpoint_resource(resource_type) + , id(id) + {} + + nc_touchpoint_resource_nmos(const ncp_nmos_resource_type& resource_type, nc_uuid id) + : nc_touchpoint_resource(resource_type.name) + , id(id) + {} + + auto tied() const -> decltype(std::tie(resource_type, id)) { return std::tie(resource_type, id); } + friend bool operator==(const nc_touchpoint_resource_nmos& lhs, const nc_touchpoint_resource_nmos& rhs) { return lhs.tied() == rhs.tied(); } + friend bool operator!=(const nc_touchpoint_resource_nmos& lhs, const nc_touchpoint_resource_nmos& rhs) { return !(lhs == rhs); } + friend bool operator<(const nc_touchpoint_resource_nmos& lhs, const nc_touchpoint_resource_nmos& rhs) { return lhs.tied() < rhs.tied(); } + }; + + // NcTouchpointResourceNmosChannelMapping + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#nctouchpointresourcenmoschannelmapping + struct nc_touchpoint_resource_nmos_channel_mapping : nc_touchpoint_resource_nmos + { + //ncp_nmos_channel_mapping_resource_type resource_type; + nc_uuid io_id; + + nc_touchpoint_resource_nmos_channel_mapping(const ncp_nmos_channel_mapping_resource_type& resource_type, nc_uuid id, const utility::string_t& io_id) + : nc_touchpoint_resource_nmos(resource_type.name, id) + , io_id(io_id) + {} + + auto tied() const -> decltype(std::tie(resource_type, id, io_id)) { return std::tie(resource_type, id, io_id); } + friend bool operator==(const nc_touchpoint_resource_nmos_channel_mapping& lhs, const nc_touchpoint_resource_nmos_channel_mapping& rhs) { return lhs.tied() == rhs.tied(); } + friend bool operator!=(const nc_touchpoint_resource_nmos_channel_mapping& lhs, const nc_touchpoint_resource_nmos_channel_mapping& rhs) { return !(lhs == rhs); } + friend bool operator<(const nc_touchpoint_resource_nmos_channel_mapping& lhs, const nc_touchpoint_resource_nmos_channel_mapping& rhs) { return lhs.tied() < rhs.tied(); } + }; +} + +#endif diff --git a/Development/nmos/control_protocol_utils.cpp b/Development/nmos/control_protocol_utils.cpp new file mode 100644 index 000000000..d22718bef --- /dev/null +++ b/Development/nmos/control_protocol_utils.cpp @@ -0,0 +1,651 @@ +#include "nmos/control_protocol_utils.h" + +#include +#include +#include +#include "bst/regex.h" +#include "cpprest/json_utils.h" +#include "nmos/control_protocol_resource.h" +#include "nmos/control_protocol_state.h" +#include "nmos/json_fields.h" +#include "nmos/query_utils.h" +#include "nmos/resources.h" + +namespace nmos +{ + namespace details + { + bool is_control_class(const nc_class_id& control_class_id, const nc_class_id& class_id_) + { + nc_class_id class_id{ class_id_ }; + if (control_class_id.size() < class_id.size()) + { + // truncate test class_id to relevant class_id + class_id.resize(control_class_id.size()); + } + return control_class_id == class_id; + } + + // get the runtime property constraints of a specific property_id + web::json::value get_runtime_property_constraints(const nc_property_id& property_id, const web::json::value& runtime_property_constraints) + { + using web::json::value; + + if (!runtime_property_constraints.is_null()) + { + auto& runtime_prop_constraints = runtime_property_constraints.as_array(); + auto found_constraints = std::find_if(runtime_prop_constraints.begin(), runtime_prop_constraints.end(), [&property_id](const web::json::value& constraints) + { + return property_id == parse_nc_property_id(nmos::fields::nc::property_id(constraints)); + }); + + if (runtime_prop_constraints.end() != found_constraints) + { + return *found_constraints; + } + } + return value::null(); + } + + // get the datatype descriptor of a specific type_name + web::json::value get_datatype_descriptor(const web::json::value& type_name, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor) + { + using web::json::value; + + if (!type_name.is_null()) + { + return get_control_protocol_datatype_descriptor(type_name.as_string()).descriptor; + } + return value::null(); + } + + // get the datatype property constraints of a specific type_name + web::json::value get_datatype_constraints(const web::json::value& type_name, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor) + { + using web::json::value; + + // NcDatatypeDescriptor + const auto& datatype_descriptor = get_datatype_descriptor(type_name, get_control_protocol_datatype_descriptor); + if (!datatype_descriptor.is_null()) + { + return nmos::fields::nc::constraints(datatype_descriptor); + } + return value::null(); + } + + // constraints validation, may throw nmos::control_protocol_exception + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncparameterconstraintsnumber + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncparameterconstraintsstring + void constraints_validation(const web::json::value& data, const web::json::value& constraints) + { + auto parameter_constraints_validation = [&constraints](const web::json::value& value) + { + // is numeric constraints + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncparameterconstraintsnumber + if (constraints.has_field(nmos::fields::nc::step) && !nmos::fields::nc::step(constraints).is_null()) + { + if (value.is_null()) { throw control_protocol_exception("value is null"); } + + if (!value.is_integer()) { throw control_protocol_exception("value is not an integer"); } + + const auto step = nmos::fields::nc::step(constraints).as_double(); + if (step <= 0) { throw control_protocol_exception("step is not a positive integer"); } + + const auto value_double = value.as_double(); + if (constraints.has_field(nmos::fields::nc::minimum) && !nmos::fields::nc::minimum(constraints).is_null()) + { + auto min = nmos::fields::nc::minimum(constraints).as_double(); + if (0 != std::fmod(value_double - min, step)) { throw control_protocol_exception("value is not divisible by step"); } + } + else if (constraints.has_field(nmos::fields::nc::maximum) && !nmos::fields::nc::maximum(constraints).is_null()) + { + auto max = nmos::fields::nc::maximum(constraints).as_double(); + if (0 != std::fmod(max - value_double, step)) { throw control_protocol_exception("value is not divisible by step"); } + } + else + { + if (0 != std::fmod(value_double, step)) { throw control_protocol_exception("value is not divisible by step"); } + } + } + if (constraints.has_field(nmos::fields::nc::minimum) && !nmos::fields::nc::minimum(constraints).is_null()) + { + if (value.is_null()) { throw control_protocol_exception("value is null"); } + + if (!value.is_integer() || value.as_double() < nmos::fields::nc::minimum(constraints).as_double()) { throw control_protocol_exception("value is less than minimum"); } + } + if (constraints.has_field(nmos::fields::nc::maximum) && !nmos::fields::nc::maximum(constraints).is_null()) + { + if (value.is_null()) { throw control_protocol_exception("value is null"); } + + if (!value.is_integer() || value.as_double() > nmos::fields::nc::maximum(constraints).as_double()) { throw control_protocol_exception("value is greater than maximum"); } + } + + // is string constraints + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncparameterconstraintsstring + if (constraints.has_field(nmos::fields::nc::max_characters) && !constraints.at(nmos::fields::nc::max_characters).is_null()) + { + if (value.is_null()) { throw control_protocol_exception("value is null"); } + + const size_t max_characters = nmos::fields::nc::max_characters(constraints); + if (!value.is_string() || value.as_string().length() > max_characters) { throw control_protocol_exception("value is longer than maximum characters"); } + } + if (constraints.has_field(nmos::fields::nc::pattern) && !constraints.at(nmos::fields::nc::pattern).is_null()) + { + if (value.is_null()) { throw control_protocol_exception("value is null"); } + + if (!value.is_string()) { throw control_protocol_exception("value is not a string"); } + const auto value_string = utility::us2s(value.as_string()); + bst::regex pattern(utility::us2s(nmos::fields::nc::pattern(constraints))); + if (!bst::regex_match(value_string, pattern)) { throw control_protocol_exception("value dose not match the pattern"); } + } + + // reaching here, parameter validation successfully + }; + + if (data.is_array()) + { + for (const auto& value : data.as_array()) + { + parameter_constraints_validation(value); + } + } + else + { + parameter_constraints_validation(data); + } + } + + // level 0 datatype constraints validation, may throw nmos::control_protocol_exception + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Constraints.html + void datatype_constraints_validation(const web::json::value& data, const datatype_constraints_validation_parameters& params) + { + // no constraints validation required + if (params.datatype_descriptor.is_null()) { return; } + + const auto& datatype_type = nmos::fields::nc::type(params.datatype_descriptor); + + // do NcDatatypeDescriptorPrimitive constraints validation + if (nc_datatype_type::Primitive == datatype_type) + { + // hmm, for the primitive type, it should not have datatype constraints specified via the datatype_descriptor but just in case + const auto& datatype_constraints = nmos::fields::nc::constraints(params.datatype_descriptor); + if (datatype_constraints.is_null()) + { + auto primitive_validation = [](const nc_name& name, const web::json::value& value) + { + auto is_int16 = [](int32_t value) + { + return value >= (std::numeric_limits::min)() + && value <= (std::numeric_limits::max)(); + }; + auto is_uint16 = [](uint32_t value) + { + return value >= (std::numeric_limits::min)() + && value <= (std::numeric_limits::max)(); + }; + auto is_float32 = [](double value) + { + return value >= (std::numeric_limits::min)() + && value <= (std::numeric_limits::max)(); + }; + + if (U("NcBoolean") == name) { return value.is_boolean(); } + if (U("NcInt16") == name && value.is_number()) { return is_int16(value.as_number().to_int32()); } + if (U("NcInt32") == name && value.is_number()) { return value.as_number().is_int32(); } + if (U("NcInt64") == name && value.is_number()) { return value.as_number().is_int64(); } + if (U("NcUint16") == name && value.is_number()) { return is_uint16(value.as_number().to_uint32()); } + if (U("NcUint32") == name && value.is_number()) { return value.as_number().is_uint32(); } + if (U("NcUint64") == name && value.is_number()) { return value.as_number().is_uint64(); } + if (U("NcFloat32") == name && value.is_number()) { return is_float32(value.as_number().to_double()); } + if (U("NcFloat64") == name && value.is_number()) { return !value.as_number().is_integral(); } + if (U("NcString") == name) { return value.is_string(); } + + // invalid primitive type + return false; + }; + + // do primitive type constraints validation + const auto& name = nmos::fields::nc::name(params.datatype_descriptor); + if (data.is_array()) + { + for (const auto& value : data.as_array()) + { + if (!primitive_validation(name, value)) + { + throw control_protocol_exception("value is not a " + utility::us2s(name) + " type"); + } + } + } + else + { + if (!primitive_validation(name, data)) + { + throw control_protocol_exception("value is not a " + utility::us2s(name) + " type");; + } + } + } + else + { + constraints_validation(data, datatype_constraints); + } + + return; + } + + // do NcDatatypeDescriptorTypeDef constraints validation + if (nc_datatype_type::Typedef == datatype_type) + { + // do the datatype constraints specified via the datatype_descriptor if presented + const auto& datatype_constraints = nmos::fields::nc::constraints(params.datatype_descriptor); + if (datatype_constraints.is_null()) + { + // do parent typename constraints validation + const auto& type_name = params.datatype_descriptor.at(nmos::fields::nc::parent_type); // parent type_name + datatype_constraints_validation(data, { details::get_datatype_descriptor(type_name, params.get_control_protocol_datatype_descriptor), params.get_control_protocol_datatype_descriptor }); + } + else + { + constraints_validation(data, datatype_constraints); + } + + return; + } + + // do NcDatatypeDescriptorEnum constraints validation + if (nc_datatype_type::Enum == datatype_type) + { + const auto& items = nmos::fields::nc::items(params.datatype_descriptor); + if (items.end() == std::find_if(items.begin(), items.end(), [&](const web::json::value& nc_enum_item_descriptor) { return nmos::fields::nc::value(nc_enum_item_descriptor) == data; })) + { + const auto& name = nmos::fields::nc::name(params.datatype_descriptor); + throw control_protocol_exception("value is not an enum " + utility::us2s(name) + " type"); + } + + return; + } + + // do NcDatatypeDescriptorStruct constraints validation + if (nc_datatype_type::Struct == datatype_type) + { + const auto& datatype_name = nmos::fields::nc::name(params.datatype_descriptor); + const auto& fields = nmos::fields::nc::fields(params.datatype_descriptor); + // NcFieldDescriptor + for (const web::json::value& nc_field_descriptor : fields) + { + const auto& field_name = nmos::fields::nc::name(nc_field_descriptor); + // is field in strurcture + if (!data.has_field(field_name)) { throw control_protocol_exception("missing " + utility::us2s(field_name) + " in " + utility::us2s(datatype_name)); } + + // is field nullable + if (nmos::fields::nc::is_nullable(nc_field_descriptor) != data.is_null()) { throw control_protocol_exception(utility::us2s(field_name) + " is not nullable"); } + + // is field sequenceable + if (nmos::fields::nc::is_sequence(nc_field_descriptor) != data.is_array()) { throw control_protocol_exception(utility::us2s(field_name) + " is not sequenceable"); } + + // check against field constraints if presented + const auto& constraints = nmos::fields::nc::constraints(nc_field_descriptor); + if (constraints.is_null()) + { + // no field constraints, move to check the constraints of its typeName + const auto& field_type_name = nc_field_descriptor.at(nmos::fields::nc::type_name); + + if (!field_type_name.is_null()) + { + auto value = data.at(field_name); + + if (value.is_array()) + { + for (const auto& val : value.as_array()) + { + // do typename constraints validation + datatype_constraints_validation(val, { details::get_datatype_descriptor(field_type_name, params.get_control_protocol_datatype_descriptor), params.get_control_protocol_datatype_descriptor }); + } + } + else + { + // do typename constraints validation + datatype_constraints_validation(value, { details::get_datatype_descriptor(field_type_name, params.get_control_protocol_datatype_descriptor), params.get_control_protocol_datatype_descriptor }); + } + } + } + else + { + // do field constraints validation + const auto& value = data.at(field_name); + constraints_validation(value, constraints); + } + } + + return; + } + + // unsupported datatype_type, no validation is required + } + + // multiple levels of constraints validation, may throw nmos::control_protocol_exception + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Constraints.html + void constraints_validation(const web::json::value& data, const web::json::value& runtime_property_constraints, const web::json::value& property_constraints, const datatype_constraints_validation_parameters& params) + { + // do level 2 runtime property constraints validation + if (!runtime_property_constraints.is_null()) { constraints_validation(data, runtime_property_constraints); return; } + + // do level 1 property constraints validation + if (!property_constraints.is_null()) { constraints_validation(data, property_constraints); return; } + + // do level 0 datatype constraints validation + datatype_constraints_validation(data, params); + } + + // method parameter constraints validation, may throw nmos::control_protocol_exception + void method_parameter_constraints_validation(const web::json::value& data, const web::json::value& property_constraints, const datatype_constraints_validation_parameters& params) + { + using web::json::value; + + // do level 1 property constraints & level 0 datatype constraints validation + constraints_validation(data, value::null(), property_constraints, params); + } + } + + // is the given class_id a NcBlock + bool is_nc_block(const nc_class_id& class_id) + { + return details::is_control_class(nc_block_class_id, class_id); + } + + // is the given class_id a NcWorker + bool is_nc_worker(const nc_class_id& class_id) + { + return details::is_control_class(nc_worker_class_id, class_id); + } + + // is the given class_id a NcManager + bool is_nc_manager(const nc_class_id& class_id) + { + return details::is_control_class(nc_manager_class_id, class_id); + } + + // is the given class_id a NcDeviceManager + bool is_nc_device_manager(const nc_class_id& class_id) + { + return details::is_control_class(nc_device_manager_class_id, class_id); + } + + // is the given class_id a NcClassManager + bool is_nc_class_manager(const nc_class_id& class_id) + { + return details::is_control_class(nc_class_manager_class_id, class_id); + } + + // construct NcClassId + nc_class_id make_nc_class_id(const nc_class_id& prefix, int32_t authority_key, const std::vector& suffix) + { + nc_class_id class_id = prefix; + class_id.push_back(authority_key); + class_id.insert(class_id.end(), suffix.begin(), suffix.end()); + return class_id; + } + nc_class_id make_nc_class_id(const nc_class_id& prefix, const std::vector& suffix) + { + return make_nc_class_id(prefix, 0, suffix); + } + + // find control class property descriptor (NcPropertyDescriptor) + web::json::value find_property_descriptor(const nc_property_id& property_id, const nc_class_id& class_id_, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor) + { + using web::json::value; + + auto class_id = class_id_; + + while (!class_id.empty()) + { + const auto& control_class = get_control_protocol_class_descriptor(class_id); + auto& property_descriptors = control_class.property_descriptors.as_array(); + auto found = std::find_if(property_descriptors.begin(), property_descriptors.end(), [&property_id](const web::json::value& property_descriptor) + { + return (property_id == nmos::details::parse_nc_property_id(nmos::fields::nc::id(property_descriptor))); + }); + if (property_descriptors.end() != found) { return *found; } + + class_id.pop_back(); + } + + return value::null(); + } + + // get block member descriptors + void get_member_descriptors(const resources& resources, const resource& resource, bool recurse, web::json::array& descriptors) + { + if (resource.data.has_field(nmos::fields::nc::members)) + { + const auto& members = nmos::fields::nc::members(resource.data); + + for (const auto& member : members) + { + web::json::push_back(descriptors, member); + } + + if (recurse) + { + // get members on all NcBlock(s) + for (const auto& member : members) + { + if (is_nc_block(nmos::details::parse_nc_class_id(nmos::fields::nc::class_id(member)))) + { + // get resource based on the oid + const auto& oid = nmos::fields::nc::oid(member); + const auto& found = find_resource(resources, utility::s2us(std::to_string(oid))); + if (resources.end() != found) + { + get_member_descriptors(resources, *found, recurse, descriptors); + } + } + } + } + } + } + + // find members with given role name or fragment + void find_members_by_role(const resources& resources, const resource& resource, const utility::string_t& role, bool match_whole_string, bool case_sensitive, bool recurse, web::json::array& descriptors) + { + auto find_members_by_matching_role = [&](const web::json::array& members) + { + using web::json::value; + + auto match = [&](const web::json::value& descriptor) + { + if (match_whole_string) + { + if (case_sensitive) { return role == nmos::fields::nc::role(descriptor); } + else { return boost::algorithm::to_upper_copy(role) == boost::algorithm::to_upper_copy(nmos::fields::nc::role(descriptor)); } + } + else + { + if (case_sensitive) { return !boost::find_first(nmos::fields::nc::role(descriptor), role).empty(); } + else { return !boost::ifind_first(nmos::fields::nc::role(descriptor), role).empty(); } + } + }; + + return boost::make_iterator_range(boost::make_filter_iterator(match, members.begin(), members.end()), boost::make_filter_iterator(match, members.end(), members.end())); + }; + + if (resource.data.has_field(nmos::fields::nc::members)) + { + const auto& members = nmos::fields::nc::members(resource.data); + + auto members_found = find_members_by_matching_role(members); + for (const auto& member : members_found) + { + web::json::push_back(descriptors, member); + } + + if (recurse) + { + // do role match on all NcBlock(s) + for (const auto& member : members) + { + if (is_nc_block(nmos::details::parse_nc_class_id(nmos::fields::nc::class_id(member)))) + { + // get resource based on the oid + const auto& oid = nmos::fields::nc::oid(member); + const auto& found = find_resource(resources, utility::s2us(std::to_string(oid))); + if (resources.end() != found) + { + find_members_by_role(resources, *found, role, match_whole_string, case_sensitive, recurse, descriptors); + } + } + } + } + } + } + + // find members with given class id + void find_members_by_class_id(const resources& resources, const nmos::resource& resource, const nc_class_id& class_id_, bool include_derived, bool recurse, web::json::array& descriptors) + { + auto find_members_by_matching_class_id = [&](const web::json::array& members) + { + using web::json::value; + + auto match = [&](const web::json::value& descriptor) + { + const auto& class_id = nmos::details::parse_nc_class_id(nmos::fields::nc::class_id(descriptor)); + + if (include_derived) { return !boost::find_first(class_id, class_id_).empty(); } + else { return class_id == class_id_; } + }; + + return boost::make_iterator_range(boost::make_filter_iterator(match, members.begin(), members.end()), boost::make_filter_iterator(match, members.end(), members.end())); + }; + + if (resource.data.has_field(nmos::fields::nc::members)) + { + auto& members = nmos::fields::nc::members(resource.data); + + auto members_found = find_members_by_matching_class_id(members); + for (const auto& member : members_found) + { + web::json::push_back(descriptors, member); + } + + if (recurse) + { + // do class_id match on all NcBlock(s) + for (const auto& member : members) + { + if (is_nc_block(nmos::details::parse_nc_class_id(nmos::fields::nc::class_id(member)))) + { + // get resource based on the oid + const auto& oid = nmos::fields::nc::oid(member); + const auto& found = find_resource(resources, utility::s2us(std::to_string(oid))); + if (resources.end() != found) + { + find_members_by_class_id(resources, *found, class_id_, include_derived, recurse, descriptors); + } + } + } + } + } + } + + // push a control protocol resource into other control protocol NcBlock resource + void push_back(control_protocol_resource& nc_block_resource, const control_protocol_resource& resource) + { + // note, model write lock should aleady be applied by the outer function, so access to control_protocol_resources is OK... + + using web::json::value; + + auto& parent = nc_block_resource.data; + const auto& child = resource.data; + + if (!is_nc_block(details::parse_nc_class_id(nmos::fields::nc::class_id(parent)))) throw std::logic_error("non-NcBlock cannot be nested"); + + web::json::push_back(parent[nmos::fields::nc::members], + details::make_nc_block_member_descriptor(nmos::fields::description(child), nmos::fields::nc::role(child), nmos::fields::nc::oid(child), nmos::fields::nc::constant_oid(child), details::parse_nc_class_id(nmos::fields::nc::class_id(child)), nmos::fields::nc::user_label(child), nmos::fields::nc::oid(parent))); + + nc_block_resource.resources.push_back(resource); + } + + // modify a control protocol resource, and insert notification event to all subscriptions + bool modify_control_protocol_resource(resources& resources, const id& id, std::function modifier, const web::json::value& notification_event) + { + // note, model write lock should aleady be applied by the outer function, so access to control_protocol_resources is OK... + + auto found = resources.find(id); + if (resources.end() == found || !found->has_data()) return false; + + auto pre = found->data; + + // "If an exception is thrown by some user-provided operation, then the element pointed to by position is erased." + // This seems too surprising, despite the fact that it means that a modification may have been partially completed, + // so capture and rethrow. + // See https://www.boost.org/doc/libs/1_68_0/libs/multi_index/doc/reference/ord_indices.html#modify + std::exception_ptr modifier_exception; + + auto resource_updated = nmos::strictly_increasing_update(resources); + auto result = resources.modify(found, [&resource_updated, &modifier, &modifier_exception](resource& resource) + { + try + { + modifier(resource); + } + catch (...) + { + modifier_exception = std::current_exception(); + } + + // set the update timestamp + resource.updated = resource_updated; + }); + + if (result) + { + auto& modified = *found; + + insert_notification_events(resources, modified.version, modified.downgrade_version, modified.type, pre, modified.data, notification_event); + } + + if (modifier_exception) + { + std::rethrow_exception(modifier_exception); + } + + return result; + } + + // find the control protocol resource which is assoicated with the given IS-04/IS-05/IS-08 resource id + resources::const_iterator find_control_protocol_resource(resources& resources, type type, const id& resource_id) + { + return find_resource_if(resources, type, [resource_id](const nmos::resource& resource) + { + auto& touchpoints = resource.data.at(nmos::fields::nc::touchpoints); + if (!touchpoints.is_null() && touchpoints.is_array()) + { + auto& tps = touchpoints.as_array(); + auto found_tp = std::find_if(tps.begin(), tps.end(), [resource_id](const web::json::value& touchpoint) + { + auto& resource = nmos::fields::nc::resource(touchpoint); + return (resource_id == nmos::fields::nc::id(resource).as_string() + && nmos::ncp_nmos_resource_types::receiver.name == nmos::fields::nc::resource_type(resource)); + }); + return (tps.end() != found_tp); + } + return false; + }); + } + + // method parameters constraints validation + void method_parameters_contraints_validation(const web::json::value& arguments, const web::json::value& nc_method_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor) + { + for (const auto& param : nmos::fields::nc::parameters(nc_method_descriptor)) + { + const auto& name = nmos::fields::nc::name(param); + const auto& constraints = nmos::fields::nc::constraints(param); + const auto& type_name = param.at(nmos::fields::nc::type_name); + if (arguments.is_null() || !arguments.has_field(name)) + { + // missing argument parameter + throw control_protocol_exception("missing argument parameter " + utility::us2s(name)); + } + details::method_parameter_constraints_validation(arguments.at(name), constraints, { nmos::details::get_datatype_descriptor(type_name, get_control_protocol_datatype_descriptor), get_control_protocol_datatype_descriptor }); + } + } +} diff --git a/Development/nmos/control_protocol_utils.h b/Development/nmos/control_protocol_utils.h new file mode 100644 index 000000000..6d7060851 --- /dev/null +++ b/Development/nmos/control_protocol_utils.h @@ -0,0 +1,84 @@ +#ifndef NMOS_CONTROL_PROTOCOL_UTILS_H +#define NMOS_CONTROL_PROTOCOL_UTILS_H + +#include "cpprest/basic_utils.h" +#include "nmos/control_protocol_handlers.h" + +namespace nmos +{ + struct control_protocol_resource; + + struct control_protocol_exception : std::runtime_error + { + control_protocol_exception(const std::string& message) : std::runtime_error(message) {} + }; + + namespace details + { + // get the runtime property constraints of a given property_id + web::json::value get_runtime_property_constraints(const nc_property_id& property_id, const web::json::value& runtime_property_constraints_list); + + // get the datatype descriptor of a specific type_name + web::json::value get_datatype_descriptor(const web::json::value& type_name, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype); + + // get the datatype property constraints of a given type_name + web::json::value get_datatype_constraints(const web::json::value& type_name, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype); + + struct datatype_constraints_validation_parameters + { + web::json::value datatype_descriptor; + get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor; + }; + // multiple levels of constraints validation, may throw nmos::control_protocol_exception + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Constraints.html + void constraints_validation(const web::json::value& value, const web::json::value& runtime_property_constraints, const web::json::value& property_constraints, const datatype_constraints_validation_parameters& params); + + // method parameter constraints validation, may throw nmos::control_protocol_exception + void method_parameter_constraints_validation(const web::json::value& data, const web::json::value& property_constraints, const datatype_constraints_validation_parameters& params); + } + + // is the given class_id a NcBlock + bool is_nc_block(const nc_class_id& class_id); + + // is the given class_id a NcWorker + bool is_nc_worker(const nc_class_id& class_id); + + // is the given class_id a NcManager + bool is_nc_manager(const nc_class_id& class_id); + + // is the given class_id a NcDeviceManager + bool is_nc_device_manager(const nc_class_id& class_id); + + // is the given class_id a NcClassManager + bool is_nc_class_manager(const nc_class_id& class_id); + + // construct NcClassId + nc_class_id make_nc_class_id(const nc_class_id& prefix, int32_t authority_key, const std::vector& suffix); + nc_class_id make_nc_class_id(const nc_class_id& prefix, const std::vector& suffix); // using default authority_key 0 + + // find control class property descriptor (NcPropertyDescriptor) + web::json::value find_property_descriptor(const nc_property_id& property_id, const nc_class_id& class_id, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor); + + // get block memeber descriptors + void get_member_descriptors(const resources& resources, const resource& resource, bool recurse, web::json::array& descriptors); + + // find members with given role name or fragment + void find_members_by_role(const resources& resources, const resource& resource, const utility::string_t& role, bool match_whole_string, bool case_sensitive, bool recurse, web::json::array& nc_block_member_descriptors); + + // find members with given class id + void find_members_by_class_id(const resources& resources, const resource& resource, const nc_class_id& class_id, bool include_derived, bool recurse, web::json::array& descriptors); + + // push control protocol resource into other control protocol NcBlock resource + void push_back(control_protocol_resource& nc_block_resource, const control_protocol_resource& resource); + + // modify a control protocol resource, and insert notification event to all subscriptions + bool modify_control_protocol_resource(resources& resources, const id& id, std::function modifier, const web::json::value& notification_event); + + // find the control protocol resource which is assoicated with the given IS-04/IS-05/IS-08 resource id + resources::const_iterator find_control_protocol_resource(resources& resources, type type, const id& id); + + // method parameters constraints validation, may throw nmos::control_protocol_exception + void method_parameters_contraints_validation(const web::json::value& arguments, const web::json::value& nc_method_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor); +} + +#endif diff --git a/Development/nmos/control_protocol_ws_api.cpp b/Development/nmos/control_protocol_ws_api.cpp new file mode 100644 index 000000000..30f15ab69 --- /dev/null +++ b/Development/nmos/control_protocol_ws_api.cpp @@ -0,0 +1,538 @@ +#include "nmos/control_protocol_ws_api.h" + +#include +#include +#include "cpprest/json_validator.h" +#include "nmos/api_utils.h" +#include "nmos/control_protocol_resources.h" +#include "nmos/control_protocol_resource.h" +#include "nmos/control_protocol_utils.h" +#include "nmos/is12_versions.h" +#include "nmos/json_schema.h" +#include "nmos/model.h" +#include "nmos/query_utils.h" +#include "nmos/slog.h" + +namespace nmos +{ + namespace details + { + static const web::json::experimental::json_validator& controlprotocol_validator() + { + // hmm, could be based on supported API versions from settings, like other APIs' validators? + static const web::json::experimental::json_validator validator + { + nmos::experimental::load_json_schema, + boost::copy_range>(boost::range::join(boost::range::join( + is12_versions::all | boost::adaptors::transformed(experimental::make_controlprotocolapi_base_message_schema_uri), + is12_versions::all | boost::adaptors::transformed(experimental::make_controlprotocolapi_command_message_schema_uri)), + is12_versions::all | boost::adaptors::transformed(experimental::make_controlprotocolapi_subscription_message_schema_uri) + )) + }; + return validator; + } + + // Validate against specification schema + // throws web::json::json_exception on failure, which results in a 400 Badly-formed command + void validate_controlprotocolapi_base_message_schema(const nmos::api_version& version, const web::json::value& request_data) + { + controlprotocol_validator().validate(request_data, experimental::make_controlprotocolapi_base_message_schema_uri(version)); + } + void validate_controlprotocolapi_command_message_schema(const nmos::api_version& version, const web::json::value& request_data) + { + controlprotocol_validator().validate(request_data, experimental::make_controlprotocolapi_command_message_schema_uri(version)); + } + void validate_controlprotocolapi_subscription_message_schema(const nmos::api_version& version, const web::json::value& request_data) + { + controlprotocol_validator().validate(request_data, experimental::make_controlprotocolapi_subscription_message_schema_uri(version)); + } + } + + // IS-12 Control Protocol WebSocket API + + web::websockets::experimental::listener::validate_handler make_control_protocol_ws_validate_handler(nmos::node_model& model, nmos::experimental::ws_validate_authorization_handler ws_validate_authorization, slog::base_gate& gate_) + { + return [&model, ws_validate_authorization, &gate_](web::http::http_request req) + { + nmos::ws_api_gate gate(gate_, req.request_uri()); + + // RFC 6750 defines two methods of sending bearer access tokens which are applicable to WebSocket + // Clients SHOULD use the "Authorization Request Header Field" method. + // Clients MAY use a "URI Query Parameter". + // See https://tools.ietf.org/html/rfc6750#section-2 + if (ws_validate_authorization) + { + if (!ws_validate_authorization(req, nmos::experimental::scopes::ncp)) { return false; } + } + + // For now just return true + const auto& ws_ncp_path = req.request_uri().path(); + slog::log(gate, SLOG_FLF) << "Validating websocket connection to: " << ws_ncp_path; + + return true; + }; + } + + web::websockets::experimental::listener::open_handler make_control_protocol_ws_open_handler(nmos::node_model& model, nmos::websockets& websockets, slog::base_gate& gate_) + { + using web::json::value; + using web::json::value_of; + + return [&model, &websockets, &gate_](const web::uri& connection_uri, const web::websockets::experimental::listener::connection_id& connection_id) + { + nmos::ws_api_gate gate(gate_, connection_uri); + auto lock = model.write_lock(); + auto& resources = model.control_protocol_resources; + + const auto& ws_ncp_path = connection_uri.path(); + slog::log(gate, SLOG_FLF) << "Opening websocket connection to: " << ws_ncp_path; + + // create a subscription (1-1 relationship with the connection) + resources::const_iterator subscription; + + { + const bool secure = nmos::experimental::fields::client_secure(model.settings); + + const auto ws_href = web::uri_builder() + .set_scheme(web::ws_scheme(secure)) + .set_host(nmos::get_host(model.settings)) + .set_port(nmos::fields::control_protocol_ws_port(model.settings)) + .set_path(ws_ncp_path) + .to_uri(); + + const utility::string_t control_protocol_resource_path; + + const bool non_persistent = false; + value data = value_of({ + { nmos::fields::id, nmos::make_id() }, + { nmos::fields::max_update_rate_ms, 0 }, + { nmos::fields::resource_path, control_protocol_resource_path }, + { nmos::fields::params, value_of({ { U("query.rql"), U("in(id,())") } }) }, + { nmos::fields::persist, non_persistent }, + { nmos::fields::secure, secure }, + { nmos::fields::ws_href, ws_href.to_string() } + }, true); + + // hm, could version be determined from ws_resource_path? + nmos::resource subscription_{ is12_versions::v1_0, nmos::types::subscription, std::move(data), non_persistent }; + + subscription = insert_resource(resources, std::move(subscription_)).first; + } + + { + // create a websocket connection resource + + value data; + nmos::id id = nmos::make_id(); + data[nmos::fields::id] = value::string(id); + data[nmos::fields::subscription_id] = value::string(subscription->id); + + // create an initial websocket message with no data + + const auto resource_path = nmos::fields::resource_path(subscription->data); + const auto topic = resource_path + U('/'); + data[nmos::fields::message] = details::make_grain({}, {}, topic); + + resource grain{ is12_versions::v1_0, nmos::types::grain, std::move(data), false }; + insert_resource(resources, std::move(grain)); + + websockets.insert({ id, connection_id }); + + slog::log(gate, SLOG_FLF) << "Creating websocket connection: " << id << " to subscription: " << subscription->id; + + slog::log(gate, SLOG_FLF) << "Notifying control protocol websockets thread"; // and anyone else who cares... + model.notify(); + } + }; + } + + web::websockets::experimental::listener::close_handler make_control_protocol_ws_close_handler(nmos::node_model& model, nmos::websockets& websockets, slog::base_gate& gate_) + { + return [&model, &websockets, &gate_](const web::uri& connection_uri, const web::websockets::experimental::listener::connection_id& connection_id, web::websockets::websocket_close_status close_status, const utility::string_t& close_reason) + { + nmos::ws_api_gate gate(gate_, connection_uri); + auto lock = model.write_lock(); + auto& resources = model.control_protocol_resources; + + const auto& ws_ncp_path = connection_uri.path(); + slog::log(gate, SLOG_FLF) << "Closing websocket connection to: " << ws_ncp_path << " [" << (int)close_status << ": " << close_reason << "]"; + + auto websocket = websockets.right.find(connection_id); + if (websockets.right.end() != websocket) + { + auto grain = find_resource(resources, { websocket->second, nmos::types::grain }); + + if (resources.end() != grain) + { + slog::log(gate, SLOG_FLF) << "Deleting websocket connection: " << grain->id; + + // subscriptions have a 1-1 relationship with the websocket connection and both should now be erased immediately + auto subscription = find_resource(resources, { nmos::fields::subscription_id(grain->data), nmos::types::subscription }); + + if (resources.end() != subscription) + { + // this should erase grain too, as a subscription's subresource + erase_resource(resources, subscription->id); + } + else + { + // a grain without a subscription shouldn't be possible, but let's be tidy + erase_resource(resources, grain->id); + } + } + + websockets.right.erase(websocket); + + model.notify(); + } + }; + } + + web::websockets::experimental::listener::message_handler make_control_protocol_ws_message_handler(nmos::node_model& model, nmos::websockets& websockets, nmos::get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, nmos::get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, nmos::get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor, nmos::control_protocol_property_changed_handler property_changed, slog::base_gate& gate_) + { + using web::json::value; + using web::json::value_of; + + return [&model, &websockets, get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor, property_changed, &gate_](const web::uri& connection_uri, const web::websockets::experimental::listener::connection_id& connection_id, const web::websockets::websocket_incoming_message& msg_) + { + nmos::ws_api_gate gate(gate_, connection_uri); + + auto lock = model.write_lock(); + auto& resources = model.control_protocol_resources; + + // theoretically blocking, but in fact not + auto msg = msg_.extract_string().get(); + + const auto& ws_ncp_path = connection_uri.path(); + slog::log(gate, SLOG_FLF) << "Received websocket message: " << msg << " on connection: " << ws_ncp_path; + + auto websocket = websockets.right.find(connection_id); + if (websockets.right.end() != websocket) + { + auto grain = find_resource(resources, { websocket->second, nmos::types::grain }); + + if (resources.end() != grain) + { + auto subscription = find_resource(resources, { nmos::fields::subscription_id(grain->data), nmos::types::subscription }); + + if (resources.end() != subscription) + { + try + { + // extract the control protocol api version from the ws_ncp_path + if (web::uri::split_path(ws_ncp_path).empty()) { throw std::invalid_argument("empty URL"); } + const auto version = nmos::parse_api_version(web::uri::split_path(ws_ncp_path).back()); + + // convert message to JSON + const auto message = value::parse(utility::conversions::to_string_t(msg)); + + // validate the base-message + details::validate_controlprotocolapi_base_message_schema(version, message); + + const auto msg_type = nmos::fields::nc::message_type(message); + switch (msg_type) + { + // See https://specs.amwa.tv/is-12/branches/v1.0.x/docs/Protocol_messaging.html#command-message-type + case ncp_message_type::command: + { + // validate command-message + details::validate_controlprotocolapi_command_message_schema(version, message); + + auto responses = value::array(); + auto& commands = nmos::fields::nc::commands(message); + for (const auto& cmd : commands) + { + const auto handle = nmos::fields::nc::handle(cmd); + const auto oid = nmos::fields::nc::oid(cmd); + + // get methodId + const auto& method_id = nmos::details::parse_nc_method_id(nmos::fields::nc::method_id(cmd)); + + // get arguments + const auto& arguments = nmos::fields::nc::arguments(cmd); + + value response; + + auto resource = nmos::find_resource(resources, utility::s2us(std::to_string(oid))); + if (resources.end() != resource) + { + const auto& class_id = nmos::details::parse_nc_class_id(nmos::fields::nc::class_id(resource->data)); + + // find the relevent method handler to execute + // method tuple definition described in control_protocol_handlers.h + auto method = get_control_protocol_method_descriptor(class_id, method_id); + auto& nc_method_descriptor = std::get<0>(method); + auto& standard_method = std::get<1>(method); + auto& non_standard_method = std::get<2>(method); + if (standard_method || non_standard_method) + { + try + { + // do method arguments constraints validation + method_parameters_contraints_validation(arguments, nc_method_descriptor, get_control_protocol_datatype_descriptor); + + // execute the relevant method handler, then accumulating up their response to reponses + if (standard_method) + { + response = standard_method(resources, *resource, handle, arguments, nmos::fields::nc::is_deprecated(nc_method_descriptor), get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor, property_changed, gate); + } + else // non_standard_method + { + response = non_standard_method(resources, *resource, handle, arguments, nmos::fields::nc::is_deprecated(nc_method_descriptor), gate); + } + } + catch (const nmos::control_protocol_exception& e) + { + // invalid arguments + slog::log(gate, SLOG_FLF) << "invalid argument: " << arguments.serialize() << " error: " << e.what(); + response = make_control_protocol_message_response(handle, { nmos::nc_method_status::parameter_error }); + } + } + else + { + // unknown methodId + utility::stringstream_t ss; + ss << U("unsupported method_id: ") << nmos::fields::nc::method_id(cmd).serialize() + << U(" for control class class_id: ") << resource->data.at(nmos::fields::nc::class_id).serialize(); + response = make_control_protocol_error_response(handle, { nc_method_status::method_not_implemented }, ss.str()); + } + } + else + { + // resource not found for the given oid + utility::stringstream_t ss; + ss << U("unknown oid: ") << oid; + response = make_control_protocol_error_response(handle, { nc_method_status::bad_oid }, ss.str()); + } + // accumulating up response + web::json::push_back(responses, response); + } + + // add command_response to the grain ready to transfer to the client in nmos::send_control_protocol_ws_messages_thread + resources.modify(grain, [&](nmos::resource& grain) + { + web::json::push_back(nmos::fields::message_grain_data(grain.data), make_control_protocol_message_response(responses)); + + grain.updated = strictly_increasing_update(resources); + }); + } + break; + // See https://specs.amwa.tv/is-12/branches/v1.0.x/docs/Protocol_messaging.html#subscription-message-type + case ncp_message_type::subscription: + { + // validate subscription-message + details::validate_controlprotocolapi_subscription_message_schema(version, message); + + // subscribing to multiple OIDs, and filtering out invalid OIDs which cannot be subscribed to + auto& subscriptions = nmos::fields::nc::subscriptions(message); + value valid_subscriptions = value::array(); + for (const auto& subscription : subscriptions) + { + const auto oid = subscription.as_integer(); + auto resource = nmos::find_resource(resources, utility::s2us(std::to_string(oid))); + if (resources.end() != resource) + { + // only add the valid OIDs which can be subscribed to + web::json::push_back(valid_subscriptions, subscription); + } + } + + // update the subscription + modify_resource(resources, subscription->id, [&valid_subscriptions](nmos::resource& resource) + { + auto rql_query = U("in(id,(") + boost::algorithm::join(valid_subscriptions.as_array() | boost::adaptors::transformed([](const value& v) { return U("string:") + utility::s2us(std::to_string(v.as_integer())); }), U(",")) + U("))"); + + resource.data[nmos::fields::params] = value_of({ { U("query.rql"), rql_query } }); + }); + + // add subscription_response to the grain ready to transfer to the client in nmos::send_control_protocol_ws_messages_thread + resources.modify(grain, [&](nmos::resource& grain) + { + web::json::push_back(nmos::fields::message_grain_data(grain.data), make_control_protocol_subscription_response(valid_subscriptions)); + + grain.updated = strictly_increasing_update(resources); + }); + + slog::log(gate, SLOG_FLF) << "Received subscription command for " << valid_subscriptions.serialize(); + model.notify(); + } + break; + default: + // ignore unexpected message type + break; + } + + } + catch (const web::json::json_exception& e) + { + slog::log(gate, SLOG_FLF) << "JSON error: " << e.what(); + + resources.modify(grain, [&](nmos::resource& grain) + { + web::json::push_back(nmos::fields::message_grain_data(grain.data), + make_control_protocol_error_message({ nc_method_status::bad_command_format }, utility::s2us(e.what()))); + + grain.updated = strictly_increasing_update(resources); + }); + } + catch (const std::exception& e) + { + slog::log(gate, SLOG_FLF) << "Unexpected exception while handing control protocol command: " << e.what(); + + resources.modify(grain, [&](nmos::resource& grain) + { + web::json::push_back(nmos::fields::message_grain_data(grain.data), + make_control_protocol_error_message({ nc_method_status::bad_command_format }, utility::s2us(std::string("Unexpected exception while handing control protocol command : ") + e.what()))); + + grain.updated = strictly_increasing_update(resources); + }); + } + catch (...) + { + slog::log(gate, SLOG_FLF) << "Unexpected unknown exception for handing control protocol command"; + + resources.modify(grain, [&](nmos::resource& grain) + { + web::json::push_back(nmos::fields::message_grain_data(grain.data), + make_control_protocol_error_message({ nc_method_status::bad_command_format }, U("Unexpected unknown exception while handing control protocol command"))); + + grain.updated = strictly_increasing_update(resources); + }); + } + model.notify(); + } + } + } + }; + } + + // observe_websocket_exception is the same as the one defined in events_ws_api + namespace details + { + struct observe_websocket_exception + { + observe_websocket_exception(slog::base_gate& gate) : gate(gate) {} + + void operator()(pplx::task finally) + { + try + { + finally.get(); + } + catch (const web::websockets::websocket_exception& e) + { + slog::log(gate, SLOG_FLF) << "WebSocket error: " << e.what() << " [" << e.error_code() << "]"; + } + } + + slog::base_gate& gate; + }; + } + + void send_control_protocol_ws_messages_thread(web::websockets::experimental::listener::websocket_listener& listener, nmos::node_model& model, nmos::websockets& websockets, slog::base_gate& gate_) + { + nmos::details::omanip_gate gate(gate_, nmos::stash_category(nmos::categories::send_control_protocol_ws_messages)); + + using web::json::value; + using web::json::value_of; + + // could start out as a shared/read lock, only upgraded to an exclusive/write lock when a grain in the resources is actually modified + auto lock = model.write_lock(); + auto& condition = model.condition; + auto& shutdown = model.shutdown; + auto& resources = model.control_protocol_resources; + + tai most_recent_message{}; + auto earliest_necessary_update = (tai_clock::time_point::max)(); + + for (;;) + { + // wait for the thread to be interrupted either because there are resource changes, or because the server is being shut down + // or because message sending was throttled earlier + details::wait_until(condition, lock, earliest_necessary_update, [&] { return shutdown || most_recent_message < most_recent_update(resources); }); + if (shutdown) break; + most_recent_message = most_recent_update(resources); + + slog::log(gate, SLOG_FLF) << "Got notification on control protocol websockets thread"; + + earliest_necessary_update = (tai_clock::time_point::max)(); + + std::vector> outgoing_messages; + + for (auto wit = websockets.left.begin(); websockets.left.end() != wit;) + { + const auto& websocket = *wit; + + // for each websocket connection that has valid grain and subscription resources + const auto grain = find_resource(resources, { websocket.first, nmos::types::grain }); + if (resources.end() == grain) + { + auto close = listener.close(websocket.second, web::websockets::websocket_close_status::server_terminate, U("Expired")) + .then(details::observe_websocket_exception(gate)); + // theoretically blocking, but in fact not + close.wait(); + + wit = websockets.left.erase(wit); + continue; + } + const auto subscription = find_resource(resources, { nmos::fields::subscription_id(grain->data), nmos::types::subscription }); + if (resources.end() == subscription) + { + // a grain without a subscription shouldn't be possible, but let's be tidy + erase_resource(resources, grain->id); + + auto close = listener.close(websocket.second, web::websockets::websocket_close_status::server_terminate, U("Expired")) + .then(details::observe_websocket_exception(gate)); + // theoretically blocking, but in fact not + close.wait(); + + wit = websockets.left.erase(wit); + continue; + } + // and has events to send + if (0 == nmos::fields::message_grain_data(grain->data).size()) + { + ++wit; + continue; + } + + slog::log(gate, SLOG_FLF) << "Preparing to send " << nmos::fields::message_grain_data(grain->data).size() << " events on websocket connection: " << grain->id; + + for (const auto& event : nmos::fields::message_grain_data(grain->data).as_array()) + { + web::websockets::websocket_outgoing_message message; + + slog::log(gate, SLOG_FLF) << "outgoing_message: " << event.serialize(); + message.set_utf8_message(utility::us2s(event.serialize())); + outgoing_messages.push_back({ websocket.second, message }); + } + + // reset the grain for next time + resources.modify(grain, [&resources](nmos::resource& grain) + { + // all messages have now been prepared + nmos::fields::message_grain_data(grain.data) = value::array(); + grain.updated = strictly_increasing_update(resources); + }); + + ++wit; + } + + // send the messages without the lock on resources + details::reverse_lock_guard unlock{ lock }; + + if (!outgoing_messages.empty()) slog::log(gate, SLOG_FLF) << "Sending " << outgoing_messages.size() << " websocket messages"; + + for (auto& outgoing_message : outgoing_messages) + { + // hmmm, no way to cancel this currently... + + auto send = listener.send(outgoing_message.first, outgoing_message.second) + .then(details::observe_websocket_exception(gate)); + // current websocket_listener implementation is synchronous in any case, but just to make clear... + // for now, wait for the message to be sent + send.wait(); + } + } + } +} diff --git a/Development/nmos/control_protocol_ws_api.h b/Development/nmos/control_protocol_ws_api.h new file mode 100644 index 000000000..3cfee6d50 --- /dev/null +++ b/Development/nmos/control_protocol_ws_api.h @@ -0,0 +1,35 @@ +#ifndef NMOS_CONTROL_PROTOCOL_WS_API_H +#define NMOS_CONTROL_PROTOCOL_WS_API_H + +#include "nmos/control_protocol_handlers.h" +#include "nmos/websockets.h" +#include "nmos/ws_api_utils.h" + +namespace slog +{ + class base_gate; +} + +namespace nmos +{ + struct node_model; + + web::websockets::experimental::listener::validate_handler make_control_protocol_ws_validate_handler(nmos::node_model& model, nmos::experimental::ws_validate_authorization_handler ws_validate_authorization, slog::base_gate& gate); + web::websockets::experimental::listener::open_handler make_control_protocol_ws_open_handler(nmos::node_model& model, nmos::websockets& websockets, slog::base_gate& gate); + web::websockets::experimental::listener::close_handler make_control_protocol_ws_close_handler(nmos::node_model& model, nmos::websockets& websockets, slog::base_gate& gate); + web::websockets::experimental::listener::message_handler make_control_protocol_ws_message_handler(nmos::node_model& model, nmos::websockets& websockets, nmos::get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, nmos::get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, nmos::get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor, nmos::control_protocol_property_changed_handler property_changed, slog::base_gate& gate); + + inline web::websockets::experimental::listener::websocket_listener_handlers make_control_protocol_ws_api(nmos::node_model& model, nmos::websockets& websockets, nmos::experimental::ws_validate_authorization_handler ws_validate_authorization, nmos::get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, nmos::get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, nmos::get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor, nmos::control_protocol_property_changed_handler property_changed, slog::base_gate& gate) + { + return{ + nmos::make_control_protocol_ws_validate_handler(model, ws_validate_authorization, gate), + nmos::make_control_protocol_ws_open_handler(model, websockets, gate), + nmos::make_control_protocol_ws_close_handler(model, websockets, gate), + nmos::make_control_protocol_ws_message_handler(model, websockets, get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor, property_changed, gate) + }; + } + + void send_control_protocol_ws_messages_thread(web::websockets::experimental::listener::websocket_listener& listener, nmos::node_model& model, nmos::websockets& websockets, slog::base_gate& gate_); +} + +#endif diff --git a/Development/nmos/is12_schemas/is12_schemas.h b/Development/nmos/is12_schemas/is12_schemas.h new file mode 100644 index 000000000..392b57648 --- /dev/null +++ b/Development/nmos/is12_schemas/is12_schemas.h @@ -0,0 +1,25 @@ +#ifndef NMOS_IS12_SCHEMAS_H +#define NMOS_IS12_SCHEMAS_H + +// Extern declarations for auto-generated constants +// could be auto-generated, but isn't currently! +namespace nmos +{ + namespace is12_schemas + { + namespace v1_0_x + { + extern const char* base_message; + extern const char* command_message; + extern const char* command_response_message; + extern const char* error_message; + extern const char* event_data; + extern const char* notification_message; + extern const char* property_changed_event_data; + extern const char* subscription_message; + extern const char* subscription_response_message; + } + } +} + +#endif diff --git a/Development/nmos/is12_versions.h b/Development/nmos/is12_versions.h new file mode 100644 index 000000000..06dfc1d59 --- /dev/null +++ b/Development/nmos/is12_versions.h @@ -0,0 +1,26 @@ +#ifndef NMOS_IS12_VERSIONS_H +#define NMOS_IS12_VERSIONS_H + +#include +#include +#include "nmos/api_version.h" +#include "nmos/settings.h" + +namespace nmos +{ + namespace is12_versions + { + const api_version v1_0{ 1, 0 }; + + const std::set all{ nmos::is12_versions::v1_0 }; + + inline std::set from_settings(const nmos::settings& settings) + { + return settings.has_field(nmos::fields::is12_versions) + ? boost::copy_range>(nmos::fields::is12_versions(settings) | boost::adaptors::transformed([](const web::json::value& v) { return nmos::parse_api_version(v.as_string()); })) + : nmos::is12_versions::all; + } + } +} + +#endif diff --git a/Development/nmos/json_fields.h b/Development/nmos/json_fields.h index eb243b04d..7374c5a90 100644 --- a/Development/nmos/json_fields.h +++ b/Development/nmos/json_fields.h @@ -230,6 +230,114 @@ namespace nmos const web::json::field_as_string hostname{ U("hostname") }; // hostname, ipv4 or ipv6 const web::json::field_as_integer port{ U("port") }; // 1..65535 + // IS-12 Control Protocol and MS-05 model definitions + namespace nc + { + // for control_protocol_ws_api + const web::json::field_as_integer message_type{ U("messageType") }; + + // for control_protocol_ws_api commands + const web::json::field_as_array commands{ U("commands") }; + const web::json::field_as_array subscriptions{ U("subscriptions") }; + const web::json::field_as_integer oid{ U("oid") }; + const web::json::field_as_value method_id{ U("methodId") }; + const web::json::field_as_value_or arguments{ U("arguments"), {} }; + const web::json::field_as_value id{ U("id") }; + const web::json::field_as_integer level{ U("level") }; + const web::json::field_as_integer index{ U("index") }; + + // for control_protocol_ws_api responses & errors + const web::json::field_as_value responses{ U("responses") }; + const web::json::field_as_value result{ U("result") }; + const web::json::field_as_integer status{ U("status") }; + const web::json::field_as_value value{ U("value") }; + const web::json::field_as_string error_message{ U("errorMessage") }; + + // for control_protocol_ws_api commands & responses + const web::json::field_as_integer handle{ U("handle") }; + + // for cntrol_protocol_ws_api notifications + const web::json::field_as_array notifications{ U("notifications") }; + const web::json::field_as_value event_data{ U("eventData") }; + const web::json::field_as_value event_id{ U("eventId") }; + + const web::json::field_as_array class_id{ U("classId") }; + const web::json::field_as_bool constant_oid{ U("constantOid") }; + const web::json::field_as_integer owner{ U("owner") }; + const web::json::field_as_string role{ U("role") }; + const web::json::field_as_string user_label{ U("userLabel") }; + const web::json::field_as_array touchpoints{ U("touchpoints") }; + const web::json::field_as_array runtime_property_constraints{ U("runtimePropertyConstraints") }; + const web::json::field_as_bool recurse{ U("recurse") }; + const web::json::field_as_bool enabled{ U("enabled") }; + const web::json::field_as_array members{ U("members") }; + const web::json::field_as_string description{ U("description") }; + const web::json::field_as_string nc_version{ U("ncVersion") }; // NcVersionCode + const web::json::field_as_value manufacturer{ U("manufacturer") }; // NcManufacturer + const web::json::field_as_value product{ U("product") }; // NcProduct + const web::json::field_as_string serial_number{ U("serialNumber") }; + const web::json::field_as_string user_inventory_code{ U("userInventoryCode") }; + const web::json::field_as_string device_name{ U("deviceName") }; + const web::json::field_as_string device_role{ U("deviceRole") }; + const web::json::field_as_value operational_state{ U("operationalState") }; // NcDeviceOperationalState + const web::json::field_as_integer reset_cause{ U("resetCause") }; // NcResetCause + const web::json::field_as_string message{ U("message") }; + const web::json::field_as_array control_classes{ U("controlClasses") }; // sequence + const web::json::field_as_array datatypes{ U("datatypes") }; // sequence + const web::json::field_as_string name{ U("name")}; + const web::json::field_as_string fixed_role{ U("fixedRole") }; + const web::json::field_as_array properties{ U("properties") }; // sequence + const web::json::field_as_array methods{ U("methods") }; // sequence + const web::json::field_as_array events{ U("events") }; // sequence + const web::json::field_as_integer type{ U("type") }; // NcDatatypeType + const web::json::field_as_value constraints{ U("constraints") }; // NcParameterConstraints + const web::json::field_as_integer organization_id{ U("organizationId") }; + const web::json::field_as_string website{ U("website") }; + const web::json::field_as_string key{ U("key") }; + const web::json::field_as_string revision_level{ U("revisionLevel") }; + const web::json::field_as_string brand_name{ U("brandName") }; + const web::json::field_as_string uuid{ U("uuid") }; + const web::json::field_as_string type_name{ U("typeName") }; + const web::json::field_as_bool is_read_only{ U("isReadOnly") }; + const web::json::field_as_bool is_persistent{ U("isPersistent") }; + const web::json::field_as_bool is_nullable{ U("isNullable") }; + const web::json::field_as_bool is_sequence{ U("isSequence") }; + const web::json::field_as_bool is_deprecated{ U("isDeprecated") }; + const web::json::field_as_bool is_constant{ U("isConstant") }; + const web::json::field_as_string parent_type{ U("parentType") }; + const web::json::field_as_string event_datatype{ U("eventDatatype") }; + const web::json::field_as_string result_datatype{ U("resultDatatype") }; + const web::json::field_as_array parameters{ U("parameters") }; + const web::json::field_as_array items{ U("items") }; // sequence + const web::json::field_as_array fields{ U("fields") }; // sequence + const web::json::field_as_integer generic_state{ U("generic") }; // NcDeviceGenericState + const web::json::field_as_string device_specific_details{ U("deviceSpecificDetails") }; + const web::json::field_as_array path{ U("path") }; // NcRolePath + const web::json::field_as_bool case_sensitive{ U("caseSensitive") }; + const web::json::field_as_bool match_whole_string{ U("matchWholeString") }; + const web::json::field_as_bool include_derived{ U("includeDerived") }; + const web::json::field_as_bool include_inherited{ U("includeInherited") }; + const web::json::field_as_string context_namespace{ U("contextNamespace") }; + const web::json::field_as_value default_value{ U("defaultValue") }; + const web::json::field_as_integer change_type{ U("changeType") }; // NcPropertyChangeType + const web::json::field_as_integer sequence_item_index{ U("sequenceItemIndex") }; // NcId + const web::json::field_as_value property_id{ U("propertyId") }; + const web::json::field_as_value maximum{ U("maximum") }; + const web::json::field_as_value minimum{ U("minimum") }; + const web::json::field_as_value step{ U("step") }; + const web::json::field_as_integer max_characters{ U("maxCharacters") }; + const web::json::field_as_string pattern{ U("pattern") }; + const web::json::field_as_value resource{ U("resource") }; + const web::json::field_as_string resource_type{ U("resourceType") }; + const web::json::field_as_string io_id{ U("ioId") }; + const web::json::field_as_integer connection_status{ U("connectionStatus") }; // NcConnectionStatus + const web::json::field_as_string connection_status_message{ U("connectionStatusMessage") }; + const web::json::field_as_integer payload_status{ U("payloadStatus") }; // NcPayloadStatus + const web::json::field_as_string payload_status_message{ U("payloadStatusMessage") }; + const web::json::field_as_bool signal_protection_status{ U("signalProtectionStatus") }; + const web::json::field_as_bool active{ U("active") }; + } + // NMOS Parameter Registers // Sender Attributes Register diff --git a/Development/nmos/json_schema.cpp b/Development/nmos/json_schema.cpp index 2a5bb89ae..d24f2a5df 100644 --- a/Development/nmos/json_schema.cpp +++ b/Development/nmos/json_schema.cpp @@ -10,6 +10,8 @@ #include "nmos/is09_versions.h" #include "nmos/is09_schemas/is09_schemas.h" #include "nmos/is10_schemas/is10_schemas.h" +#include "nmos/is12_versions.h" +#include "nmos/is12_schemas/is12_schemas.h" #include "nmos/type.h" namespace nmos @@ -149,6 +151,25 @@ namespace nmos const web::uri authapi_token_schema_schema_uri = make_schema_uri(tag, _XPLATSTR("token_schema.json")); } } + + namespace is12_schemas + { + web::uri make_schema_uri(const utility::string_t& tag, const utility::string_t& ref = {}) + { + return{ _XPLATSTR("https://github.com/AMWA-TV/is-12/raw/") + tag + _XPLATSTR("/APIs/schemas/") + ref }; + } + + // See https://github.com/AMWA-TV/is-12/tree/v1.0-dev/APIs/schemas/ + namespace v1_0 + { + using namespace nmos::is12_schemas::v1_0_x; + const utility::string_t tag(_XPLATSTR("v1.0.x")); + + const web::uri controlprotocolapi_base_message_schema_uri = make_schema_uri(tag, _XPLATSTR("base-message.json")); + const web::uri controlprotocolapi_command_message_schema_uri = make_schema_uri(tag, _XPLATSTR("command-message.json")); + const web::uri controlprotocolapi_subscription_message_schema_uri = make_schema_uri(tag, _XPLATSTR("subscription-message.json")); + } + } } namespace nmos @@ -351,6 +372,25 @@ namespace nmos }; } + static std::map make_is12_schemas() + { + using namespace nmos::is12_schemas; + + return + { + // v1.0 + { make_schema_uri(v1_0::tag, _XPLATSTR("base-message.json")), make_schema(v1_0::base_message) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("command-message.json")), make_schema(v1_0::command_message) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("command-response-message.json")), make_schema(v1_0::command_response_message) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("error-message.json")), make_schema(v1_0::error_message) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("event-data.json")), make_schema(v1_0::event_data) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("notification-message.json")), make_schema(v1_0::notification_message) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("property-changed-event-data.json")), make_schema(v1_0::property_changed_event_data) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("subscription-message.json")), make_schema(v1_0::subscription_message) }, + { make_schema_uri(v1_0::tag, _XPLATSTR("subscription-response-message.json")), make_schema(v1_0::subscription_response_message) } + }; + } + inline void merge(std::map& to, std::map&& from) { to.insert(from.begin(), from.end()); // std::map::merge in C++17 @@ -363,6 +403,7 @@ namespace nmos merge(result, make_is08_schemas()); merge(result, make_is09_schemas()); merge(result, make_is10_schemas()); + merge(result, make_is12_schemas()); return result; } @@ -454,6 +495,21 @@ namespace nmos return is10_schemas::v1_0::authapi_token_response_schema_uri; } + web::uri make_controlprotocolapi_base_message_schema_uri(const nmos::api_version& version) + { + return is12_schemas::v1_0::controlprotocolapi_base_message_schema_uri; + } + + web::uri make_controlprotocolapi_command_message_schema_uri(const nmos::api_version& version) + { + return is12_schemas::v1_0::controlprotocolapi_command_message_schema_uri; + } + + web::uri make_controlprotocolapi_subscription_message_schema_uri(const nmos::api_version& version) + { + return is12_schemas::v1_0::controlprotocolapi_subscription_message_schema_uri; + } + // load the json schema for the specified base URI web::json::value load_json_schema(const web::uri& id) { diff --git a/Development/nmos/json_schema.h b/Development/nmos/json_schema.h index 4c8c7b60a..57cb0996b 100644 --- a/Development/nmos/json_schema.h +++ b/Development/nmos/json_schema.h @@ -36,6 +36,10 @@ namespace nmos web::uri make_authapi_token_schema_schema_uri(const nmos::api_version& version); web::uri make_authapi_token_response_schema_uri(const nmos::api_version& version); + web::uri make_controlprotocolapi_base_message_schema_uri(const nmos::api_version& version); + web::uri make_controlprotocolapi_command_message_schema_uri(const nmos::api_version& version); + web::uri make_controlprotocolapi_subscription_message_schema_uri(const nmos::api_version& version); + // load the json schema for the specified base URI web::json::value load_json_schema(const web::uri& id); } diff --git a/Development/nmos/model.h b/Development/nmos/model.h index d5c6b9f99..d9c25559c 100644 --- a/Development/nmos/model.h +++ b/Development/nmos/model.h @@ -101,6 +101,10 @@ namespace nmos // IS-08 inputs and outputs for this node // see nmos/channelmapping_resources.h nmos::resources channelmapping_resources; + + // IS-12 resources for this node + // see nmos/control_protocol_resources.h + nmos::resources control_protocol_resources; }; struct registry_model : model diff --git a/Development/nmos/node_resources.cpp b/Development/nmos/node_resources.cpp index e177c833d..2d6d8d232 100644 --- a/Development/nmos/node_resources.cpp +++ b/Development/nmos/node_resources.cpp @@ -16,6 +16,7 @@ #include "nmos/is05_versions.h" #include "nmos/is07_versions.h" #include "nmos/is08_versions.h" +#include "nmos/is12_versions.h" #include "nmos/media_type.h" #include "nmos/resource.h" #include "nmos/sdp_utils.h" // for nmos::make_components @@ -129,6 +130,28 @@ namespace nmos } } + if (0 <= nmos::fields::control_protocol_ws_port(settings)) + { + for (const auto& version : nmos::is12_versions::from_settings(settings)) + { + // See https://specs.amwa.tv/is-12/branches/v1.0.x/docs/IS-04_interactions.html + auto ncp_uri = web::uri_builder() + .set_scheme(nmos::ws_scheme(settings)) + .set_port(nmos::fields::control_protocol_ws_port(settings)) + .set_path(U("/x-nmos/ncp/") + make_api_version(version)); + auto type = U("urn:x-nmos:control:ncp/") + make_api_version(version); + + for (const auto& host : hosts) + { + web::json::push_back(data[U("controls")], value_of({ + { U("href"), ncp_uri.set_host(host).to_uri().to_string() }, + { U("type"), type }, + { U("authorization"), nmos::experimental::fields::server_authorization(settings) } + })); + } + } + } + return{ is04_versions::v1_3, types::device, std::move(data), false }; } diff --git a/Development/nmos/node_server.cpp b/Development/nmos/node_server.cpp index 3202e5e61..00258389d 100644 --- a/Development/nmos/node_server.cpp +++ b/Development/nmos/node_server.cpp @@ -3,6 +3,7 @@ #include "cpprest/ws_utils.h" #include "nmos/api_utils.h" #include "nmos/channelmapping_activation.h" +#include "nmos/control_protocol_ws_api.h" #include "nmos/events_api.h" #include "nmos/events_ws_api.h" #include "nmos/is04_versions.h" @@ -73,9 +74,20 @@ namespace nmos node_server.api_routers[{ {}, nmos::fields::channelmapping_port(node_model.settings) }].mount({}, nmos::make_channelmapping_api(node_model, node_implementation.validate_map, validate_authorization ? validate_authorization(nmos::experimental::scopes::channelmapping) : nullptr, gate)); + const auto& events_ws_port = nmos::fields::events_ws_port(node_model.settings); auto& events_ws_api = node_server.ws_handlers[{ {}, nmos::fields::events_ws_port(node_model.settings) }]; events_ws_api.first = nmos::make_events_ws_api(node_model, events_ws_api.second, node_implementation.ws_validate_authorization, gate); + // can't share a port between the events ws and the control protocol ws + const auto& control_protocol_enabled = (0 <= nmos::fields::control_protocol_ws_port(node_model.settings)); + const auto& control_protocol_ws_port = nmos::fields::control_protocol_ws_port(node_model.settings); + if (control_protocol_enabled) + { + if (control_protocol_ws_port == events_ws_port) throw std::runtime_error("Same port used for events and control protocol websockets are not supported"); + auto& control_protocol_ws_api = node_server.ws_handlers[{ {}, control_protocol_ws_port }]; + control_protocol_ws_api.first = nmos::make_control_protocol_ws_api(node_model, control_protocol_ws_api.second, node_implementation.ws_validate_authorization, node_implementation.get_control_protocol_class_descriptor, node_implementation.get_control_protocol_datatype_descriptor, node_implementation.get_control_protocol_method_descriptor, node_implementation.control_protocol_property_changed, gate); + } + // Set up the listeners for each HTTP API port auto http_config = nmos::make_http_listener_config(node_model.settings, node_implementation.load_server_certificates, node_implementation.load_dh_param, node_implementation.get_ocsp_response, gate); @@ -94,6 +106,10 @@ namespace nmos auto websocket_config = nmos::make_websocket_listener_config(node_model.settings, node_implementation.load_server_certificates, node_implementation.load_dh_param, node_implementation.get_ocsp_response, gate); websocket_config.set_log_callback(nmos::make_slog_logging_callback(gate)); + size_t event_ws_pos{ 0 }; + bool found_event_ws{ false }; + size_t control_protocol_ws_pos{ 0 }; + bool found_control_protocol_ws{ false }; for (auto& ws_handler : node_server.ws_handlers) { // if IP address isn't specified for this router, use default server address or wildcard address @@ -101,9 +117,21 @@ namespace nmos // map the configured client port to the server port on which to listen // hmm, this should probably also take account of the address node_server.ws_listeners.push_back(nmos::make_ws_api_listener(server_secure, host, nmos::experimental::server_port(ws_handler.first.second, node_model.settings), ws_handler.second.first, websocket_config, gate)); + + if (!found_event_ws) + { + if (ws_handler.first.second == events_ws_port) { found_event_ws = true; } + else { ++event_ws_pos; } + } + + if (control_protocol_enabled && !found_control_protocol_ws) + { + if (ws_handler.first.second == control_protocol_ws_port) { found_control_protocol_ws = true; } + else { ++control_protocol_ws_pos; } + } } - auto& events_ws_listener = node_server.ws_listeners.back(); + auto& events_ws_listener = node_server.ws_listeners.at(event_ws_pos); // Set up node operation (including the DNS-SD advertisements) @@ -128,6 +156,13 @@ namespace nmos node_server.thread_functions.push_back([&, load_ca_certificates, system_changed] { nmos::node_system_behaviour_thread(node_model, load_ca_certificates, system_changed, gate); }); } + if (control_protocol_enabled) + { + auto& control_protocol_ws_listener = node_server.ws_listeners.at(control_protocol_ws_pos); + auto& control_protocol_ws_api = node_server.ws_handlers.at({ {}, control_protocol_ws_port }); + node_server.thread_functions.push_back([&] { nmos::send_control_protocol_ws_messages_thread(control_protocol_ws_listener, node_model, control_protocol_ws_api.second, gate); }); + } + return node_server; } diff --git a/Development/nmos/node_server.h b/Development/nmos/node_server.h index 6f7d3a176..25a15d4b7 100644 --- a/Development/nmos/node_server.h +++ b/Development/nmos/node_server.h @@ -7,6 +7,7 @@ #include "nmos/channelmapping_activation.h" #include "nmos/connection_api.h" #include "nmos/connection_activation.h" +#include "nmos/control_protocol_handlers.h" #include "nmos/node_behaviour.h" #include "nmos/node_system_behaviour.h" #include "nmos/ocsp_response_handler.h" @@ -26,7 +27,7 @@ namespace nmos // underlying implementation into the server instance for the NMOS Node struct node_implementation { - node_implementation(nmos::load_server_certificates_handler load_server_certificates, nmos::load_dh_param_handler load_dh_param, nmos::load_ca_certificates_handler load_ca_certificates, nmos::system_global_handler system_changed, nmos::registration_handler registration_changed, nmos::transport_file_parser parse_transport_file, nmos::details::connection_resource_patch_validator validate_staged, nmos::connection_resource_auto_resolver resolve_auto, nmos::connection_sender_transportfile_setter set_transportfile, nmos::connection_activation_handler connection_activated, nmos::ocsp_response_handler get_ocsp_response, get_authorization_bearer_token_handler get_authorization_bearer_token, validate_authorization_handler validate_authorization, ws_validate_authorization_handler ws_validate_authorization, nmos::load_rsa_private_keys_handler load_rsa_private_keys, load_authorization_clients_handler load_authorization_clients, save_authorization_client_handler save_authorization_client, request_authorization_code_handler request_authorization_code) + node_implementation(nmos::load_server_certificates_handler load_server_certificates, nmos::load_dh_param_handler load_dh_param, nmos::load_ca_certificates_handler load_ca_certificates, nmos::system_global_handler system_changed, nmos::registration_handler registration_changed, nmos::transport_file_parser parse_transport_file, nmos::details::connection_resource_patch_validator validate_staged, nmos::connection_resource_auto_resolver resolve_auto, nmos::connection_sender_transportfile_setter set_transportfile, nmos::connection_activation_handler connection_activated, nmos::ocsp_response_handler get_ocsp_response, get_authorization_bearer_token_handler get_authorization_bearer_token, validate_authorization_handler validate_authorization, ws_validate_authorization_handler ws_validate_authorization, nmos::load_rsa_private_keys_handler load_rsa_private_keys, load_authorization_clients_handler load_authorization_clients, save_authorization_client_handler save_authorization_client, request_authorization_code_handler request_authorization_code, nmos::get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, nmos::get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, nmos::get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor, nmos::control_protocol_property_changed_handler control_protocol_property_changed) : load_server_certificates(std::move(load_server_certificates)) , load_dh_param(std::move(load_dh_param)) , load_ca_certificates(std::move(load_ca_certificates)) @@ -45,6 +46,10 @@ namespace nmos , load_authorization_clients(std::move(load_authorization_clients)) , save_authorization_client(std::move(save_authorization_client)) , request_authorization_code(std::move(request_authorization_code)) + , get_control_protocol_class_descriptor(std::move(get_control_protocol_class_descriptor)) + , get_control_protocol_datatype_descriptor(std::move(get_control_protocol_datatype_descriptor)) + , get_control_protocol_method_descriptor(std::move(get_control_protocol_method_descriptor)) + , control_protocol_property_changed(std::move(control_protocol_property_changed)) {} // use the default constructor and chaining member functions for fluent initialization @@ -73,6 +78,10 @@ namespace nmos node_implementation& on_load_authorization_clients(load_authorization_clients_handler load_authorization_clients) { this->load_authorization_clients = std::move(load_authorization_clients); return *this; } node_implementation& on_save_authorization_client(save_authorization_client_handler save_authorization_client) { this->save_authorization_client = std::move(save_authorization_client); return *this; } node_implementation& on_request_authorization_code(request_authorization_code_handler request_authorization_code) { this->request_authorization_code = std::move(request_authorization_code); return *this; } + node_implementation& on_get_control_class_descriptor(nmos::get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor) { this->get_control_protocol_class_descriptor = std::move(get_control_protocol_class_descriptor); return *this; } + node_implementation& on_get_control_datatype_descriptor(nmos::get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor) { this->get_control_protocol_datatype_descriptor = std::move(get_control_protocol_datatype_descriptor); return *this; } + node_implementation& on_get_control_protocol_method_descriptor(nmos::get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor) { this->get_control_protocol_method_descriptor = std::move(get_control_protocol_method_descriptor); return *this; } + node_implementation& on_control_protocol_property_changed(nmos::control_protocol_property_changed_handler control_protocol_property_changed) { this->control_protocol_property_changed = std::move(control_protocol_property_changed); return *this; } // deprecated, use on_validate_connection_resource_patch node_implementation& on_validate_merged(nmos::details::connection_resource_patch_validator validate_merged) { return on_validate_connection_resource_patch(std::move(validate_merged)); } @@ -110,6 +119,11 @@ namespace nmos load_authorization_clients_handler load_authorization_clients; save_authorization_client_handler save_authorization_client; request_authorization_code_handler request_authorization_code; + + nmos::get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor; + nmos::get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor; + nmos::get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor; + nmos::control_protocol_property_changed_handler control_protocol_property_changed; }; // Construct a server instance for an NMOS Node, implementing the IS-04 Node API, IS-05 Connection API, IS-07 Events API diff --git a/Development/nmos/query_utils.cpp b/Development/nmos/query_utils.cpp index 14e381200..62977ff09 100644 --- a/Development/nmos/query_utils.cpp +++ b/Development/nmos/query_utils.cpp @@ -577,4 +577,49 @@ namespace nmos } } } + + // insert 'value changed', 'sequence item added', 'sequence item changed' or 'sequence item removed' notification events into all grains whose subscriptions match the specified version, type and "pre" or "post" values + // this is used for the IS-12 propertry changed event + void insert_notification_events(nmos::resources& resources, const nmos::api_version& version, const nmos::api_version& downgrade_version, const nmos::type& type, const web::json::value& pre, const web::json::value& post, const web::json::value& event) + { + using web::json::value; + + if (pre == post) return; + + if (!details::is_queryable_resource(type)) return; + + auto& by_type = resources.get(); + const auto subscriptions = by_type.equal_range(details::has_data(nmos::types::subscription)); + + for (auto it = subscriptions.first; subscriptions.second != it; ++it) + { + // for each subscription + const auto& subscription = *it; + + // check whether the resource_path matches the resource type and the query parameters match either the "pre" or "post" resource + + const auto resource_path = nmos::fields::resource_path(subscription.data); + const resource_query match(subscription.version, resource_path, nmos::fields::params(subscription.data)); + + const bool pre_match = match(version, downgrade_version, type, pre, resources); + const bool post_match = match(version, downgrade_version, type, post, resources); + + if (!pre_match && !post_match) continue; + + // add the event to the grain for each websocket connection to this subscription + + for (const auto& id : subscription.sub_resources) + { + auto grain = find_resource(resources, { id, nmos::types::grain }); + if (resources.end() == grain) continue; // check websocket connection is still open + + resources.modify(grain, [&resources, &event](nmos::resource& grain) + { + auto& events = nmos::fields::message_grain_data(grain.data); + web::json::push_back(events, event); + grain.updated = strictly_increasing_update(resources); + }); + } + } + } } diff --git a/Development/nmos/query_utils.h b/Development/nmos/query_utils.h index 91addbe46..fcfbe9c0b 100644 --- a/Development/nmos/query_utils.h +++ b/Development/nmos/query_utils.h @@ -114,6 +114,9 @@ namespace nmos // insert 'added', 'removed' or 'modified' resource events into all grains whose subscriptions match the specified version, type and "pre" or "post" values void insert_resource_events(nmos::resources& resources, const nmos::api_version& version, const nmos::api_version& downgrade_version, const nmos::type& type, const web::json::value& pre, const web::json::value& post); + // insert 'value changed', 'sequence item added', 'sequence item changed' or 'sequence item removed' notification events into all grains whose subscriptions match the specified version, type and "pre" or "post" values + void insert_notification_events(nmos::resources& resources, const nmos::api_version& version, const nmos::api_version& downgrade_version, const nmos::type& type, const web::json::value& pre, const web::json::value& post, const web::json::value& event); + namespace fields { const web::json::field_as_string_or query_rql{ U("query.rql"), {} }; diff --git a/Development/nmos/scope.h b/Development/nmos/scope.h index 1f3999531..25d65004e 100644 --- a/Development/nmos/scope.h +++ b/Development/nmos/scope.h @@ -24,6 +24,8 @@ namespace nmos const scope events{ U("events") }; // IS-08 const scope channelmapping{ U("channelmapping") }; + // IS-12 + const scope ncp{ U("ncp") }; } inline utility::string_t make_scope(const scope& scope) @@ -40,6 +42,7 @@ namespace nmos if (scopes::netctrl.name == scope) { return scopes::netctrl; } if (scopes::events.name == scope) { return scopes::events; } if (scopes::channelmapping.name == scope) { return scopes::channelmapping; } + if (scopes::ncp.name == scope) { return scopes::ncp; } return{}; } } diff --git a/Development/nmos/settings.cpp b/Development/nmos/settings.cpp index 78a8e7ab1..5608fbcac 100644 --- a/Development/nmos/settings.cpp +++ b/Development/nmos/settings.cpp @@ -66,6 +66,8 @@ namespace nmos const auto http_port = nmos::fields::http_port(settings); // can't share a port between an http_listener and a websocket_listener, so use next higher port const auto ws_port = http_port + 1; + // can't share a port between the events ws and the control protocol ws + const auto ncp_ws_port = ws_port + 1; if (registry) web::json::insert(settings, std::make_pair(nmos::fields::query_port, http_port)); if (registry) web::json::insert(settings, std::make_pair(nmos::fields::query_ws_port, ws_port)); if (registry) web::json::insert(settings, std::make_pair(nmos::fields::registration_port, http_port)); @@ -83,6 +85,7 @@ namespace nmos if (registry) web::json::insert(settings, std::make_pair(nmos::experimental::fields::schemas_port, http_port)); web::json::insert(settings, std::make_pair(nmos::experimental::fields::authorization_redirect_port, http_port)); web::json::insert(settings, std::make_pair(nmos::experimental::fields::jwks_uri_port, http_port)); + if (!registry) web::json::insert(settings, std::make_pair(nmos::fields::control_protocol_ws_port, ncp_ws_port)); } } } diff --git a/Development/nmos/settings.h b/Development/nmos/settings.h index 616df01ff..c0981f8bf 100644 --- a/Development/nmos/settings.h +++ b/Development/nmos/settings.h @@ -104,6 +104,9 @@ namespace nmos // is10_versions [registry, node]: used to specify the enabled API versions for a version-locked configuration const web::json::field_as_array is10_versions{ U("is10_versions") }; // when omitted, nmos::is10_versions::all is used + // is12_versions [node]: used to specify the enabled API versions for a version-locked configuration + const web::json::field_as_array is12_versions{ U("is12_versions") }; // when omitted, nmos::is12_versions::all is used + // pri [registry, node]: used for the 'pri' TXT record; specifying nmos::service_priorities::no_priority (maximum value) disables advertisement completely const web::json::field_as_integer_or pri{ U("pri"), 100 }; // default to highest_development_priority @@ -146,6 +149,8 @@ namespace nmos const web::json::field_as_integer_or channelmapping_port{ U("channelmapping_port"), 3215 }; // system_port [node]: used to construct request URLs for the System API (if not discovered via DNS-SD) const web::json::field_as_integer_or system_port{ U("system_port"), 10641 }; + // control_protocol_ws_port [node]: used to construct request URLs for the Control Protocol websocket, or negative to disable the control protocol features + const web::json::field_as_integer_or control_protocol_ws_port{ U("control_protocol_ws_port"), 3218 }; // listen_backlog [registry, node]: the maximum length of the queue of pending connections, or zero for the implementation default (the implementation may not honour this value) const web::json::field_as_integer_or listen_backlog{ U("listen_backlog"), 0 }; @@ -445,6 +450,20 @@ namespace nmos // If the Resource Server fails to verify a token using all public keys available it MUST reject the token." // see https://specs.amwa.tv/is-10/releases/v1.0.0/docs/4.5._Behaviour_-_Resource_Servers.html#public-keys const web::json::field_as_integer_or service_unavailable_retry_after{ U("service_unavailable_retry_after"), 5 }; + + // manufacturer_name [node]: the manufacturer name of the NcDeviceManager used for NMOS Control Protocol + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdevicemanager + const web::json::field_as_string_or manufacturer_name{ U("manufacturer_name"), U("") }; + + // product_name/product_key/product_revision_level [node]: the product description of the NcDeviceManager used for NMOS Control Protocol + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncproduct + const web::json::field_as_string_or product_name{ U("product_name"), U("") }; + const web::json::field_as_string_or product_key{ U("product_key"), U("") }; + const web::json::field_as_string_or product_revision_level{ U("product_revision_level"), U("") }; + + // serial_number [node]: the serial number of the NcDeviceManager used for NMOS Control Protocol + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncdevicemanager + const web::json::field_as_string_or serial_number{ U("serial_number"), U("") }; } } } diff --git a/Development/nmos/slog.h b/Development/nmos/slog.h index d3bd48bee..13a1a67b8 100644 --- a/Development/nmos/slog.h +++ b/Development/nmos/slog.h @@ -45,6 +45,7 @@ namespace nmos const category node_system_behaviour{ "node_system_behaviour" }; const category ocsp_behaviour{ "ocsp_behaviour" }; const category authorization_behaviour{ "authorization_behaviour" }; + const category send_control_protocol_ws_messages{ "send_control_protocol_ws_messages" }; // other categories may be defined ad-hoc } diff --git a/Development/nmos/test/control_protocol_test.cpp b/Development/nmos/test/control_protocol_test.cpp new file mode 100644 index 000000000..2d4aeba6e --- /dev/null +++ b/Development/nmos/test/control_protocol_test.cpp @@ -0,0 +1,1464 @@ +// The first "test" is of course whether the header compiles standalone +#include "nmos/control_protocol_resource.h" +#include "nmos/control_protocol_state.h" +#include "nmos/control_protocol_typedefs.h" +#include "nmos/control_protocol_utils.h" + +#include "bst/test/test.h" + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testNcClassDescriptor) +{ + using web::json::value_of; + using web::json::value; + + // NcObject + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/classes/1.html + + const auto property_class_id = value_of({ + { U("description"), U("Static value. All instances of the same class will have the same identity value") }, + { U("id"), value_of({ + { U("level"), 1 }, + { U("index"), 1 } + }) }, + { U("name"), U("classId") }, + { U("typeName"), U("NcClassId") }, + { U("isReadOnly"), true }, + { U("isNullable"), false }, + { U("isSequence"), false }, + { U("isDeprecated"), false }, + { U("constraints"), value::null() } + }); + const auto property_class_id_ = nmos::details::make_nc_property_descriptor(U("Static value. All instances of the same class will have the same identity value"), nmos::nc_object_class_id_property_id, nmos::fields::nc::class_id, U("NcClassId"), true, false, false, false, value::null()); + BST_REQUIRE_EQUAL(property_class_id, property_class_id_); + + const auto property_oid = value_of({ + { U("description"), U("Object identifier") }, + { U("id"), value_of({ + { U("level"), 1 }, + { U("index"), 2 } + }) }, + { U("name"), U("oid") }, + { U("typeName"), U("NcOid") }, + { U("isReadOnly"), true }, + { U("isNullable"), false }, + { U("isSequence"), false }, + { U("isDeprecated"), false }, + { U("constraints"), value::null() } + }); + const auto property_oid_ = nmos::details::make_nc_property_descriptor(U("Object identifier"), nmos::nc_object_oid_property_id, nmos::fields::nc::oid, U("NcOid"), true, false, false, false, value::null()); + BST_REQUIRE_EQUAL(property_oid, property_oid_); + + const auto property_constant_oid = value_of({ + { U("description"), U("TRUE iff OID is hardwired into device") }, + { U("id"), value_of({ + { U("level"), 1 }, + { U("index"), 3 } + }) }, + { U("name"), U("constantOid") }, + { U("typeName"), U("NcBoolean") }, + { U("isReadOnly"), true }, + { U("isNullable"), false }, + { U("isSequence"), false }, + { U("isDeprecated"), false }, + { U("constraints"), value::null() } + }); + const auto property_constant_oid_ = nmos::details::make_nc_property_descriptor(U("TRUE iff OID is hardwired into device"), nmos::nc_object_constant_oid_property_id, nmos::fields::nc::constant_oid, U("NcBoolean"), true, false, false, false, value::null()); + BST_REQUIRE_EQUAL(property_constant_oid, property_constant_oid_); + + const auto property_owner = value_of({ + { U("description"), U("OID of containing block. Can only ever be null for the root block") }, + { U("id"), value_of({ + { U("level"), 1 }, + { U("index"), 4 } + }) }, + { U("name"), U("owner") }, + { U("typeName"), U("NcOid") }, + { U("isReadOnly"), true }, + { U("isNullable"), true }, + { U("isSequence"), false }, + { U("isDeprecated"), false }, + { U("constraints"), value::null() } + }); + const auto property_owner_ = nmos::details::make_nc_property_descriptor(U("OID of containing block. Can only ever be null for the root block"), nmos::nc_object_owner_property_id, nmos::fields::nc::owner, U("NcOid"), true, true, false, false, value::null()); + BST_REQUIRE_EQUAL(property_owner, property_owner_); + + const auto property_role = value_of({ + { U("description"), U("Role of object in the containing block") }, + { U("id"), value_of({ + { U("level"), 1 }, + { U("index"), 5 } + }) }, + { U("name"), U("role") }, + { U("typeName"), U("NcString") }, + { U("isReadOnly"), true }, + { U("isNullable"), false }, + { U("isSequence"), false }, + { U("isDeprecated"), false }, + { U("constraints"), value::null() } + }); + const auto property_role_ = nmos::details::make_nc_property_descriptor(U("Role of object in the containing block"), nmos::nc_object_role_property_id, nmos::fields::nc::role, U("NcString"), true, false, false, false, value::null()); + BST_REQUIRE_EQUAL(property_role, property_role_); + + const auto property_user_label = value_of({ + { U("description"), U("Scribble strip") }, + { U("id"), value_of({ + { U("level"), 1 }, + { U("index"), 6 } + }) }, + { U("name"), U("userLabel") }, + { U("typeName"), U("NcString") }, + { U("isReadOnly"), false }, + { U("isNullable"), true }, + { U("isSequence"), false }, + { U("isDeprecated"), false }, + { U("constraints"), value::null() } + }); + const auto property_user_label_ = nmos::details::make_nc_property_descriptor(U("Scribble strip"), nmos::nc_object_user_label_property_id, nmos::fields::nc::user_label, U("NcString"), false, true, false, false, value::null()); + BST_REQUIRE_EQUAL(property_user_label, property_user_label_); + + const auto property_touchpoints = value_of({ + { U("description"), U("Touchpoints to other contexts") }, + { U("id"), value_of({ + { U("level"), 1 }, + { U("index"), 7 } + }) }, + { U("name"), U("touchpoints") }, + { U("typeName"), U("NcTouchpoint") }, + { U("isReadOnly"), true }, + { U("isNullable"), true }, + { U("isSequence"), true }, + { U("isDeprecated"), false }, + { U("constraints"), value::null() } + }); + const auto property_touchpoints_ = nmos::details::make_nc_property_descriptor(U("Touchpoints to other contexts"), nmos::nc_object_touchpoints_property_id, nmos::fields::nc::touchpoints, U("NcTouchpoint"), true, true, true, false, value::null()); + BST_REQUIRE_EQUAL(property_touchpoints, property_touchpoints_); + + const auto property_runtime_property_constraints = value_of({ + { U("description"), U("Runtime property constraints") }, + { U("id"), value_of({ + { U("level"), 1 }, + { U("index"), 8 } + }) }, + { U("name"), U("runtimePropertyConstraints") }, + { U("typeName"), U("NcPropertyConstraints") }, + { U("isReadOnly"), true }, + { U("isNullable"), true }, + { U("isSequence"), true }, + { U("isDeprecated"), false }, + { U("constraints"), value::null() } + }); + const auto property_runtime_property_constraints_ = nmos::details::make_nc_property_descriptor(U("Runtime property constraints"), nmos::nc_object_runtime_property_constraints_property_id, nmos::fields::nc::runtime_property_constraints, U("NcPropertyConstraints"), true, true, true, false, value::null()); + BST_REQUIRE_EQUAL(property_runtime_property_constraints, property_runtime_property_constraints_); + + const auto method_get = value_of({ + { U("description"), U("Get property value") }, + { U("id"), value_of({ + { U("level"), 1 }, + { U("index"), 1 } + }) }, + { U("name"), U("Get") }, + { U("resultDatatype"), U("NcMethodResultPropertyValue") }, + { U("parameters"), value_of({ + value_of({ + { U("description"), U("Property id") }, + { U("name"), U("id") }, + { U("typeName"), U("NcPropertyId") }, + { U("isNullable"), false }, + { U("isSequence"), false }, + { U("constraints"), value::null() } + }) + }) }, + { U("isDeprecated"), false } + }); + + { + auto parameters = value::array(); + web::json::push_back(parameters, nmos::details::make_nc_parameter_descriptor(U("Property id"), nmos::fields::nc::id, U("NcPropertyId"), false, false, value::null())); + const auto method_get_ = nmos::details::make_nc_method_descriptor(U("Get property value"), nmos::nc_object_get_method_id, U("Get"), U("NcMethodResultPropertyValue"), parameters, false); + + BST_REQUIRE_EQUAL(method_get, method_get_); + } + + const auto method_set = value_of({ + { U("description"), U("Set property value") }, + { U("id"), value_of({ + { U("level"), 1 }, + { U("index"), 2 } + }) }, + { U("name"), U("Set") }, + { U("resultDatatype"), U("NcMethodResult") }, + { U("parameters"), value_of({ + value_of({ + { U("description"), U("Property id") }, + { U("name"), U("id") }, + { U("typeName"), U("NcPropertyId") }, + { U("isNullable"), false }, + { U("isSequence"), false }, + { U("constraints"), value::null() } + }), + value_of({ + { U("description"), U("Property value") }, + { U("name"), U("value") }, + { U("typeName"), value::null() }, + { U("isNullable"), true }, + { U("isSequence"), false }, + { U("constraints"), value::null() } + }) + }) }, + { U("isDeprecated"), false } + }); + + { + auto parameters = value::array(); + web::json::push_back(parameters, nmos::details::make_nc_parameter_descriptor(U("Property id"), nmos::fields::nc::id, U("NcPropertyId"), false, false, value::null())); + web::json::push_back(parameters, nmos::details::make_nc_parameter_descriptor(U("Property value"), nmos::fields::nc::value, true, false, value::null())); + const auto method_set_ = nmos::details::make_nc_method_descriptor(U("Set property value"), nmos::nc_object_set_method_id, U("Set"), U("NcMethodResult"), parameters, false); + + BST_REQUIRE_EQUAL(method_set, method_set_); + } + + const auto method_get_sequence_item = value_of({ + { U("description"), U("Get sequence item") }, + { U("id"), value_of({ + { U("level"), 1 }, + { U("index"), 3 } + }) }, + { U("name"), U("GetSequenceItem") }, + { U("resultDatatype"), U("NcMethodResultPropertyValue") }, + { U("parameters"), value_of({ + value_of({ + { U("description"), U("Property id") }, + { U("name"), U("id") }, + { U("typeName"), U("NcPropertyId") }, + { U("isNullable"), false }, + { U("isSequence"), false }, + { U("constraints"), value::null() } + }), + value_of({ + { U("description"), U("Index of item in the sequence") }, + { U("name"), U("index") }, + { U("typeName"), U("NcId")}, + { U("isNullable"), false }, + { U("isSequence"), false }, + { U("constraints"), value::null() } + }) + }) }, + { U("isDeprecated"), false } + }); + + { + auto parameters = value::array(); + web::json::push_back(parameters, nmos::details::make_nc_parameter_descriptor(U("Property id"), nmos::fields::nc::id, U("NcPropertyId"), false, false, value::null())); + web::json::push_back(parameters, nmos::details::make_nc_parameter_descriptor(U("Index of item in the sequence"), nmos::fields::nc::index, U("NcId"), false, false, value::null())); + const auto method_get_sequence_item_ = nmos::details::make_nc_method_descriptor(U("Get sequence item"), nmos::nc_object_get_sequence_item_method_id, U("GetSequenceItem"), U("NcMethodResultPropertyValue"), parameters, false); + + BST_REQUIRE_EQUAL(method_get_sequence_item, method_get_sequence_item_); + } + + const auto method_set_sequence_item = value_of({ + { U("description"), U("Set sequence item value") }, + { U("id"), value_of({ + { U("level"), 1 }, + { U("index"), 4 } + }) }, + { U("name"), U("SetSequenceItem") }, + { U("resultDatatype"), U("NcMethodResult") }, + { U("parameters"), value_of({ + value_of({ + { U("description"), U("Property id") }, + { U("name"), U("id") }, + { U("typeName"), U("NcPropertyId") }, + { U("isNullable"), false }, + { U("isSequence"), false }, + { U("constraints"), value::null() } + }), + value_of({ + { U("description"), U("Index of item in the sequence") }, + { U("name"), U("index") }, + { U("typeName"), U("NcId") }, + { U("isNullable"), false }, + { U("isSequence"), false }, + { U("constraints"), value::null() } + }), + value_of({ + { U("description"), U("Value") }, + { U("name"), U("value") }, + { U("typeName"), value::null() }, + { U("isNullable"), true }, + { U("isSequence"), false }, + { U("constraints"), value::null() } + }) + }) }, + { U("isDeprecated"), false } + }); + + { + auto parameters = value::array(); + web::json::push_back(parameters, nmos::details::make_nc_parameter_descriptor(U("Property id"), nmos::fields::nc::id, U("NcPropertyId"), false, false, value::null())); + web::json::push_back(parameters, nmos::details::make_nc_parameter_descriptor(U("Index of item in the sequence"), nmos::fields::nc::index, U("NcId"), false, false, value::null())); + web::json::push_back(parameters, nmos::details::make_nc_parameter_descriptor(U("Value"), nmos::fields::nc::value, true, false, value::null())); + const auto method_set_sequence_item_ = nmos::details::make_nc_method_descriptor(U("Set sequence item value"), nmos::nc_object_set_sequence_item_method_id, U("SetSequenceItem"), U("NcMethodResult"), parameters, false); + + BST_REQUIRE_EQUAL(method_set_sequence_item, method_set_sequence_item_); + } + + const auto method_add_sequence_item = value_of({ + { U("description"), U("Add item to sequence") }, + { U("id"), value_of({ + { U("level"), 1 }, + { U("index"), 5 } + }) }, + { U("name"), U("AddSequenceItem") }, + { U("resultDatatype"), U("NcMethodResultId") }, + { U("parameters"), value_of({ + value_of({ + { U("description"), U("Property id") }, + { U("name"), U("id") }, + { U("typeName"), U("NcPropertyId") }, + { U("isNullable"), false }, + { U("isSequence"), false }, + { U("constraints"), value::null() } + }), + value_of({ + { U("description"), U("Value") }, + { U("name"), U("value") }, + { U("typeName"), value::null() }, + { U("isNullable"), true }, + { U("isSequence"), false }, + { U("constraints"), value::null() } + }) + }) }, + { U("isDeprecated"), false } + }); + + { + auto parameters = value::array(); + web::json::push_back(parameters, nmos::details::make_nc_parameter_descriptor(U("Property id"), nmos::fields::nc::id, U("NcPropertyId"), false, false, value::null())); + web::json::push_back(parameters, nmos::details::make_nc_parameter_descriptor(U("Value"), nmos::fields::nc::value, true, false, value::null())); + const auto method_add_sequence_item_ = nmos::details::make_nc_method_descriptor(U("Add item to sequence"), nmos::nc_object_add_sequence_item_method_id, U("AddSequenceItem"), U("NcMethodResultId"), parameters, false); + + BST_REQUIRE_EQUAL(method_add_sequence_item, method_add_sequence_item_); + } + + const auto method_remove_sequence_item = value_of({ + { U("description"), U("Delete sequence item") }, + { U("id"), value_of({ + { U("level"), 1 }, + { U("index"), 6 } + }) }, + { U("name"), U("RemoveSequenceItem") }, + { U("resultDatatype"), U("NcMethodResult") }, + { U("parameters"), value_of({ + value_of({ + { U("description"), U("Property id") }, + { U("name"), U("id") }, + { U("typeName"), U("NcPropertyId") }, + { U("isNullable"), false }, + { U("isSequence"), false }, + { U("constraints"), value::null() } + }), + value_of({ + { U("description"), U("Index of item in the sequence") }, + { U("name"), U("index") }, + { U("typeName"), U("NcId") }, + { U("isNullable"), false }, + { U("isSequence"), false }, + { U("constraints"), value::null() } + }) + }) }, + { U("isDeprecated"), false } + }); + + { + auto parameters = value::array(); + web::json::push_back(parameters, nmos::details::make_nc_parameter_descriptor(U("Property id"), nmos::fields::nc::id, U("NcPropertyId"), false, false, value::null())); + web::json::push_back(parameters, nmos::details::make_nc_parameter_descriptor(U("Index of item in the sequence"), nmos::fields::nc::index, U("NcId"), false, false, value::null())); + const auto method_remove_sequence_item_ = nmos::details::make_nc_method_descriptor(U("Delete sequence item"), nmos::nc_object_remove_sequence_item_method_id, U("RemoveSequenceItem"), U("NcMethodResult"), parameters, false); + + BST_REQUIRE_EQUAL(method_remove_sequence_item, method_remove_sequence_item_); + } + + const auto method_get_sequence_length = value_of({ + { U("description"), U("Get sequence length") }, + { U("id"), value_of({ + { U("level"), 1 }, + { U("index"), 7 } + }) }, + { U("name"), U("GetSequenceLength") }, + { U("resultDatatype"), U("NcMethodResultLength") }, + { U("parameters"), value_of({ + value_of({ + { U("description"), U("Property id") }, + { U("name"), U("id") }, + { U("typeName"), U("NcPropertyId") }, + { U("isNullable"), false }, + { U("isSequence"), false }, + { U("constraints"), value::null() } + }) + }) }, + { U("isDeprecated"), false } + }); + + { + auto parameters = value::array(); + web::json::push_back(parameters, nmos::details::make_nc_parameter_descriptor(U("Property id"), nmos::fields::nc::id, U("NcPropertyId"), false, false, value::null())); + const auto method_get_sequence_length_ = nmos::details::make_nc_method_descriptor(U("Get sequence length"), nmos::nc_object_get_sequence_length_method_id, U("GetSequenceLength"), U("NcMethodResultLength"), parameters, false); + + BST_REQUIRE_EQUAL(method_get_sequence_length, method_get_sequence_length_); + } + + const auto event_property_changed = value_of({ + { U("description"), U("Property changed event") }, + { U("id"), value_of({ + { U("level"), 1 }, + { U("index"), 1 } + }) }, + { U("name"), U("PropertyChanged") }, + { U("eventDatatype"), U("NcPropertyChangedEventData") }, + { U("isDeprecated"), false } + }); + + const auto event_property_changed_ = nmos::details::make_nc_event_descriptor(U("Property changed event"), nmos::nc_object_property_changed_event_id, U("PropertyChanged"), U("NcPropertyChangedEventData"), false); + BST_REQUIRE_EQUAL(event_property_changed, event_property_changed_); + + const auto nc_object_class = value_of({ + { U("description"), U("NcObject class descriptor") }, + { U("classId"), value_of({ + { 1 } + }) }, + { U("name"), U("NcObject") }, + { U("fixedRole"), value::null() }, + { U("properties"), value_of({ + property_class_id, + property_oid, + property_constant_oid, + property_owner, + property_role, + property_user_label, + property_touchpoints, + property_runtime_property_constraints + }) }, + { U("methods"), value_of({ + method_get, + method_set, + method_get_sequence_item, + method_set_sequence_item, + method_add_sequence_item, + method_remove_sequence_item, + method_get_sequence_length + }) }, + { U("events"), value_of({ + event_property_changed + }) } + }); + const auto nc_object_class_ = nmos::details::make_nc_class_descriptor(U("NcObject class descriptor"), nmos::nc_object_class_id, U("NcObject"), nmos::make_nc_object_properties(), nmos::make_nc_object_methods(), nmos::make_nc_object_events()); + BST_REQUIRE_EQUAL(nc_object_class, nc_object_class_); +} + +BST_TEST_CASE(testNcDatatypeDescriptorStruct) +{ + using web::json::value_of; + using web::json::value; + + // NcBlockMemberDescriptor + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcBlockMemberDescriptor.html + const auto nc_datatype_descriptor = value_of({ + { U("description"), U("Descriptor which is specific to a block member") }, + { U("name"), U("NcBlockMemberDescriptor") }, + { U("type"), 2 }, + { U("fields"), value_of({ + value_of({ + { U("description"), U("Role of member in its containing block") }, + { U("name"), U("role") }, + { U("typeName"), U("NcString") }, + { U("isNullable"), false }, + { U("isSequence"), false }, + { U("constraints"), value::null() } + }), + value_of({ + { U("description"), U("OID of member") }, + { U("name"), U("oid") }, + { U("typeName"), U("NcOid") }, + { U("isNullable"), false }, + { U("isSequence"), false }, + { U("constraints"), value::null() } + }), + value_of({ + { U("description"), U("TRUE iff member's OID is hardwired into device") }, + { U("name"), U("constantOid") }, + { U("typeName"), U("NcBoolean") }, + { U("isNullable"), false }, + { U("isSequence"), false }, + { U("constraints"), value::null() } + }), + value_of({ + { U("description"), U("Class ID") }, + { U("name"), U("classId") }, + { U("typeName"), U("NcClassId") }, + { U("isNullable"), false }, + { U("isSequence"), false }, + { U("constraints"), value::null() } + }), + value_of({ + { U("description"), U("User label") }, + { U("name"), U("userLabel") }, + { U("typeName"), U("NcString") }, + { U("isNullable"), true }, + { U("isSequence"), false }, + { U("constraints"), value::null() } + }), + value_of({ + { U("description"), U("Containing block's OID") }, + { U("name"), U("owner") }, + { U("typeName"), U("NcOid") }, + { U("isNullable"), false }, + { U("isSequence"), false }, + { U("constraints"), value::null() } + }) + }) }, + { U("parentType"), U("NcDescriptor") }, + { U("constraints"), value::null() } + }); + + auto fields = value::array(); + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("Role of member in its containing block"), nmos::fields::nc::role, U("NcString"), false, false, value::null())); + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("OID of member"), nmos::fields::nc::oid, U("NcOid"), false, false, value::null())); + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("TRUE iff member's OID is hardwired into device"), nmos::fields::nc::constant_oid, U("NcBoolean"), false, false, value::null())); + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("Class ID"), nmos::fields::nc::class_id, U("NcClassId"), false, false, value::null())); + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("User label"), nmos::fields::nc::user_label, U("NcString"), true, false, value::null())); + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("Containing block's OID"), nmos::fields::nc::owner, U("NcOid"), false, false, value::null())); + const auto nc_datatype_descriptor_ = nmos::details::make_nc_datatype_descriptor_struct(U("Descriptor which is specific to a block member"), U("NcBlockMemberDescriptor"), fields, U("NcDescriptor"), value::null()); + + BST_REQUIRE_EQUAL(nc_datatype_descriptor, nc_datatype_descriptor_); +} + +BST_TEST_CASE(testNcDatatypeTypedef) +{ + using web::json::value_of; + using web::json::value; + + // NcClassId + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcClassId.html + const auto nc_class_id = value_of({ + { U("description"), U("Sequence of class ID fields") }, + { U("name"), U("NcClassId") }, + { U("type"), 1 }, + { U("parentType"), U("NcInt32") }, + { U("isSequence"), true }, + { U("constraints"), value::null() } + }); + const auto nc_class_id_ = nmos::details::make_nc_datatype_typedef(U("Sequence of class ID fields"), U("NcClassId"), true, U("NcInt32"), value::null()); + + BST_REQUIRE_EQUAL(nc_class_id, nc_class_id_); +} + +BST_TEST_CASE(testNcDatatypeDescriptorEnum) +{ + using web::json::value_of; + using web::json::value; + + // NcDeviceGenericState + // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/models/datatypes/NcDeviceGenericState.html + const auto nc_device_generic_state = value_of({ + { U("description"), U("Device generic operational state") }, + { U("name"), U("NcDeviceGenericState") }, + { U("type"), 3 }, + { U("items"), value_of({ + value_of({ + { U("description"), U("Unknown") }, + { U("name"), U("Unknown") }, + { U("value"), 0 } + }), + value_of({ + { U("description"), U("Normal operation") }, + { U("name"), U("NormalOperation") }, + { U("value"), 1 } + }), + value_of({ + { U("description"), U("Device is initializing") }, + { U("name"), U("Initializing") }, + { U("value"), 2 } + }), + value_of({ + { U("description"), U("Device is performing a software or firmware update") }, + { U("name"), U("Updating") }, + { U("value"), 3 } + }), + value_of({ + { U("description"), U("Device is experiencing a licensing error") }, + { U("name"), U("LicensingError") }, + { U("value"), 4 } + }), + value_of({ + { U("description"), U("Device is experiencing an internal error") }, + { U("name"), U("InternalError") }, + { U("value"), 5 } + }) + }) }, + { U("constraints"), value::null() } + }); + + auto items = value::array(); + web::json::push_back(items, nmos::details::make_nc_enum_item_descriptor(U("Unknown"), U("Unknown"), 0)); + web::json::push_back(items, nmos::details::make_nc_enum_item_descriptor(U("Normal operation"), U("NormalOperation"), 1)); + web::json::push_back(items, nmos::details::make_nc_enum_item_descriptor(U("Device is initializing"), U("Initializing"), 2)); + web::json::push_back(items, nmos::details::make_nc_enum_item_descriptor(U("Device is performing a software or firmware update"), U("Updating"), 3)); + web::json::push_back(items, nmos::details::make_nc_enum_item_descriptor(U("Device is experiencing a licensing error"), U("LicensingError"), 4)); + web::json::push_back(items, nmos::details::make_nc_enum_item_descriptor(U("Device is experiencing an internal error"), U("InternalError"), 5)); + const auto nc_device_generic_state_ = nmos::details::make_nc_datatype_descriptor_enum(U("Device generic operational state"), U("NcDeviceGenericState"), items, value::null()); + + BST_REQUIRE_EQUAL(nc_device_generic_state, nc_device_generic_state_); +} + +BST_TEST_CASE(testNcDatatypeDescriptorPrimitive) +{ + using web::json::value_of; + using web::json::value; + + const auto test_primitive = value_of({ + { U("description"), U("Primitive datatype descriptor") }, + { U("name"), U("test_primitive") }, + { U("type"), 0 }, + { U("constraints"), value::null() } + }); + + const auto test_primitive_ = nmos::details::make_nc_datatype_descriptor_primitive(U("Primitive datatype descriptor"), U("test_primitive"), value::null()); + + BST_REQUIRE_EQUAL(test_primitive, test_primitive_); +} + +BST_TEST_CASE(testNcClassId) +{ + BST_REQUIRE_EQUAL(false, nmos::is_nc_block({ })); + BST_REQUIRE_EQUAL(false, nmos::is_nc_block({ 1 })); + BST_REQUIRE_EQUAL(false, nmos::is_nc_block({ 1, 2 })); + BST_REQUIRE_EQUAL(false, nmos::is_nc_block({ 1, 2, 0 })); + BST_REQUIRE(nmos::is_nc_block(nmos::nc_block_class_id)); + BST_REQUIRE(nmos::is_nc_block(nmos::make_nc_class_id(nmos::nc_block_class_id, { 1 }))); + + BST_REQUIRE_EQUAL(false, nmos::is_nc_worker({ })); + BST_REQUIRE_EQUAL(false, nmos::is_nc_worker({ 1 })); + BST_REQUIRE_EQUAL(false, nmos::is_nc_worker({ 1, 1 })); + BST_REQUIRE_EQUAL(false, nmos::is_nc_worker({ 1, 1, 1 })); + BST_REQUIRE(nmos::is_nc_worker(nmos::nc_worker_class_id)); + BST_REQUIRE(nmos::is_nc_worker(nmos::make_nc_class_id(nmos::nc_worker_class_id, { 1 }))); + + BST_REQUIRE_EQUAL(false, nmos::is_nc_manager({ })); + BST_REQUIRE_EQUAL(false, nmos::is_nc_manager({ 1 })); + BST_REQUIRE_EQUAL(false, nmos::is_nc_manager({ 1, 1 })); + BST_REQUIRE_EQUAL(false, nmos::is_nc_manager({ 1, 1, 1 })); + BST_REQUIRE(nmos::is_nc_manager(nmos::nc_manager_class_id)); + BST_REQUIRE(nmos::is_nc_manager(nmos::make_nc_class_id(nmos::nc_manager_class_id, { 1 }))); + + BST_REQUIRE_EQUAL(false, nmos::is_nc_device_manager({ })); + BST_REQUIRE_EQUAL(false, nmos::is_nc_device_manager({ 1 })); + BST_REQUIRE_EQUAL(false, nmos::is_nc_device_manager({ 1, 1 })); + BST_REQUIRE_EQUAL(false, nmos::is_nc_device_manager({ 1, 1, 1 })); + BST_REQUIRE_EQUAL(false, nmos::is_nc_device_manager({ 1, 3, 2 })); + BST_REQUIRE(nmos::is_nc_device_manager(nmos::nc_device_manager_class_id)); + BST_REQUIRE(nmos::is_nc_device_manager(nmos::make_nc_class_id(nmos::nc_device_manager_class_id, { 1 }))); + + BST_REQUIRE_EQUAL(false, nmos::is_nc_class_manager({ })); + BST_REQUIRE_EQUAL(false, nmos::is_nc_class_manager({ 1 })); + BST_REQUIRE_EQUAL(false, nmos::is_nc_class_manager({ 1, 1 })); + BST_REQUIRE_EQUAL(false, nmos::is_nc_class_manager({ 1, 1, 1 })); + BST_REQUIRE_EQUAL(false, nmos::is_nc_class_manager({ 1, 3, 1 })); + BST_REQUIRE(nmos::is_nc_class_manager(nmos::nc_class_manager_class_id)); + BST_REQUIRE(nmos::is_nc_class_manager(nmos::make_nc_class_id(nmos::nc_class_manager_class_id, { 1 }))); +} + +BST_TEST_CASE(testFindProperty) +{ + auto& nc_block_members_property_id = nmos::nc_block_members_property_id; + auto& nc_block_class_id = nmos::nc_block_class_id; + auto& nc_worker_class_id = nmos::nc_worker_class_id; + const auto invalid_property_id = nmos::nc_property_id(1000, 1000); + const auto invalid_class_id = nmos::nc_class_id({ 1000, 1000 }); + + nmos::experimental::control_protocol_state control_protocol_state; + auto get_control_protocol_class_descriptor = nmos::make_get_control_protocol_class_descriptor_handler(control_protocol_state); + + { + // valid - find members property in NcBlock + auto property = nmos::find_property_descriptor(nc_block_members_property_id, nc_block_class_id, get_control_protocol_class_descriptor); + BST_REQUIRE(!property.is_null()); + } + { + // invalid - find members property in NcWorker + auto property = nmos::find_property_descriptor(nc_block_members_property_id, nc_worker_class_id, get_control_protocol_class_descriptor); + BST_REQUIRE(property.is_null()); + } + { + // invalid - find unknown propertry in NcBlock + auto property = nmos::find_property_descriptor(invalid_property_id, nc_block_class_id, get_control_protocol_class_descriptor); + BST_REQUIRE(property.is_null()); + } + { + // invalid - find unknown property in unknown class + auto property = nmos::find_property_descriptor(invalid_property_id, invalid_class_id, get_control_protocol_class_descriptor); + BST_REQUIRE(property.is_null()); + } +} + +BST_TEST_CASE(testConstraints) +{ + using web::json::value_of; + using web::json::value; + + const nmos::nc_property_id property_string_id{ 100, 1 }; + const nmos::nc_property_id property_int32_id{ 100, 2 }; + const nmos::nc_property_id unknown_property_id{ 100, 3 }; + + // constraints + + // runtime constraints + const auto runtime_property_string_constraints = nmos::details::make_nc_property_constraints_string(property_string_id, 10, U("^[0-9]+$")); + const auto runtime_property_int32_constraints = nmos::details::make_nc_property_constraints_number(property_int32_id, 10, 1000, 1); + + const auto runtime_property_constraints = value_of({ + { runtime_property_string_constraints }, + { runtime_property_int32_constraints } + }); + + // propertry constraints + const auto property_string_constraints = nmos::details::make_nc_parameter_constraints_string(5, U("^[a-z]+$")); + const auto property_int32_constraints = nmos::details::make_nc_parameter_constraints_number(50, 500, 5); + + // datatype constraints + const auto datatype_string_constraints = nmos::details::make_nc_parameter_constraints_string(2, U("^[0-9a-z]+$")); + const auto datatype_int32_constraints = nmos::details::make_nc_parameter_constraints_number(100, 250, 10); + + // datatypes + const auto no_constraints_bool_datatype = nmos::details::make_nc_datatype_typedef(U("No constraints boolean datatype"), U("NoConstraintsBoolean"), false, U("NcBoolean"), value::null()); + const auto no_constraints_int16_datatype = nmos::details::make_nc_datatype_typedef(U("No constraints int16 datatype"), U("NoConstraintsInt16"), false, U("NcInt16"), value::null()); + const auto no_constraints_int32_datatype = nmos::details::make_nc_datatype_typedef(U("No constraints int32 datatype"), U("NoConstraintsInt32"), false, U("NcInt32"), value::null()); + const auto no_constraints_int64_datatype = nmos::details::make_nc_datatype_typedef(U("No constraints int64 datatype"), U("NoConstraintsInt64"), false, U("NcInt64"), value::null()); + const auto no_constraints_uint16_datatype = nmos::details::make_nc_datatype_typedef(U("No constraints uint16 datatype"), U("NoConstraintsUint16"), false, U("NcUint16"), value::null()); + const auto no_constraints_uint32_datatype = nmos::details::make_nc_datatype_typedef(U("No constraints uint32 datatype"), U("NoConstraintsUint32"), false, U("NcUint32"), value::null()); + const auto no_constraints_uint64_datatype = nmos::details::make_nc_datatype_typedef(U("No constraints uint64 datatype"), U("NoConstraintsUint64"), false, U("NcUint64"), value::null()); + const auto no_constraints_float32_datatype = nmos::details::make_nc_datatype_typedef(U("No constraints float32 datatype"), U("NoConstraintsFloat32"), false, U("NcFloat32"), value::null()); + const auto no_constraints_float64_datatype = nmos::details::make_nc_datatype_typedef(U("No constraints float64 datatype"), U("NoConstraintsFloat64"), false, U("NcFloat64"), value::null()); + const auto no_constraints_string_datatype = nmos::details::make_nc_datatype_typedef(U("No constraints string datatype"), U("NoConstraintsString"), false, U("NcString"), value::null()); + const auto with_constraints_string_datatype = nmos::details::make_nc_datatype_typedef(U("With constraints string datatype"), U("WithConstraintsString"), false, U("NcString"), datatype_string_constraints); + const auto with_constraints_int32_datatype = nmos::details::make_nc_datatype_typedef(U("With constraints int32 datatype"), U("WithConstraintsInt32"), false, U("NcInt32"), datatype_int32_constraints); + const auto no_constraints_int32_seq_datatype = nmos::details::make_nc_datatype_typedef(U("No constraints int64 datatype"), U("NoConstraintsInt64"), true, U("NcInt32"), value::null()); + const auto no_constraints_string_seq_datatype = nmos::details::make_nc_datatype_typedef(U("No constraints string datatype"), U("NoConstraintsString"), true, U("NcString"), value::null()); + + enum enum_value { foo, bar, baz }; + auto items = value::array(); + web::json::push_back(items, nmos::details::make_nc_enum_item_descriptor(U("foo"), U("foo"), enum_value::foo)); + web::json::push_back(items, nmos::details::make_nc_enum_item_descriptor(U("bar"), U("bar"), enum_value::bar)); + web::json::push_back(items, nmos::details::make_nc_enum_item_descriptor(U("baz"), U("baz"), enum_value::baz)); + const auto enum_datatype = nmos::details::make_nc_datatype_descriptor_enum(U("enum datatype"), U("enumDatatype"), items, value::null()); // no datatype constraints for enum datatype + + auto simple_struct_fields = value::array(); + web::json::push_back(simple_struct_fields, nmos::details::make_nc_field_descriptor(U("simple enum property example"), U("simpleEnumProperty"), U("enumDatatype"), false, false, value::null())); // no field constraints for enum field, as it is already described by its type + web::json::push_back(simple_struct_fields, nmos::details::make_nc_field_descriptor(U("simple string property example"), U("simpleStringProperty"), U("NcString"), false, false, datatype_string_constraints)); + web::json::push_back(simple_struct_fields, nmos::details::make_nc_field_descriptor(U("simple number property example"), U("simpleNumberProperty"), U("NcInt32"), false, false, datatype_int32_constraints)); + web::json::push_back(simple_struct_fields, nmos::details::make_nc_field_descriptor(U("simle boolean property example"), U("simpleBooleanProperty"), U("NcBoolean"), false, false, value::null())); // no field constraints for boolean field, as it is already described by its type + const auto simple_struct_datatype = nmos::details::make_nc_datatype_descriptor_struct(U("simple struct datatype"), U("simpleStructDatatype"), simple_struct_fields, value::null()); // no datatype constraints for struct datatype + + auto fields = value::array(); + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("Enum property example"), U("enumProperty"), U("enumDatatype"), false, false, value::null())); // no field constraints for enum field, as it is already described by its type + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("String property example"), U("stringProperty"), U("NcString"), false, false, datatype_string_constraints)); + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("Number property example"), U("numberProperty"), U("NcInt32"), false, false, datatype_int32_constraints)); + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("Boolean property example"), U("booleanProperty"), U("NcBoolean"), false, false, value::null())); // no field constraints for boolean field, as it is already described by its type + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("Struct property example"), U("structProperty"), U("simpleStructDatatype"), false, false, value::null())); // no datatype constraints for struct datatype + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("Sequence enum property example"), U("sequenceEnumProperty"), U("enumDatatype"), false, false, value::null())); // no field constraints for enum field, as it is already described by its type + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("Sequence string property example"), U("sequenceStringProperty"), U("NcString"), false, false, datatype_string_constraints)); + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("Sequence number property example"), U("sequenceNumberProperty"), U("NcInt32"), false, false, datatype_int32_constraints)); + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("Sequence boolean property example"), U("sequenceBooleanProperty"), U("NcBoolean"), false, false, value::null())); // no field constraints for boolean field, as it is already described by its type + web::json::push_back(fields, nmos::details::make_nc_field_descriptor(U("Sequence struct property example"), U("sequenceStructProperty"), U("simpleStructDatatype"), false, false, value::null())); // no field constraints for struct field + const auto struct_datatype = nmos::details::make_nc_datatype_descriptor_struct(U("struct datatype"), U("structDatatype"), fields, value::null()); // no datatype constraints for struct datatype + + // setup datatypes in control_protocol_state + nmos::experimental::control_protocol_state control_protocol_state; + control_protocol_state.insert(nmos::experimental::datatype_descriptor{ no_constraints_int16_datatype }); + control_protocol_state.insert(nmos::experimental::datatype_descriptor{ no_constraints_int32_datatype }); + control_protocol_state.insert(nmos::experimental::datatype_descriptor{ no_constraints_int64_datatype }); + control_protocol_state.insert(nmos::experimental::datatype_descriptor{ no_constraints_uint16_datatype }); + control_protocol_state.insert(nmos::experimental::datatype_descriptor{ no_constraints_uint32_datatype }); + control_protocol_state.insert(nmos::experimental::datatype_descriptor{ no_constraints_uint64_datatype }); + control_protocol_state.insert(nmos::experimental::datatype_descriptor{ no_constraints_string_datatype }); + control_protocol_state.insert(nmos::experimental::datatype_descriptor{ with_constraints_int32_datatype }); + control_protocol_state.insert(nmos::experimental::datatype_descriptor{ with_constraints_string_datatype }); + control_protocol_state.insert(nmos::experimental::datatype_descriptor{ enum_datatype }); + control_protocol_state.insert(nmos::experimental::datatype_descriptor{ simple_struct_datatype }); + control_protocol_state.insert(nmos::experimental::datatype_descriptor{ no_constraints_int32_seq_datatype }); + control_protocol_state.insert(nmos::experimental::datatype_descriptor{ no_constraints_string_seq_datatype }); + + // test get_runtime_property_constraints + BST_REQUIRE_EQUAL(nmos::details::get_runtime_property_constraints(property_string_id, runtime_property_constraints), runtime_property_string_constraints); + BST_REQUIRE_EQUAL(nmos::details::get_runtime_property_constraints(property_int32_id, runtime_property_constraints), runtime_property_int32_constraints); + BST_REQUIRE_EQUAL(nmos::details::get_runtime_property_constraints(unknown_property_id, runtime_property_constraints), value::null()); + + // string property constraints validation + + // runtime property constraints validation + const nmos::details::datatype_constraints_validation_parameters with_constraints_string_constraints_validation_params{ with_constraints_string_datatype, nmos::make_get_control_protocol_datatype_descriptor_handler(control_protocol_state) }; + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(value::string(U("1234567890")), runtime_property_string_constraints, property_string_constraints, with_constraints_string_constraints_validation_params)); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value::string(U("12345678901")), runtime_property_string_constraints, property_string_constraints, with_constraints_string_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value::string(U("123456789A")), runtime_property_string_constraints, property_string_constraints, with_constraints_string_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(value_of({ value::string(U("1234567890")), value::string(U("1234567890")) }), runtime_property_string_constraints, property_string_constraints, with_constraints_string_constraints_validation_params)); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value_of({ value::string(U("1234567890")), value::string(U("12345678901")) }), runtime_property_string_constraints, property_string_constraints, with_constraints_string_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value_of({ value::string(U("1234567890")), 1 }), runtime_property_string_constraints, property_string_constraints, with_constraints_string_constraints_validation_params), nmos::control_protocol_exception); + // property constraints validation + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(value::string(U("abcde")), value::null(), property_string_constraints, with_constraints_string_constraints_validation_params)); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value::string(U("abcdef")), value::null(), property_string_constraints, with_constraints_string_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value::string(U("abcd1")), value::null(), property_string_constraints, with_constraints_string_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(value_of({ value::string(U("abcde")), value::string(U("abcde")) }), value::null(), property_string_constraints, with_constraints_string_constraints_validation_params)); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value_of({ value::string(U("abcde")), value::string(U("abcdef")) }), value::null(), property_string_constraints, with_constraints_string_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value_of({ value::string(U("abcde")), 1 }), value::null(), property_string_constraints, with_constraints_string_constraints_validation_params), nmos::control_protocol_exception); + // datatype constraints validation + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(value::string(U("1a")), value::null(), value::null(), with_constraints_string_constraints_validation_params)); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value::string(U("1a2")), value::null(), value::null(), with_constraints_string_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value::string(U("1*")), value::null(), value::null(), with_constraints_string_constraints_validation_params), nmos::control_protocol_exception); + const nmos::details::datatype_constraints_validation_parameters no_constraints_string_constraints_validation_params{ no_constraints_string_datatype, nmos::make_get_control_protocol_datatype_descriptor_handler(control_protocol_state) }; + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(value::string(U("1234567890-abcde-!\"£$%^&*()_+=")), value::null(), value::null(), no_constraints_string_constraints_validation_params)); + + // number property constraints validation + + // runtime property constraints validation + const nmos::details::datatype_constraints_validation_parameters with_constraints_int32_constraints_validation_params{ with_constraints_int32_datatype, nmos::make_get_control_protocol_datatype_descriptor_handler(control_protocol_state) }; + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(10, runtime_property_int32_constraints, property_int32_constraints, with_constraints_int32_constraints_validation_params)); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(1000, runtime_property_int32_constraints, property_int32_constraints, with_constraints_int32_constraints_validation_params)); + BST_REQUIRE_THROW(nmos::details::constraints_validation(9, runtime_property_int32_constraints, property_int32_constraints, with_constraints_int32_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(1001, runtime_property_int32_constraints, property_int32_constraints, with_constraints_int32_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(value_of({ 10, 1000 }), runtime_property_int32_constraints, property_int32_constraints, with_constraints_int32_constraints_validation_params)); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value_of({ 10, 1001 }), runtime_property_int32_constraints, property_int32_constraints, with_constraints_int32_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value_of({ 10, value::string(U("a")) }), runtime_property_int32_constraints, property_int32_constraints, with_constraints_int32_constraints_validation_params), nmos::control_protocol_exception); + // property constraints validation + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(50, value::null(), property_int32_constraints, with_constraints_int32_constraints_validation_params)); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(500, value::null(), property_int32_constraints, with_constraints_int32_constraints_validation_params)); + BST_REQUIRE_THROW(nmos::details::constraints_validation(45, value::null(), property_int32_constraints, with_constraints_int32_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(505, value::null(), property_int32_constraints, with_constraints_int32_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(499, value::null(), property_int32_constraints, with_constraints_int32_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(value_of({ 50, 500 }), value::null(), property_int32_constraints, with_constraints_int32_constraints_validation_params)); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value_of({ 49, 500 }), value::null(), property_int32_constraints, with_constraints_int32_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value_of({ 50, 501 }), value::null(), property_int32_constraints, with_constraints_int32_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value_of({ 45, 500 }), value::null(), property_int32_constraints, with_constraints_int32_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value_of({ 50, value::string(U("a")) }), value::null(), property_int32_constraints, with_constraints_int32_constraints_validation_params), nmos::control_protocol_exception); + // datatype constraints validation + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(100, value::null(), value::null(), with_constraints_int32_constraints_validation_params)); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(250, value::null(), value::null(), with_constraints_int32_constraints_validation_params)); + BST_REQUIRE_THROW(nmos::details::constraints_validation(90, value::null(), value::null(), with_constraints_int32_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(260, value::null(), value::null(), with_constraints_int32_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(99, value::null(), value::null(), with_constraints_int32_constraints_validation_params), nmos::control_protocol_exception); + // int16 datatype constraints validation + const nmos::details::datatype_constraints_validation_parameters no_constraints_int16_constraints_validation_params{ no_constraints_int16_datatype, nmos::make_get_control_protocol_datatype_descriptor_handler(control_protocol_state) }; + BST_REQUIRE_THROW(nmos::details::constraints_validation(int64_t(std::numeric_limits::min()) - 1, value::null(), value::null(), no_constraints_int16_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(int64_t(std::numeric_limits::max()) + 1, value::null(), value::null(), no_constraints_int16_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(std::numeric_limits::min(), value::null(), value::null(), no_constraints_int16_constraints_validation_params)); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(std::numeric_limits::max(), value::null(), value::null(), no_constraints_int16_constraints_validation_params)); + // int32 datatype constraints validation + const nmos::details::datatype_constraints_validation_parameters no_constraints_int32_constraints_validation_params{ no_constraints_int32_datatype, nmos::make_get_control_protocol_datatype_descriptor_handler(control_protocol_state) }; + BST_REQUIRE_THROW(nmos::details::constraints_validation(int64_t(std::numeric_limits::min()) - 1, value::null(), value::null(), no_constraints_int32_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(int64_t(std::numeric_limits::max()) + 1, value::null(), value::null(), no_constraints_int32_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(std::numeric_limits::min(), value::null(), value::null(), no_constraints_int32_constraints_validation_params)); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(std::numeric_limits::max(), value::null(), value::null(), no_constraints_int32_constraints_validation_params)); + // int64 datatype constraints validation + const nmos::details::datatype_constraints_validation_parameters no_constraints_int64_constraints_validation_params{ no_constraints_int64_datatype, nmos::make_get_control_protocol_datatype_descriptor_handler(control_protocol_state) }; + BST_REQUIRE_THROW(nmos::details::constraints_validation(std::numeric_limits::min(), value::null(), value::null(), no_constraints_int64_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(std::numeric_limits::max(), value::null(), value::null(), no_constraints_int64_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(std::numeric_limits::min(), value::null(), value::null(), no_constraints_int64_constraints_validation_params)); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(std::numeric_limits::max(), value::null(), value::null(), no_constraints_int64_constraints_validation_params)); + // uint16 datatype constraints validation + const nmos::details::datatype_constraints_validation_parameters no_constraints_uint16_constraints_validation_params{ no_constraints_uint16_datatype, nmos::make_get_control_protocol_datatype_descriptor_handler(control_protocol_state) }; + BST_REQUIRE_THROW(nmos::details::constraints_validation(-1, value::null(), value::null(), no_constraints_uint16_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(uint64_t(std::numeric_limits::max()) + 1, value::null(), value::null(), no_constraints_uint16_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(std::numeric_limits::min(), value::null(), value::null(), no_constraints_uint16_constraints_validation_params)); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(std::numeric_limits::max(), value::null(), value::null(), no_constraints_uint16_constraints_validation_params)); + // uint32 datatype constraints validation + const nmos::details::datatype_constraints_validation_parameters no_constraints_uint32_constraints_validation_params{ no_constraints_uint32_datatype, nmos::make_get_control_protocol_datatype_descriptor_handler(control_protocol_state) }; + BST_REQUIRE_THROW(nmos::details::constraints_validation(-1, value::null(), value::null(), no_constraints_uint32_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(uint64_t(std::numeric_limits::max()) + 1, value::null(), value::null(), no_constraints_uint32_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(std::numeric_limits::min(), value::null(), value::null(), no_constraints_uint32_constraints_validation_params)); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(std::numeric_limits::max(), value::null(), value::null(), no_constraints_uint32_constraints_validation_params)); + // uint64 datatype constraints validation + const nmos::details::datatype_constraints_validation_parameters no_constraints_uint64_constraints_validation_params{ no_constraints_uint64_datatype, nmos::make_get_control_protocol_datatype_descriptor_handler(control_protocol_state) }; + BST_REQUIRE_THROW(nmos::details::constraints_validation(-1, value::null(), value::null(), no_constraints_uint64_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(std::numeric_limits::max(), value::null(), value::null(), no_constraints_int64_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(std::numeric_limits::min(), value::null(), value::null(), no_constraints_uint64_constraints_validation_params)); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(std::numeric_limits::max(), value::null(), value::null(), no_constraints_uint64_constraints_validation_params)); + // float32 datatype constraints validation + const nmos::details::datatype_constraints_validation_parameters no_constraints_float32_constraints_validation_params{ no_constraints_float32_datatype, nmos::make_get_control_protocol_datatype_descriptor_handler(control_protocol_state) }; + BST_REQUIRE_THROW(nmos::details::constraints_validation(std::numeric_limits::min(), value::null(), value::null(), no_constraints_float32_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(std::numeric_limits::max(), value::null(), value::null(), no_constraints_float32_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(std::numeric_limits::min(), value::null(), value::null(), no_constraints_float32_constraints_validation_params)); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(std::numeric_limits::max(), value::null(), value::null(), no_constraints_float32_constraints_validation_params)); + // float64 datatype constraints validation + const nmos::details::datatype_constraints_validation_parameters no_constraints_float64_constraints_validation_params{ no_constraints_float64_datatype, nmos::make_get_control_protocol_datatype_descriptor_handler(control_protocol_state) }; + BST_REQUIRE_THROW(nmos::details::constraints_validation(1000, value::null(), value::null(), no_constraints_float64_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(1000.0, value::null(), value::null(), no_constraints_float64_constraints_validation_params)); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(std::numeric_limits::min(), value::null(), value::null(), no_constraints_float64_constraints_validation_params)); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(std::numeric_limits::max(), value::null(), value::null(), no_constraints_float64_constraints_validation_params)); + // enum property datatype constraints validation + const nmos::details::datatype_constraints_validation_parameters enum_constraints_validation_params{ enum_datatype, nmos::make_get_control_protocol_datatype_descriptor_handler(control_protocol_state) }; + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(enum_value::foo, value::null(), value::null(), enum_constraints_validation_params)); + BST_REQUIRE_THROW(nmos::details::constraints_validation(4, value::null(), value::null(), enum_constraints_validation_params), nmos::control_protocol_exception); + // invalid data vs primitive datatype constraints + const nmos::details::datatype_constraints_validation_parameters no_constraints_string_seq_constraints_validation_params{ no_constraints_string_seq_datatype, nmos::make_get_control_protocol_datatype_descriptor_handler(control_protocol_state) }; + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(value_of({ value::string(U("1234567890-abcde-!\"£$%^&*()_+=")) }), value::null(), value::null(), no_constraints_string_seq_constraints_validation_params)); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(value_of({ value::string(U("1234567890-abcde-!\"£$%^&*()_+=")), value::string(U("1234567890-abcde-!\"£$%^&*()_+=")) }), value::null(), value::null(), no_constraints_string_seq_constraints_validation_params)); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value_of({ 1 }), value::null(), value::null(), no_constraints_string_seq_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(1, value::null(), value::null(), no_constraints_string_seq_constraints_validation_params), nmos::control_protocol_exception); + const nmos::details::datatype_constraints_validation_parameters no_constraints_int32_seq_constraints_validation_params{ no_constraints_int32_seq_datatype, nmos::make_get_control_protocol_datatype_descriptor_handler(control_protocol_state) }; + BST_REQUIRE_THROW(nmos::details::constraints_validation(value_of({ value::string(U("1234567890-abcde-!\"£$%^&*()_+=")) }), value::null(), value::null(), no_constraints_int32_seq_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(value_of({ 1 }), value::null(), value::null(), no_constraints_int32_seq_constraints_validation_params)); + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(value_of({ 1, 2 }), value::null(), value::null(), no_constraints_int32_seq_constraints_validation_params)); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value_of({ value::string(U("1234567890-abcde-!\"£$%^&*()_+=")) }), value::null(), value::null(), no_constraints_int32_seq_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value::string(U("1234567890-abcde-!\"£$%^&*()_+=")), value::null(), value::null(), no_constraints_int32_seq_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value_of({ 1, 2 }), value::null(), value::null(), with_constraints_int32_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(value_of({ 1, 2 }), value::null(), value::null(), with_constraints_string_constraints_validation_params), nmos::control_protocol_exception); + + // struct property datatype constraints validation + const auto good_struct = value_of({ + { U("enumProperty"), enum_value::baz }, + { U("stringProperty"), U("xy") }, + { U("numberProperty"), 100 }, + { U("booleanProperty"), true }, + { U("structProperty"), value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }) }, + { U("sequenceEnumProperty"), value_of({ enum_value::foo, enum_value::bar }) }, + { U("sequenceStringProperty"), value_of({ U("aa"), U("bb") }) }, + { U("sequenceNumberProperty"), value_of({ 100, 110 }) }, + { U("sequenceBooleanProperty"), value_of({ true, false }) }, + { U("sequenceStructProperty"), value_of({ + value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }), value_of({ + { U("simpleEnumProperty"), enum_value::foo }, + { U("simpleStringProperty"), U("ab") }, + { U("simpleNumberProperty"), 200 }, + { U("simpleBooleanProperty"), false } + }) }) } + }); + // missing field + const auto bad_struct1 = value_of({ + { U("stringProperty"), U("xy") }, + { U("numberProperty"), 100 }, + { U("booleanProperty"), true }, + { U("structProperty"), value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }) }, + { U("sequenceEnumProperty"), value_of({ enum_value::foo, enum_value::bar }) }, + { U("sequenceStringProperty"), value_of({ U("aa"), U("bb") }) }, + { U("sequenceNumberProperty"), value_of({ 100, 110 }) }, + { U("sequenceBooleanProperty"), value_of({ true, false }) }, + { U("sequenceStructProperty"), value_of({ + value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }), value_of({ + { U("simpleEnumProperty"), enum_value::foo }, + { U("simpleStringProperty"), U("ab") }, + { U("simpleNumberProperty"), 200 }, + { U("simpleBooleanProperty"), false } + }) }) } + }); + // invalid fields + const auto bad_struct2 = value_of({ + { U("enumProperty"), 3 }, // bad value + { U("stringProperty"), U("xy") }, + { U("numberProperty"), 100 }, + { U("booleanProperty"), true }, + { U("structProperty"), value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }) }, + { U("sequenceEnumProperty"), value_of({ enum_value::foo, enum_value::bar }) }, + { U("sequenceStringProperty"), value_of({ U("aa"), U("bb") }) }, + { U("sequenceNumberProperty"), value_of({ 100, 110 }) }, + { U("sequenceBooleanProperty"), value_of({ true, false }) }, + { U("sequenceStructProperty"), value_of({ + value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }), value_of({ + { U("simpleEnumProperty"), enum_value::foo }, + { U("simpleStringProperty"), U("ab") }, + { U("simpleNumberProperty"), 200 }, + { U("simpleBooleanProperty"), false } + }) }) } + }); + const auto bad_struct2_1 = value_of({ + { U("enumProperty"), enum_value::foo }, + { U("stringProperty"), U("xyz") }, // bad value + { U("numberProperty"), 100 }, + { U("booleanProperty"), true }, + { U("structProperty"), value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }) }, + { U("sequenceEnumProperty"), value_of({ enum_value::foo, enum_value::bar }) }, + { U("sequenceStringProperty"), value_of({ U("aa"), U("bb") }) }, + { U("sequenceNumberProperty"), value_of({ 100, 110 }) }, + { U("sequenceBooleanProperty"), value_of({ true, false }) }, + { U("sequenceStructProperty"), value_of({ + value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }), value_of({ + { U("simpleEnumProperty"), enum_value::foo }, + { U("simpleStringProperty"), U("ab") }, + { U("simpleNumberProperty"), 200 }, + { U("simpleBooleanProperty"), false } + }) }) } + }); + const auto bad_struct2_2 = value_of({ + { U("enumProperty"), enum_value::foo }, + { U("stringProperty"), U("x£") }, // bad value + { U("numberProperty"), 100 }, + { U("booleanProperty"), true }, + { U("structProperty"), value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }) }, + { U("sequenceEnumProperty"), value_of({ enum_value::foo, enum_value::bar }) }, + { U("sequenceStringProperty"), value_of({ U("aa"), U("bb") }) }, + { U("sequenceNumberProperty"), value_of({ 100, 110 }) }, + { U("sequenceBooleanProperty"), value_of({ true, false }) }, + { U("sequenceStructProperty"), value_of({ + value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }), value_of({ + { U("simpleEnumProperty"), enum_value::foo }, + { U("simpleStringProperty"), U("ab") }, + { U("simpleNumberProperty"), 200 }, + { U("simpleBooleanProperty"), false } + }) }) } + }); + const auto bad_struct2_3 = value_of({ + { U("enumProperty"), enum_value::foo }, + { U("stringProperty"), U("xy") }, + { U("numberProperty"), 99 }, // bad value + { U("booleanProperty"), true }, + { U("sequenceEnumProperty"), value_of({ enum_value::foo, enum_value::bar }) }, + { U("sequenceStringProperty"), value_of({ U("aa"), U("bb") }) }, + { U("sequenceNumberProperty"), value_of({ 100, 110 }) }, + { U("sequenceBooleanProperty"), value_of({ true, false }) }, + { U("sequenceStructProperty"), value_of({ + value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }), value_of({ + { U("simpleEnumProperty"), enum_value::foo }, + { U("simpleStringProperty"), U("ab") }, + { U("simpleNumberProperty"), 200 }, + { U("simpleBooleanProperty"), false } + }) }) } + }); + const auto bad_struct2_4 = value_of({ + { U("enumProperty"), enum_value::foo }, + { U("stringProperty"), U("xy") }, + { U("numberProperty"), 100 }, + { U("booleanProperty"), 0 }, // bad value + { U("structProperty"), value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }) }, + { U("sequenceEnumProperty"), value_of({ enum_value::foo, enum_value::bar }) }, + { U("sequenceStringProperty"), value_of({ U("aa"), U("bb") }) }, + { U("sequenceNumberProperty"), value_of({ 100, 110 }) }, + { U("sequenceBooleanProperty"), value_of({ true, false }) }, + { U("sequenceStructProperty"), value_of({ + value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }), value_of({ + { U("simpleEnumProperty"), enum_value::foo }, + { U("simpleStringProperty"), U("ab") }, + { U("simpleNumberProperty"), 200 }, + { U("simpleBooleanProperty"), false } + }) }) } + }); + const auto bad_struct2_5 = value_of({ + { U("enumProperty"), enum_value::foo }, + { U("stringProperty"), U("xy") }, + { U("numberProperty"), 100 }, + { U("booleanProperty"), true }, + { U("structProperty"), value_of({ + { U("simpleEnumProperty"), 3 }, // bad value + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }) }, + { U("sequenceEnumProperty"), value_of({ enum_value::foo, enum_value::bar }) }, + { U("sequenceStringProperty"), value_of({ U("aa"), U("bb") }) }, + { U("sequenceNumberProperty"), value_of({ 100, 110 }) }, + { U("sequenceBooleanProperty"), value_of({ true, false }) }, + { U("sequenceStructProperty"), value_of({ + value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }), value_of({ + { U("simpleEnumProperty"), enum_value::foo }, + { U("simpleStringProperty"), U("ab") }, + { U("simpleNumberProperty"), 200 }, + { U("simpleBooleanProperty"), false } + }) }) } + }); + const auto bad_struct2_5_1 = value_of({ + { U("enumProperty"), enum_value::foo }, + { U("stringProperty"), U("xy") }, + { U("numberProperty"), 100 }, + { U("booleanProperty"), true }, + { U("structProperty"), value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xyz") }, // bad value + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }) }, + { U("sequenceEnumProperty"), value_of({ enum_value::foo, enum_value::bar }) }, + { U("sequenceStringProperty"), value_of({ U("aa"), U("bb") }) }, + { U("sequenceNumberProperty"), value_of({ 100, 110 }) }, + { U("sequenceBooleanProperty"), value_of({ true, false }) }, + { U("sequenceStructProperty"), value_of({ + value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }), value_of({ + { U("simpleEnumProperty"), enum_value::foo }, + { U("simpleStringProperty"), U("ab") }, + { U("simpleNumberProperty"), 200 }, + { U("simpleBooleanProperty"), false } + }) }) } + }); + const auto bad_struct2_5_2 = value_of({ + { U("enumProperty"), enum_value::foo }, + { U("stringProperty"), U("xy") }, + { U("numberProperty"), 100 }, + { U("booleanProperty"), true }, + { U("structProperty"), value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 99 }, // bad value + { U("simpleBooleanProperty"), true } + }) }, + { U("sequenceEnumProperty"), value_of({ enum_value::foo, enum_value::bar }) }, + { U("sequenceStringProperty"), value_of({ U("aa"), U("bb") }) }, + { U("sequenceNumberProperty"), value_of({ 100, 110 }) }, + { U("sequenceBooleanProperty"), value_of({ true, false }) }, + { U("sequenceStructProperty"), value_of({ + value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }), value_of({ + { U("simpleEnumProperty"), enum_value::foo }, + { U("simpleStringProperty"), U("ab") }, + { U("simpleNumberProperty"), 200 }, + { U("simpleBooleanProperty"), false } + }) }) } + }); + const auto bad_struct2_5_3 = value_of({ + { U("enumProperty"), enum_value::foo }, + { U("stringProperty"), U("xy") }, + { U("numberProperty"), 100 }, + { U("booleanProperty"), true }, + { U("structProperty"), value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), 3 } // bad value + }) }, + { U("sequenceEnumProperty"), value_of({ enum_value::foo, enum_value::bar }) }, + { U("sequenceStringProperty"), value_of({ U("aa"), U("bb") }) }, + { U("sequenceNumberProperty"), value_of({ 100, 110 }) }, + { U("sequenceBooleanProperty"), value_of({ true, false }) }, + { U("sequenceStructProperty"), value_of({ + value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }), value_of({ + { U("simpleEnumProperty"), enum_value::foo }, + { U("simpleStringProperty"), U("ab") }, + { U("simpleNumberProperty"), 200 }, + { U("simpleBooleanProperty"), false } + }) }) } + }); + const auto bad_struct2_6 = value_of({ + { U("enumProperty"), enum_value::foo }, + { U("stringProperty"), U("xy") }, + { U("numberProperty"), 100 }, + { U("booleanProperty"), true }, + { U("structProperty"), value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }) }, + { U("sequenceEnumProperty"), value_of({ enum_value::foo, enum_value::bar, 4 }) }, // bad value + { U("sequenceStringProperty"), value_of({ U("aa"), U("bb") }) }, + { U("sequenceNumberProperty"), value_of({ 100, 110 }) }, + { U("sequenceBooleanProperty"), value_of({ true, false }) }, + { U("sequenceStructProperty"), value_of({ + value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }), value_of({ + { U("simpleEnumProperty"), enum_value::foo }, + { U("simpleStringProperty"), U("ab") }, + { U("simpleNumberProperty"), 200 }, + { U("simpleBooleanProperty"), false } + }) }) } + }); + const auto bad_struct2_6_1 = value_of({ + { U("enumProperty"), enum_value::foo }, + { U("stringProperty"), U("xy") }, + { U("numberProperty"), 100 }, + { U("booleanProperty"), true }, + { U("structProperty"), value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }) }, + { U("sequenceEnumProperty"), value_of({ enum_value::foo, enum_value::bar }) }, + { U("sequenceStringProperty"), value_of({ U("aa"), U("bbb") }) }, // bad value + { U("sequenceNumberProperty"), value_of({ 100, 110 }) }, + { U("sequenceBooleanProperty"), value_of({ true, false }) }, + { U("sequenceStructProperty"), value_of({ + value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }), value_of({ + { U("simpleEnumProperty"), enum_value::foo }, + { U("simpleStringProperty"), U("ab") }, + { U("simpleNumberProperty"), 200 }, + { U("simpleBooleanProperty"), false } + }) }) } + }); + const auto bad_struct2_6_2 = value_of({ + { U("enumProperty"), enum_value::foo }, + { U("stringProperty"), U("xy") }, + { U("numberProperty"), 100 }, + { U("booleanProperty"), true }, + { U("structProperty"), value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }) }, + { U("sequenceEnumProperty"), value_of({ enum_value::foo, enum_value::bar }) }, + { U("sequenceStringProperty"), value_of({ U("aa"), U("bb") }) }, + { U("sequenceNumberProperty"), value_of({ 99, 110 }) }, // bad value + { U("sequenceBooleanProperty"), value_of({ true, false }) }, + { U("sequenceStructProperty"), value_of({ + value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }), value_of({ + { U("simpleEnumProperty"), enum_value::foo }, + { U("simpleStringProperty"), U("ab") }, + { U("simpleNumberProperty"), 200 }, + { U("simpleBooleanProperty"), false } + }) }) } + }); + const auto bad_struct2_6_3 = value_of({ + { U("enumProperty"), enum_value::foo }, + { U("stringProperty"), U("xy") }, + { U("numberProperty"), 100 }, + { U("booleanProperty"), true }, + { U("structProperty"), value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }) }, + { U("sequenceEnumProperty"), value_of({ enum_value::foo, enum_value::bar }) }, + { U("sequenceStringProperty"), value_of({ U("aa"), U("bb") }) }, + { U("sequenceNumberProperty"), value_of({ 100, 110 }) }, + { U("sequenceBooleanProperty"), value_of({ true, 0 }) }, // bad value + { U("sequenceStructProperty"), value_of({ + value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }), value_of({ + { U("simpleEnumProperty"), enum_value::foo }, + { U("simpleStringProperty"), U("ab") }, + { U("simpleNumberProperty"), 200 }, + { U("simpleBooleanProperty"), false } + }) }) } + }); + const auto bad_struct2_7 = value_of({ + { U("enumProperty"), enum_value::foo }, + { U("stringProperty"), U("xy") }, + { U("numberProperty"), 100 }, + { U("booleanProperty"), true }, + { U("structProperty"), value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }) }, + { U("sequenceEnumProperty"), value_of({ enum_value::foo, enum_value::bar }) }, + { U("sequenceStringProperty"), value_of({ U("aa"), U("bb") }) }, + { U("sequenceNumberProperty"), value_of({ 100, 110 }) }, + { U("sequenceBooleanProperty"), value_of({ true, false }) }, + { U("sequenceStructProperty"), value_of({ + value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }), value_of({ + { U("simpleEnumProperty"), 3 }, // bad value + { U("simpleStringProperty"), U("ab") }, + { U("simpleNumberProperty"), 200 }, + { U("simpleBooleanProperty"), false } + }) }) } + }); + const auto bad_struct2_7_1 = value_of({ + { U("enumProperty"), enum_value::foo }, + { U("stringProperty"), U("xy") }, + { U("numberProperty"), 100 }, + { U("booleanProperty"), true }, + { U("structProperty"), value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }) }, + { U("sequenceEnumProperty"), value_of({ enum_value::foo, enum_value::bar }) }, + { U("sequenceStringProperty"), value_of({ U("aa"), U("bb") }) }, + { U("sequenceNumberProperty"), value_of({ 100, 110 }) }, + { U("sequenceBooleanProperty"), value_of({ true, false }) }, + { U("sequenceStructProperty"), value_of({ + value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }), value_of({ + { U("simpleEnumProperty"), enum_value::foo }, + { U("simpleStringProperty"), U("abc") }, // bad value + { U("simpleNumberProperty"), 200 }, + { U("simpleBooleanProperty"), false } + }) }) } + }); + const auto bad_struct2_7_2 = value_of({ + { U("enumProperty"), enum_value::foo }, + { U("stringProperty"), U("xy") }, + { U("numberProperty"), 100 }, + { U("booleanProperty"), true }, + { U("structProperty"), value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }) }, + { U("sequenceEnumProperty"), value_of({ enum_value::foo, enum_value::bar }) }, + { U("sequenceStringProperty"), value_of({ U("aa"), U("bb") }) }, + { U("sequenceNumberProperty"), value_of({ 100, 110 }) }, + { U("sequenceBooleanProperty"), value_of({ true, false }) }, + { U("sequenceStructProperty"), value_of({ + value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }), value_of({ + { U("simpleEnumProperty"), enum_value::foo }, + { U("simpleStringProperty"), U("ab") }, + { U("simpleNumberProperty"), 251 }, // bad value + { U("simpleBooleanProperty"), false } + }) }) } + }); + const auto bad_struct2_7_3 = value_of({ + { U("enumProperty"), enum_value::foo }, + { U("stringProperty"), U("xy") }, + { U("numberProperty"), 100 }, + { U("booleanProperty"), true }, + { U("structProperty"), value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }) }, + { U("sequenceEnumProperty"), value_of({ enum_value::foo, enum_value::bar }) }, + { U("sequenceStringProperty"), value_of({ U("aa"), U("bb") }) }, + { U("sequenceNumberProperty"), value_of({ 100, 110 }) }, + { U("sequenceBooleanProperty"), value_of({ true, false }) }, + { U("sequenceStructProperty"), value_of({ + value_of({ + { U("simpleEnumProperty"), enum_value::bar }, + { U("simpleStringProperty"), U("xy") }, + { U("simpleNumberProperty"), 100 }, + { U("simpleBooleanProperty"), true } + }), value_of({ + { U("simpleEnumProperty"), enum_value::foo }, + { U("simpleStringProperty"), U("ab") }, + { U("simpleNumberProperty"), 200 }, + { U("simpleBooleanProperty"), 0 } // bad value + }) }) } + }); + + const nmos::details::datatype_constraints_validation_parameters struct_constraints_validation_params{ struct_datatype, nmos::make_get_control_protocol_datatype_descriptor_handler(control_protocol_state) }; + BST_REQUIRE_NO_THROW(nmos::details::constraints_validation(good_struct, value::null(), value::null(), struct_constraints_validation_params)); + BST_REQUIRE_THROW(nmos::details::constraints_validation(bad_struct1, value::null(), value::null(), struct_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(bad_struct2, value::null(), value::null(), struct_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(bad_struct2_1, value::null(), value::null(), struct_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(bad_struct2_2, value::null(), value::null(), struct_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(bad_struct2_3, value::null(), value::null(), struct_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(bad_struct2_4, value::null(), value::null(), struct_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(bad_struct2_5, value::null(), value::null(), struct_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(bad_struct2_5_1, value::null(), value::null(), struct_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(bad_struct2_5_2, value::null(), value::null(), struct_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(bad_struct2_5_3, value::null(), value::null(), struct_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(bad_struct2_6, value::null(), value::null(), struct_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(bad_struct2_6_1, value::null(), value::null(), struct_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(bad_struct2_6_2, value::null(), value::null(), struct_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(bad_struct2_6_3, value::null(), value::null(), struct_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(bad_struct2_7, value::null(), value::null(), struct_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(bad_struct2_7_1, value::null(), value::null(), struct_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(bad_struct2_7_2, value::null(), value::null(), struct_constraints_validation_params), nmos::control_protocol_exception); + BST_REQUIRE_THROW(nmos::details::constraints_validation(bad_struct2_7_3, value::null(), value::null(), struct_constraints_validation_params), nmos::control_protocol_exception); +} diff --git a/Development/nmos/type.h b/Development/nmos/type.h index d58734f81..8da37f685 100644 --- a/Development/nmos/type.h +++ b/Development/nmos/type.h @@ -39,6 +39,16 @@ namespace nmos // the System API global configuration resource type, see nmos/system_resources.h const type global{ U("global") }; + + // the Control Protocol API resource type, see nmos/control_protcol_resources.h + const type nc_block{ U("nc_block") }; + const type nc_worker{ U("nc_worker") }; + const type nc_manager{ U("nc_manager") }; + const type nc_device_manager{ U("nc_device_manager") }; + const type nc_class_manager{ U("nc_class_manager") }; + const type nc_receiver_monitor{ U("nc_receiver_monitor") }; + const type nc_receiver_monitor_protected{ U("nc_receiver_monitor_protected") }; + const type nc_ident_beacon{ U("nc_ident_beacon") }; } } diff --git a/Development/third_party/is-12/README.md b/Development/third_party/is-12/README.md new file mode 100644 index 000000000..44ab19110 --- /dev/null +++ b/Development/third_party/is-12/README.md @@ -0,0 +1,9 @@ +# AMWA IS-12 NMOS Control & Monitoring Protocol Specification + +This directory contains files from the [AMWA NMOS Control & Monitoring Protocol](https://github.com/AMWA-TV/is-12), in particular tagged versions of the JSON schemas used by the API specifications. + +Original source code: + +- (c) AMWA 2023 +- Licensed under the Apache License, Version 2.0; http://www.apache.org/licenses/LICENSE-2.0 + diff --git a/Development/third_party/is-12/v1.0.x/APIs/schemas/base-message.json b/Development/third_party/is-12/v1.0.x/APIs/schemas/base-message.json new file mode 100644 index 000000000..1ecd16c6b --- /dev/null +++ b/Development/third_party/is-12/v1.0.x/APIs/schemas/base-message.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Base protocol message structure", + "title": "Base protocol message", + "required": [ + "messageType" + ], + "properties": { + "messageType": { + "description": "Protocol message type", + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5 + ] + } + } +} \ No newline at end of file diff --git a/Development/third_party/is-12/v1.0.x/APIs/schemas/command-message.json b/Development/third_party/is-12/v1.0.x/APIs/schemas/command-message.json new file mode 100644 index 000000000..093b69eda --- /dev/null +++ b/Development/third_party/is-12/v1.0.x/APIs/schemas/command-message.json @@ -0,0 +1,76 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Command protocol message structure", + "title": "Command protocol message", + "allOf": [ + { + "$ref": "base-message.json" + }, + { + "type": "object", + "required": [ + "commands", + "messageType" + ], + "properties": { + "commands": { + "description": "Commands being transmited in this transaction", + "type": "array", + "items": { + "type": "object", + "required": [ + "handle", + "oid", + "methodId" + ], + "properties": { + "handle": { + "type": "integer", + "description": "Integer value used for pairing with the response", + "minimum": 1, + "maximum": 65535 + }, + "oid": { + "type": "integer", + "description": "Object id containing the method", + "minimum": 1 + }, + "methodId": { + "type": "object", + "description": "ID structure for the target method", + "required": [ + "level", + "index" + ], + "properties": { + "level": { + "type": "integer", + "description": "Level component of the method ID", + "minimum": 1 + }, + "index": { + "type": "integer", + "description": "Index component of the method ID", + "minimum": 1 + } + } + }, + "arguments": { + "type": "object", + "description": "Method arguments" + } + } + } + }, + "messageType": { + "description": "Protocol message type", + "type": "integer", + "enum": [ + 0 + ] + } + } + } + ] +} \ No newline at end of file diff --git a/Development/third_party/is-12/v1.0.x/APIs/schemas/command-response-message.json b/Development/third_party/is-12/v1.0.x/APIs/schemas/command-response-message.json new file mode 100644 index 000000000..93711f583 --- /dev/null +++ b/Development/third_party/is-12/v1.0.x/APIs/schemas/command-response-message.json @@ -0,0 +1,69 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Command response protocol message structure", + "title": "Command response protocol message", + "allOf": [ + { + "$ref": "base-message.json" + }, + { + "type": "object", + "required": [ + "responses", + "messageType" + ], + "properties": { + "responses": { + "description": "Responses being transmited in this transaction", + "type": "array", + "items": { + "type": "object", + "required": [ + "handle", + "result" + ], + "properties": { + "handle": { + "type": "integer", + "description": "Integer value used for pairing with the command", + "minimum": 1, + "maximum": 65535 + }, + "result": { + "type": "object", + "description": "Response result", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "integer", + "description": "Status of the command response. Must include the numeric values for NcMethodStatus or other types which inherit from it. 200 must be returned if the command was successful", + "minimum": 0, + "maximum": 65535 + }, + "value": { + "type": ["string", "number", "object", "array", "boolean", "null" ], + "description": "Method return value as described in the MS-05-02 Type definition or in a private Type definition" + }, + "errorMessage": { + "description": "Error message associated with the failure of the command (optional)", + "type": "string" + } + } + } + } + } + }, + "messageType": { + "description": "Protocol message type", + "type": "integer", + "enum": [ + 1 + ] + } + } + } + ] +} \ No newline at end of file diff --git a/Development/third_party/is-12/v1.0.x/APIs/schemas/error-message.json b/Development/third_party/is-12/v1.0.x/APIs/schemas/error-message.json new file mode 100644 index 000000000..139c77ffc --- /dev/null +++ b/Development/third_party/is-12/v1.0.x/APIs/schemas/error-message.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Error protocol message structure - used by devices to return general error messages for example when incoming messages do not have messageType, handles or contain invalid JSON", + "title": "Error protocol message", + "allOf": [ + { + "$ref": "base-message.json" + }, + { + "type": "object", + "required": [ + "status", + "errorMessage", + "messageType" + ], + "properties": { + "status": { + "type": "integer", + "description": "Status of the message response. Must include the numeric values for NcMethodStatus or other types which inherit from it.", + "minimum": 0, + "maximum": 65535 + }, + "errorMessage": { + "description": "Error details associated with the failure", + "type": "string" + }, + "messageType": { + "description": "Protocol message type", + "type": "integer", + "enum": [ + 5 + ] + } + } + } + ] +} diff --git a/Development/third_party/is-12/v1.0.x/APIs/schemas/event-data.json b/Development/third_party/is-12/v1.0.x/APIs/schemas/event-data.json new file mode 100644 index 000000000..9b644871c --- /dev/null +++ b/Development/third_party/is-12/v1.0.x/APIs/schemas/event-data.json @@ -0,0 +1,11 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Event data structure", + "title": "Event data", + "oneOf": [ + { + "$ref": "property-changed-event-data.json" + } + ] +} diff --git a/Development/third_party/is-12/v1.0.x/APIs/schemas/notification-message.json b/Development/third_party/is-12/v1.0.x/APIs/schemas/notification-message.json new file mode 100644 index 000000000..860770eb4 --- /dev/null +++ b/Development/third_party/is-12/v1.0.x/APIs/schemas/notification-message.json @@ -0,0 +1,69 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Notification protocol message structure", + "title": "Notification protocol message", + "allOf": [ + { + "$ref": "base-message.json" + }, + { + "type": "object", + "required": [ + "notifications", + "messageType" + ], + "properties": { + "notifications": { + "description": "Notifications being transmited in this transaction", + "type": "array", + "items": { + "type": "object", + "required": [ + "oid", + "eventId", + "eventData" + ], + "properties": { + "oid": { + "type": "integer", + "description": "Emitter object id", + "minimum": 1 + }, + "eventId": { + "type": "object", + "description": "Event ID structure", + "required": [ + "level", + "index" + ], + "properties": { + "level": { + "type": "integer", + "description": "Level component of the event ID", + "minimum": 1 + }, + "index": { + "type": "integer", + "description": "Index component of the event ID", + "minimum": 1 + } + } + }, + "eventData": { + "$ref": "event-data.json" + } + } + } + }, + "messageType": { + "description": "Protocol message type", + "type": "integer", + "enum": [ + 2 + ] + } + } + } + ] +} \ No newline at end of file diff --git a/Development/third_party/is-12/v1.0.x/APIs/schemas/property-changed-event-data.json b/Development/third_party/is-12/v1.0.x/APIs/schemas/property-changed-event-data.json new file mode 100644 index 000000000..7d6be6f1a --- /dev/null +++ b/Development/third_party/is-12/v1.0.x/APIs/schemas/property-changed-event-data.json @@ -0,0 +1,58 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Property changed event data structure", + "title": "Property changed event data", + "properties": { + "propertyId": { + "type": "object", + "description": "Property ID structure", + "required": [ + "level", + "index" + ], + "properties": { + "level": { + "type": "integer", + "description": "Level component of the property ID", + "minimum": 1 + }, + "index": { + "type": "integer", + "description": "Index component of the property ID", + "minimum": 1 + } + } + }, + "changeType": { + "type": "integer", + "description": "Event change type numeric value. Must include the numeric values for NcPropertyChangeType", + "minimum": 0, + "maximum": 65535 + }, + "value": { + "type": [ + "string", + "number", + "object", + "array", + "boolean", + "null" + ], + "description": "Property value as described in the MS-05-02 Class definition or in a private Class definition" + }, + "sequenceItemIndex": { + "type": [ + "number", + "null" + ], + "description": "Index of sequence item if the property is a sequence" + } + }, + "required": [ + "propertyId", + "changeType", + "value", + "sequenceItemIndex" + ] +} diff --git a/Development/third_party/is-12/v1.0.x/APIs/schemas/subscription-message.json b/Development/third_party/is-12/v1.0.x/APIs/schemas/subscription-message.json new file mode 100644 index 000000000..290ccd903 --- /dev/null +++ b/Development/third_party/is-12/v1.0.x/APIs/schemas/subscription-message.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Subscription protocol message structure", + "title": "Subscription protocol message", + "allOf": [ + { + "$ref": "base-message.json" + }, + { + "type": "object", + "required": [ + "subscriptions", + "messageType" + ], + "properties": { + "subscriptions": { + "description": "Array of OIDs desired for subscription", + "type": "array", + "items": { + "type": "integer" + } + }, + "messageType": { + "description": "Protocol message type", + "type": "integer", + "enum": [ + 3 + ] + } + } + } + ] +} diff --git a/Development/third_party/is-12/v1.0.x/APIs/schemas/subscription-response-message.json b/Development/third_party/is-12/v1.0.x/APIs/schemas/subscription-response-message.json new file mode 100644 index 000000000..587cdd62a --- /dev/null +++ b/Development/third_party/is-12/v1.0.x/APIs/schemas/subscription-response-message.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "Subscription response protocol message structure", + "title": "Subscription response protocol message", + "allOf": [ + { + "$ref": "base-message.json" + }, + { + "type": "object", + "required": [ + "subscriptions", + "messageType" + ], + "properties": { + "subscriptions": { + "description": "Array of OIDs which have successfully been added to the subscription list.", + "type": "array", + "items": { + "type": "integer" + } + }, + "messageType": { + "description": "Protocol message type", + "type": "integer", + "enum": [ + 4 + ] + } + } + } + ] +} diff --git a/README.md b/README.md index 4819d8b7b..cbfe9898d 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,15 @@ This repository contains an implementation of the [AMWA Networked Media Open Spe - [AMWA IS-08 NMOS Audio Channel Mapping Specification](https://specs.amwa.tv/is-08/) - [AMWA IS-09 NMOS System Parameters Specification](https://specs.amwa.tv/is-09/) (originally defined in JT-NM TR-1001-1:2018 Annex A) - [AMWA IS-10 NMOS Authorization Specification](https://specs.amwa.tv/is-10/) +- [AMWA IS-12 AMWA IS-12 NMOS Control Protocol](https://specs.amwa.tv/is-12/) - [AMWA BCP-002-01 NMOS Grouping Recommendations - Natural Grouping](https://specs.amwa.tv/bcp-002-01/) - [AMWA BCP-002-02 NMOS Asset Distinguishing Information](https://specs.amwa.tv/bcp-002-02/) - [AMWA BCP-003-01 Secure Communication in NMOS Systems](https://specs.amwa.tv/bcp-003-01/) - [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 MS-05-01 NMOS Control Architecture](https://specs.amwa.tv/ms-05-01/) +- [AMWA MS-05-02 NMOS Control Framework](https://specs.amwa.tv/ms-05-02/) For more information about AMWA, NMOS and the Networked Media Incubator, please refer to . @@ -116,6 +119,7 @@ The implementation is designed to be extended. Development is ongoing, following Recent activity on the project (newest first): +- Added support for the IS-12 NMOS Control Protocol - Update to Conan 2; Conan 1.X is no longer supported - Added support for IS-10 Authorization - Added support for HSTS and OCSP stapling