diff --git a/Development/nmos/node_interfaces.cpp b/Development/nmos/node_interfaces.cpp index 2af5e40d..10ec7e4a 100644 --- a/Development/nmos/node_interfaces.cpp +++ b/Development/nmos/node_interfaces.cpp @@ -1,6 +1,8 @@ #include "nmos/node_interfaces.h" #include +#include "bst/regex.h" +#include "cpprest/basic_utils.h" #include "cpprest/host_utils.h" #include "nmos/json_fields.h" @@ -20,13 +22,24 @@ namespace nmos return chassis_id.is_null() ? utility::string_t{} : chassis_id.as_string(); } + // Port ID must be a MAC address, strictly following the lowercase hexadecimal format and separated by hyphens (not colons) + // It should match the regular expression pattern ^([0-9a-f]{2}-){5}([0-9a-f]{2})$ + // see https://specs.amwa.tv/is-04/branches/v1.2.x/APIs/schemas/with-refs/node.html + bool is_valid_node_interfaces_port_id(const utility::string_t& port_id) + { + static const bst::regex port_id_regex(R"(([0-9a-f]{2}-){5}[0-9a-f]{2}$)"); + return bst::regex_match(utility::us2s(port_id), port_id_regex); + } + // Port ID must be a MAC address web::json::value make_node_interfaces_port_id(const utility::string_t& port_id) { using web::json::value; - // when no physical address is available, use the common null value of all zeros + // IS-04 port_id requires the six-octet lowercase-hyphen form. Any other representation, + // including uppercase or colon-separated MAC addresses, is not representable in this field + // and uses the existing null-address fallback of all zeros // see https://standards.ieee.org/content/dam/ieee-standards/standards/web/documents/tutorials/eui.pdf - return value::string(!port_id.empty() ? port_id : U("00-00-00-00-00-00")); + return value::string(is_valid_node_interfaces_port_id(port_id) ? port_id : U("00-00-00-00-00-00")); } } diff --git a/Development/nmos/test/node_interfaces_test.cpp b/Development/nmos/test/node_interfaces_test.cpp index 29bbae62..f97e6b06 100644 --- a/Development/nmos/test/node_interfaces_test.cpp +++ b/Development/nmos/test/node_interfaces_test.cpp @@ -4,6 +4,22 @@ #include "bst/test/test.h" #include "nmos/json_fields.h" +namespace +{ + utility::string_t make_node_interface_port_id(const utility::string_t& port_id) + { + const nmos::node_interface iface{ + U("aa-bb-cc-dd-ee-01"), + port_id, + U("tunl0"), + U(""), + U("") + }; + auto json = nmos::make_node_interface(iface); + return nmos::fields::port_id(json); + } +} + //////////////////////////////////////////////////////////////////////////////////////////// BST_TEST_CASE(testMakeParseNodeInterface) { @@ -45,6 +61,72 @@ BST_TEST_CASE(testMakeParseNodeInterfaceNullChassisId) BST_REQUIRE(iface == nmos::parse_node_interface(json)); } +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testMakeNodeInterfaceValidPortIdUnchanged) +{ + BST_REQUIRE_EQUAL(U("aa-bb-cc-dd-ee-ff"), make_node_interface_port_id(U("aa-bb-cc-dd-ee-ff"))); +} + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testMakeNodeInterfaceEmptyPortIdFallback) +{ + BST_REQUIRE_EQUAL(U("00-00-00-00-00-00"), make_node_interface_port_id(U(""))); +} + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testMakeNodeInterfaceInvalidPortIdFallback) +{ + const std::vector invalid_port_ids{ + // Uppercase MAC addresses + U("AA-BB-CC-DD-EE-FF"), + U("AA-bb-cc-dd-ee-ff"), + // Colon-separated MAC addresses + U("00:00:00:00:00:00"), + U("aa:bb:cc:dd:ee:ff"), + U("12:34:56:78:9a:bc"), + // Various malformed MAC addresses + // Wrong length + U("00-00-00-00"), + U("00-00-00-00-00"), + U("00-00-00-00-00-00-00"), + U("aa-bb-cc-dd-ee"), + U("aa-bb-cc-dd-ee-ff-gg"), + // Invalid hex characters + U("gg-00-00-00-00-00"), + U("aa-bb-cc-dd-ee-GG"), + // Missing separators + U("aabbccddeeff"), + U("00000000000000"), + // Wrong separator positions + U("aab-bcc-dde-eff"), + U("aa-bbccdd-ee-ff"), + // Mixed separators + U("aa:bb-cc-dd-ee-ff"), + U("aa-bb:cc:dd-ee-ff"), + // Extra characters + U(" aa-bb-cc-dd-ee-ff"), + U("aa-bb-cc-dd-ee-ff "), + U("aa-bb-cc-dd-ee-ff-"), + U("-aa-bb-cc-dd-ee-ff"), + // Special characters + U("aa.bb.cc.dd.ee.ff"), + U("aa_bb_cc_dd_ee_ff"), + // Empty octets + U("--bb-cc-dd-ee-ff"), + U("aa--cc-dd-ee-ff"), + // Single character octets + U("a-b-c-d-e-f"), + U("0-0-0-0-0-0"), + // Three character octets + U("aaa-bbb-ccc-ddd-eee-fff") + }; + + for (const auto& invalid_port_id : invalid_port_ids) + { + BST_CHECK_EQUAL(U("00-00-00-00-00-00"), make_node_interface_port_id(invalid_port_id)); + } +} + //////////////////////////////////////////////////////////////////////////////////////////// BST_TEST_CASE(testMakeParseNodeInterfaceAttachedNetworkDevice) {