From 378dd19cafdf5b7b0b0c2a75774db32db743f835 Mon Sep 17 00:00:00 2001 From: Gareth Sylvester-Bradley <31761158+garethsb@users.noreply.github.com> Date: Sun, 31 May 2026 21:44:46 +0100 Subject: [PATCH] Add attached_network_device support to node_interface. Extend node_interface with attached fields, add make_node_interface and parse_node_interface round-trip (including attached_network_device), and unit tests. Co-authored-by: Simon Lo Signed-off-by: Gareth Sylvester-Bradley --- Development/cmake/NmosCppTest.cmake | 1 + Development/nmos/node_interfaces.cpp | 50 +++++++++++--- Development/nmos/node_interfaces.h | 18 ++++- .../nmos/test/node_interfaces_test.cpp | 67 +++++++++++++++++++ 4 files changed, 127 insertions(+), 9 deletions(-) create mode 100644 Development/nmos/test/node_interfaces_test.cpp diff --git a/Development/cmake/NmosCppTest.cmake b/Development/cmake/NmosCppTest.cmake index 2acecf83e..7a0af0a61 100644 --- a/Development/cmake/NmosCppTest.cmake +++ b/Development/cmake/NmosCppTest.cmake @@ -56,6 +56,7 @@ set(NMOS_CPP_TEST_NMOS_TEST_SOURCES nmos/test/jwt_generator_test.cpp nmos/test/jwt_validation_test.cpp nmos/test/mdns_test.cpp + nmos/test/node_interfaces_test.cpp nmos/test/paging_utils_test.cpp nmos/test/query_api_test.cpp nmos/test/sdp_test_utils.cpp diff --git a/Development/nmos/node_interfaces.cpp b/Development/nmos/node_interfaces.cpp index 619850548..2af5e40d4 100644 --- a/Development/nmos/node_interfaces.cpp +++ b/Development/nmos/node_interfaces.cpp @@ -15,6 +15,11 @@ namespace nmos return !chassis_id.empty() ? value::string(chassis_id) : value::null(); } + utility::string_t parse_node_interfaces_chassis_id(const web::json::value& chassis_id) + { + return chassis_id.is_null() ? utility::string_t{} : chassis_id.as_string(); + } + // Port ID must be a MAC address web::json::value make_node_interfaces_port_id(const utility::string_t& port_id) { @@ -25,38 +30,67 @@ namespace nmos } } + // make node interface JSON data + web::json::value make_node_interface(const node_interface& interface) + { + using web::json::value_of; + + const bool keep_order = true; + + const auto has_attached = !interface.attached_chassis_id.empty() && !interface.attached_port_id.empty(); + + return value_of({ + { nmos::fields::chassis_id, details::make_node_interfaces_chassis_id(interface.chassis_id) }, + { nmos::fields::port_id, details::make_node_interfaces_port_id(interface.port_id) }, + { nmos::fields::name, interface.name }, + { has_attached ? nmos::fields::attached_network_device.key : U(""), value_of({ + { nmos::fields::chassis_id, interface.attached_chassis_id }, + { nmos::fields::port_id, interface.attached_port_id } + }, keep_order) } + }, keep_order); + } + + // parse node interface JSON data + node_interface parse_node_interface(const web::json::value& interface) + { + const auto& attached = nmos::fields::attached_network_device(interface); + return { + details::parse_node_interfaces_chassis_id(interface.at(nmos::fields::chassis_id)), + nmos::fields::port_id(interface), + nmos::fields::name(interface), + attached.is_object() ? nmos::fields::chassis_id(attached) : utility::string_t{}, + attached.is_object() ? nmos::fields::port_id(attached) : utility::string_t{} + }; + } + // make node interfaces JSON data, for the specified map from local interface_id - // no attached_network_device details are included web::json::value make_node_interfaces(const std::map& interfaces) { using web::json::value_from_elements; - using web::json::value_of; return value_from_elements(interfaces | boost::adaptors::transformed([](const std::map::value_type& interface) { - return value_of({ - { nmos::fields::chassis_id, details::make_node_interfaces_chassis_id(interface.second.chassis_id) }, - { nmos::fields::port_id, details::make_node_interfaces_port_id(interface.second.port_id) }, - { nmos::fields::name, interface.second.name } - }); + return make_node_interface(interface.second); })); } namespace experimental { // make a map from local interface_id to the (recommended) node interface details for the specified host interfaces + // no attached_network_device details are included std::map node_interfaces(const std::vector& host_interfaces) { return boost::copy_range>(host_interfaces | boost::adaptors::transformed([&](const web::hosts::experimental::host_interface& interface) { return std::map::value_type{ interface.name, - { host_interfaces.front().physical_address, interface.physical_address, interface.name } + { host_interfaces.front().physical_address, interface.physical_address, interface.name, {}, {} } }; })); } // make a map from local interface_id to the (recommended) node interface details + // no attached_network_device details are included std::map node_interfaces() { return node_interfaces(web::hosts::experimental::host_interfaces()); diff --git a/Development/nmos/node_interfaces.h b/Development/nmos/node_interfaces.h index 379900a6b..e9f6e3262 100644 --- a/Development/nmos/node_interfaces.h +++ b/Development/nmos/node_interfaces.h @@ -2,6 +2,7 @@ #define NMOS_NODE_INTERFACES_H #include +#include #include #include "cpprest/details/basic_types.h" @@ -33,18 +34,33 @@ namespace nmos utility::string_t port_id; // Interface name (recommended to be the local interface_id) utility::string_t name; + // Attached network device chassis ID; empty string indicates attached_network_device is omitted + utility::string_t attached_chassis_id; + // Attached network device port ID; empty string indicates attached_network_device is omitted + utility::string_t attached_port_id; + + auto tied() const -> decltype(std::tie(chassis_id, port_id, name, attached_chassis_id, attached_port_id)) { return std::tie(chassis_id, port_id, name, attached_chassis_id, attached_port_id); } + friend bool operator==(const node_interface& lhs, const node_interface& rhs) { return lhs.tied() == rhs.tied(); } + friend bool operator!=(const node_interface& lhs, const node_interface& rhs) { return !(lhs == rhs); } }; + // make node interface JSON data + web::json::value make_node_interface(const node_interface& interface); + + // parse node interface JSON data + node_interface parse_node_interface(const web::json::value& interface); + // make node interfaces JSON data, for the specified map from local interface_id - // no attached_network_device details are included web::json::value make_node_interfaces(const std::map& interfaces); namespace experimental { // make a map from local interface_id to the (recommended) node interface details for the specified host interfaces + // no attached_network_device details are included std::map node_interfaces(const std::vector& host_interfaces); // make a map from local interface_id to the (recommended) node interface details + // no attached_network_device details are included std::map node_interfaces(); } } diff --git a/Development/nmos/test/node_interfaces_test.cpp b/Development/nmos/test/node_interfaces_test.cpp new file mode 100644 index 000000000..29bbae626 --- /dev/null +++ b/Development/nmos/test/node_interfaces_test.cpp @@ -0,0 +1,67 @@ +// The first "test" is of course whether the header compiles standalone +#include "nmos/node_interfaces.h" + +#include "bst/test/test.h" +#include "nmos/json_fields.h" + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testMakeParseNodeInterface) +{ + using web::json::value_of; + + const nmos::node_interface iface{ + U("aa-bb-cc-dd-ee-01"), + U("aa-bb-cc-dd-ee-ff"), + U("eth0"), + U(""), + U("") + }; + + const auto json = nmos::make_node_interface(iface); + BST_REQUIRE(!json.at(nmos::fields::chassis_id).is_null()); + BST_REQUIRE_EQUAL(U("aa-bb-cc-dd-ee-01"), nmos::fields::chassis_id(json)); + BST_REQUIRE_EQUAL(U("aa-bb-cc-dd-ee-ff"), nmos::fields::port_id(json)); + BST_REQUIRE_EQUAL(U("eth0"), nmos::fields::name(json)); + BST_REQUIRE(!json.has_field(nmos::fields::attached_network_device)); + + BST_REQUIRE(iface == nmos::parse_node_interface(json)); +} + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testMakeParseNodeInterfaceNullChassisId) +{ + const nmos::node_interface iface{ + U(""), + U("aa-bb-cc-dd-ee-ff"), + U("eth0"), + U(""), + U("") + }; + + const auto json = nmos::make_node_interface(iface); + BST_REQUIRE(json.at(nmos::fields::chassis_id).is_null()); + BST_REQUIRE(!json.has_field(nmos::fields::attached_network_device)); + + BST_REQUIRE(iface == nmos::parse_node_interface(json)); +} + +//////////////////////////////////////////////////////////////////////////////////////////// +BST_TEST_CASE(testMakeParseNodeInterfaceAttachedNetworkDevice) +{ + const nmos::node_interface iface{ + U(""), + U("aa-bb-cc-dd-ee-ff"), + U("eth0"), + U("11-22-33-44-55-66"), + U("77-88-99-aa-bb-cc") + }; + + const auto json = nmos::make_node_interface(iface); + BST_REQUIRE(json.has_field(nmos::fields::attached_network_device)); + + const auto& attached = nmos::fields::attached_network_device(json); + BST_REQUIRE_EQUAL(U("11-22-33-44-55-66"), nmos::fields::chassis_id(attached)); + BST_REQUIRE_EQUAL(U("77-88-99-aa-bb-cc"), nmos::fields::port_id(attached)); + + BST_REQUIRE(iface == nmos::parse_node_interface(json)); +}