diff --git a/Development/nmos/control_protocol_methods.cpp b/Development/nmos/control_protocol_methods.cpp index 337cd43a..8393d3ba 100644 --- a/Development/nmos/control_protocol_methods.cpp +++ b/Development/nmos/control_protocol_methods.cpp @@ -24,7 +24,7 @@ namespace nmos // find the relevant nc_property_descriptor const auto& property = nc::find_property_descriptor(details::parse_property_id(property_id), details::parse_class_id(nmos::fields::nc::class_id(resource.data)), get_control_protocol_class_descriptor); - if (!property.is_null()) + if (!property.is_null() && resource.data.has_field(nmos::fields::nc::name(property))) { return details::make_method_result({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))); } diff --git a/Development/nmos/control_protocol_utils.cpp b/Development/nmos/control_protocol_utils.cpp index 3cd640a0..e9fae7f8 100644 --- a/Development/nmos/control_protocol_utils.cpp +++ b/Development/nmos/control_protocol_utils.cpp @@ -985,6 +985,28 @@ namespace nmos } } + // Validate that the resource has been correctly constructed according to the class_descriptor + void validate_resource(const resource& resource, const experimental::control_class_descriptor& class_descriptor) + { + // Validate properties + for (const auto& property_descriptor : class_descriptor.property_descriptors.as_array()) + { + if ((resource.data.is_null()) || (!resource.data.has_field(nmos::fields::nc::name(property_descriptor)))) + { + throw control_protocol_exception("missing control resource property: " + utility::us2s(nmos::fields::nc::name(property_descriptor))); + } + } + + // Validate methods + for (const auto& method_descriptor : class_descriptor.method_descriptors) + { + if (!method_descriptor.second) + { + throw control_protocol_exception("method not implemented: " + utility::us2s(nmos::fields::nc::name(method_descriptor.first))); + } + } + } + resources::const_iterator find_resource_by_role_path(const resources& resources, const web::json::array& role_path_) { auto role_path = role_path_; @@ -1299,7 +1321,7 @@ namespace nmos { // A monitor is expected to go through a period of instability upon activation. Therefore, on monitor activation // domain specific statuses offering an Inactive option MUST transition immediately to the Healthy state. - // Furthermore, after activation, as long as the monitor isn’t being deactivated, it MUST delay the reporting + // Furthermore, after activation, as long as the monitor isn't being deactivated, it MUST delay the reporting // of non Healthy states for the duration specified by statusReportingDelay, and then transition to any other appropriate state. const auto& found = find_resource(resources, utility::s2us(std::to_string(oid))); if (resources.end() != found && nc::is_status_monitor(nc::details::parse_class_id(nmos::fields::nc::class_id(found->data)))) diff --git a/Development/nmos/control_protocol_utils.h b/Development/nmos/control_protocol_utils.h index ea9c3de6..517bf2a9 100644 --- a/Development/nmos/control_protocol_utils.h +++ b/Development/nmos/control_protocol_utils.h @@ -126,6 +126,9 @@ namespace nmos // 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); + // Validate that the resource has been correctly constructed according to the class_descriptor + void validate_resource(const resource& resource, const experimental::control_class_descriptor& class_descriptor); + resources::const_iterator find_touchpoint_resource(const resources& resources, const resource& resource); // 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 diff --git a/Development/nmos/test/control_protocol_utils_test.cpp b/Development/nmos/test/control_protocol_utils_test.cpp index ce547477..1b777036 100644 --- a/Development/nmos/test/control_protocol_utils_test.cpp +++ b/Development/nmos/test/control_protocol_utils_test.cpp @@ -1254,3 +1254,102 @@ BST_TEST_CASE(testFindMembersByClassId) BST_REQUIRE_EQUAL(example_class_id_2, nmos::nc::details::parse_class_id(nmos::fields::nc::class_id(members.at(0)))); } } + +BST_TEST_CASE(testValidateResourceProperties) +{ + using web::json::value_of; + using web::json::value; + + 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); + + auto root_block = nmos::make_root_block(); + auto oid = nmos::root_block_oid; + // root, ClassManager + auto class_manager = nmos::make_class_manager(++oid, control_protocol_state); + auto receiver_block_oid = ++oid; + // root, receivers + auto receivers = nmos::make_block(receiver_block_oid, nmos::root_block_oid, U("receivers"), U("Receivers"), U("Receivers block")); + + // root, receivers, mon1 + auto monitor1 = nmos::make_receiver_monitor(++oid, true, receiver_block_oid, U("mon1"), U("monitor 1"), U("monitor 1"), value_of({ {nmos::nc::details::make_touchpoint_nmos({nmos::ncp_touchpoint_resource_types::receiver, U("id_1")})} })); + + auto block_class_descriptor = get_control_protocol_class_descriptor(nmos::nc_block_class_id); + + BST_CHECK_NO_THROW(nmos::nc::validate_resource(root_block, block_class_descriptor)); + BST_CHECK_NO_THROW(nmos::nc::validate_resource(receivers, block_class_descriptor)); + + auto class_manager_class_descriptor = get_control_protocol_class_descriptor(nmos::nc_class_manager_class_id); + + BST_CHECK_NO_THROW(nmos::nc::validate_resource(class_manager, class_manager_class_descriptor)); + + auto receiver_monitor_class_descriptor = get_control_protocol_class_descriptor(nmos::nc_receiver_monitor_class_id); + + BST_CHECK_NO_THROW(nmos::nc::validate_resource(monitor1, receiver_monitor_class_descriptor)); + + // Negative tests + BST_CHECK_THROW(nmos::nc::validate_resource(root_block, receiver_monitor_class_descriptor), nmos::control_protocol_exception); + BST_CHECK_THROW(nmos::nc::validate_resource(receivers, class_manager_class_descriptor), nmos::control_protocol_exception); + BST_CHECK_THROW(nmos::nc::validate_resource(class_manager, block_class_descriptor), nmos::control_protocol_exception); + BST_CHECK_THROW(nmos::nc::validate_resource(monitor1, block_class_descriptor), nmos::control_protocol_exception); +} + +BST_TEST_CASE(testValidateResourceMethods) +{ + using web::json::value_of; + using web::json::value; + + 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); + + auto root_block = nmos::make_root_block(); + + auto example_method_with_no_args = [](nmos::resources& resources, const nmos::resource& resource, const web::json::value& arguments, bool is_deprecated, slog::base_gate& gate) + { + return nmos::nc::details::make_method_result({ is_deprecated ? nmos::nc_method_status::method_deprecated : nmos::nc_method_status::ok }); + }; + + auto control_class_id = nmos::nc::make_class_id(nmos::nc_worker_class_id, 0, { 2 }); + // Define class descriptor with method and associated method - should succeed on validate + { + std::vector property_descriptors = {}; + + std::vector 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) } + }; + + auto control_class_descriptor = nmos::experimental::make_control_class_descriptor(U(""), nmos::nc::make_class_id(nmos::nc_worker_class_id, 0, { 2 }), U("ControlClass"), property_descriptors, method_descriptors, {}); + + auto make_control_object = [&](nmos::nc_oid oid) + { + auto data = nmos::nc::details::make_worker(control_class_id, oid, true, 1, U("role"), value::string(U("")), U(""), value::null(), value::null(), true); + return nmos::control_protocol_resource{ nmos::is12_versions::v1_0, nmos::types::nc_worker, std::move(data), true }; + }; + + auto control_object = make_control_object(3); + + BST_CHECK_NO_THROW(nmos::nc::validate_resource(control_object, control_class_descriptor)); + } + // Define class descriptor with method with no associated method - should throw on validate + { + std::vector property_descriptors = {}; + + std::vector method_descriptors = + { + { nmos::experimental::make_control_class_method_descriptor(U("Example method with no arguments"), { 3, 1 }, U("MethodNoArgs"), U("NcMethodResult"), {}, false, nullptr) } + }; + + auto control_class_descriptor = nmos::experimental::make_control_class_descriptor(U(""), nmos::nc::make_class_id(nmos::nc_worker_class_id, 0, { 2 }), U("ControlClass"), property_descriptors, method_descriptors, {}); + + auto make_control_object = [&](nmos::nc_oid oid) + { + auto data = nmos::nc::details::make_worker(control_class_id, oid, true, 1, U("role"), value::string(U("")), U(""), value::null(), value::null(), true); + return nmos::control_protocol_resource{ nmos::is12_versions::v1_0, nmos::types::nc_worker, std::move(data), true }; + }; + + auto control_object = make_control_object(3); + + BST_CHECK_THROW(nmos::nc::validate_resource(control_object, control_class_descriptor), nmos::control_protocol_exception); + } +}