From be721e36877acc547f26f2ca2def80ec57bff34a Mon Sep 17 00:00:00 2001 From: Shin <128954611+shin4141@users.noreply.github.com> Date: Wed, 19 Aug 2026 13:56:23 +0900 Subject: [PATCH 1/2] Fix invalid node interface port IDs --- Development/nmos/node_interfaces.cpp | 26 ++++++++++- .../nmos/test/node_interfaces_test.cpp | 46 +++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/Development/nmos/node_interfaces.cpp b/Development/nmos/node_interfaces.cpp index 2af5e40d4..55a56dda6 100644 --- a/Development/nmos/node_interfaces.cpp +++ b/Development/nmos/node_interfaces.cpp @@ -20,13 +20,35 @@ namespace nmos return chassis_id.is_null() ? utility::string_t{} : chassis_id.as_string(); } + bool is_valid_node_interfaces_port_id(const utility::string_t& port_id) + { + if (17 != port_id.size()) return false; + + for (size_t index = 0; index < port_id.size(); ++index) + { + const auto character = port_id[index]; + if (2 == index % 3) + { + if (U('-') != character) return false; + } + else if (!((U('0') <= character && character <= U('9')) || (U('a') <= character && character <= U('f')))) + { + return false; + } + } + + return true; + } + // 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 29bbae626..4f5402984 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,36 @@ 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{ + U("00-00-00-00"), + U("00-00-00-00-00"), + U("00-00-00-00-00-00-00"), + U("gg-00-00-00-00-00"), + U("AA-BB-CC-DD-EE-FF"), + U("00:00:00:00:00:00") + }; + + 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) { From 073a7a07299d46690c69d5f0d407caf7f31a721c Mon Sep 17 00:00:00 2001 From: Shin <128954611+shin4141@users.noreply.github.com> Date: Fri, 21 Aug 2026 20:20:56 +0900 Subject: [PATCH 2/2] Use regex for node interface port ID validation --- Development/nmos/node_interfaces.cpp | 23 ++++------- .../nmos/test/node_interfaces_test.cpp | 40 ++++++++++++++++++- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/Development/nmos/node_interfaces.cpp b/Development/nmos/node_interfaces.cpp index 55a56dda6..10ec7e4a2 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,24 +22,13 @@ 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) { - if (17 != port_id.size()) return false; - - for (size_t index = 0; index < port_id.size(); ++index) - { - const auto character = port_id[index]; - if (2 == index % 3) - { - if (U('-') != character) return false; - } - else if (!((U('0') <= character && character <= U('9')) || (U('a') <= character && character <= U('f')))) - { - return false; - } - } - - return true; + 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 diff --git a/Development/nmos/test/node_interfaces_test.cpp b/Development/nmos/test/node_interfaces_test.cpp index 4f5402984..f97e6b06c 100644 --- a/Development/nmos/test/node_interfaces_test.cpp +++ b/Development/nmos/test/node_interfaces_test.cpp @@ -77,12 +77,48 @@ BST_TEST_CASE(testMakeNodeInterfaceEmptyPortIdFallback) 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-FF"), - U("00: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)