diff --git a/docs/examples/1_presentation/example_1_presentation_1_ping_user_service_udp.cpp b/docs/examples/1_presentation/example_1_presentation_1_ping_user_service_udp.cpp index fb2c46409..c455cde1e 100644 --- a/docs/examples/1_presentation/example_1_presentation_1_ping_user_service_udp.cpp +++ b/docs/examples/1_presentation/example_1_presentation_1_ping_user_service_udp.cpp @@ -26,7 +26,6 @@ #include #include -#include // NOLINT for NUNAVUT_ASSERT #include #include diff --git a/docs/examples/1_presentation/example_1_presentation_2_heartbeat_getinfo_udp.cpp b/docs/examples/1_presentation/example_1_presentation_2_heartbeat_getinfo_udp.cpp index 06c520df8..ca2134b76 100644 --- a/docs/examples/1_presentation/example_1_presentation_2_heartbeat_getinfo_udp.cpp +++ b/docs/examples/1_presentation/example_1_presentation_2_heartbeat_getinfo_udp.cpp @@ -171,7 +171,7 @@ TEST_F(Example_1_Presentation_2_Heartbeat_GetInfo_Udp, main) ASSERT_THAT(heartbeat_subscriber, testing::Optional(testing::_)); heartbeat_subscriber->setOnReceiveCallback([&](const auto& arg) { // - state.heartbeat_.print(arg.approx_now - startup_time_, arg.message, arg.metadata); + NodeHelpers::Heartbeat::print(arg.approx_now - startup_time_, arg.message, arg.metadata); }); // Bring up 'GetInfo' server. diff --git a/docs/examples/1_presentation/example_1_presentation_3_hb_getinfo_ping_linux_can.cpp b/docs/examples/1_presentation/example_1_presentation_3_hb_getinfo_ping_linux_can.cpp index 79c76ed2f..09fbf4cc2 100644 --- a/docs/examples/1_presentation/example_1_presentation_3_hb_getinfo_ping_linux_can.cpp +++ b/docs/examples/1_presentation/example_1_presentation_3_hb_getinfo_ping_linux_can.cpp @@ -27,12 +27,9 @@ #include #include -#include // NOLINT for NUNAVUT_ASSERT #include #include -#include #include -#include #include #include diff --git a/docs/examples/2_application/example_2_application_0_node_hb_getinfo_udp.cpp b/docs/examples/2_application/example_2_application_0_node_hb_getinfo_udp.cpp index 7f8868b99..78821da66 100644 --- a/docs/examples/2_application/example_2_application_0_node_hb_getinfo_udp.cpp +++ b/docs/examples/2_application/example_2_application_0_node_hb_getinfo_udp.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -153,10 +154,13 @@ TEST_F(Example_2_Application_0_NodeHeartbeatGetInfo_Udp, main) ASSERT_THAT(maybe_node, testing::VariantWith(testing::_)) << "Can't create node."; auto node = cetl::get(std::move(maybe_node)); // - const std::string node_name{"org.opencyphal.Ex_2_App_0_Node_UDP"}; - std::copy_n(node_name.begin(), - std::min(node_name.size(), 50UL), - std::back_inserter(node.getInfoProvider().response().name)); + auto& get_info_prov = node.getInfoProvider(); + get_info_prov // + .setSoftwareVersion(0, 1) + .setHardwareVersion(0, 2) + .setName("org.opencyphal.Ex_2_App_0_Node_UDP") + .setCertificateOfAuthenticity("my_cert") + .setUniqueId(std::array{0x12, 0x34, 0x56}); // 4. Bring up registry provider, and expose several registers. Load persistent storage. // @@ -167,15 +171,14 @@ TEST_F(Example_2_Application_0_NodeHeartbeatGetInfo_Udp, main) const BitArray param_ro_val{BitArray::_traits_::TypeOf::value{{true, false}, mr_alloc_}, mr_alloc_}; auto param_ro = rgy.route("ro", [¶m_ro_val] { return param_ro_val; }); // - auto& get_info = node.getInfoProvider().response(); - auto param_name = rgy.route( // + auto param_name = rgy.route( // "uavcan.node.description", - [this, &get_info] { return makeStringValue(registry::makeStringView(get_info.name)); }, - [&get_info](const registry::IRegister::Value& value) -> cetl::optional { + [this, &get_info_prov] { return makeStringValue(registry::makeStringView(get_info_prov.response().name)); }, + [&get_info_prov](const registry::IRegister::Value& value) -> cetl::optional { // if (const auto* const str = value.get_string_if()) { - get_info.name = str->value; + get_info_prov.setName(registry::makeStringView(str->value)); return cetl::nullopt; } return registry::SetError::Semantics; diff --git a/docs/examples/platform/node_helpers.hpp b/docs/examples/platform/node_helpers.hpp index 04e32ec88..89eb5b946 100644 --- a/docs/examples/platform/node_helpers.hpp +++ b/docs/examples/platform/node_helpers.hpp @@ -19,7 +19,6 @@ #include #include -#include // NOLINT for NUNAVUT_ASSERT #include #include #include diff --git a/include/libcyphal/application/node/get_info_provider.hpp b/include/libcyphal/application/node/get_info_provider.hpp index 5c991db88..2e6af0701 100644 --- a/include/libcyphal/application/node/get_info_provider.hpp +++ b/include/libcyphal/application/node/get_info_provider.hpp @@ -11,10 +11,14 @@ #include "libcyphal/types.hpp" #include +#include #include +#include #include +#include +#include #include namespace libcyphal @@ -86,14 +90,120 @@ class GetInfoProvider final // NOSONAR cpp:S4963 /// @brief Sets the response transmission timeout (default is 1s). /// /// @param timeout Duration of the response transmission timeout. Applied for the next response transmission. + /// @return Reference to self for method chaining. /// - void setResponseTimeout(const Duration& timeout) noexcept + GetInfoProvider& setResponseTimeout(const Duration& timeout) noexcept { response_timeout_ = timeout; + return *this; + } + + /// @brief Sets the node unique 128-bit id in the GetInfo response. + /// + /// Default is all zeros. + /// Truncates the id if it exceeds 16 bytes capacity of the response field. + /// + /// @return Reference to self for method chaining. + /// + GetInfoProvider& setUniqueId(const cetl::span id) noexcept + { + response_.unique_id = {}; + (void) std::copy_n(id.data(), std::min(id.size(), response_.unique_id.size()), response_.unique_id.begin()); + return *this; + } + + /// @brief Sets the node protocol version in the GetInfo response. + /// + /// Default is '1.0'. + /// + /// @return Reference to self for method chaining. + /// + GetInfoProvider& setProtocolVersion(const std::uint8_t major, const std::uint8_t minor) noexcept // NOLINT + { + response_.protocol_version.major = major; + response_.protocol_version.minor = minor; + return *this; + } + + /// @brief Sets the node hardware version in the GetInfo response. + /// + /// Default is '0.0'. + /// + /// @return Reference to self for method chaining. + /// + GetInfoProvider& setHardwareVersion(const std::uint8_t major, const std::uint8_t minor) noexcept // NOLINT + { + response_.hardware_version.major = major; + response_.hardware_version.minor = minor; + return *this; + } + + /// @brief Sets the node software version in the GetInfo response. + /// + /// Default is '0.0'. + /// + /// @return Reference to self for method chaining. + /// + GetInfoProvider& setSoftwareVersion(const std::uint8_t major, const std::uint8_t minor) noexcept // NOLINT + { + response_.software_version.major = major; + response_.software_version.minor = minor; + return *this; + } + + /// @brief Sets the node software Version Control System (VCS) revision in the GetInfo response. + /// + /// Default is `0`. + /// + /// @return Reference to self for method chaining. + /// + GetInfoProvider& setSoftwareVcsRevisionId(const std::uint64_t revision_id) + { + response_.software_vcs_revision_id = revision_id; + return *this; + } + + /// @brief Sets the node software image CRC in the GetInfo response. + /// + /// Default is empty (not present). + /// + /// @return Reference to self for method chaining. + /// + GetInfoProvider& setSoftwareImageCrc(const std::uint64_t crc) + { + response_.software_image_crc.clear(); + response_.software_image_crc.push_back(crc); + return *this; + } + + /// @brief Sets the node name in the GetInfo response. + /// + /// Default is ''. + /// Truncates the name if it exceeds the capacity of the response field. + /// + /// @return Reference to self for method chaining. + /// + GetInfoProvider& setName(const cetl::string_view name) + { + return setStringField(response_.name, name); + } + + /// @brief Sets the node certificate of authenticity in the GetInfo response. + /// + /// Default is ''. + /// Truncates the certificate if it exceeds the capacity of the response field. + /// + /// @return Reference to self for method chaining. + /// + GetInfoProvider& setCertificateOfAuthenticity(const cetl::string_view certificate) + { + return setStringField(response_.certificate_of_authenticity, + certificate); } private: - using Server = presentation::ServiceServer; + using ArrayCapacity = Response::_traits_::ArrayCapacity; + using Server = presentation::ServiceServer; GetInfoProvider(presentation::Presentation& presentation, Server&& server) : presentation_{presentation} @@ -115,6 +225,16 @@ class GetInfoProvider final // NOSONAR cpp:S4963 }); } + template + GetInfoProvider& setStringField(Field& field, const cetl::string_view value) + { + const auto dst_size = std::min(value.size(), Capacity); + field.clear(); + field.reserve(dst_size); + (void) std::copy_n(value.begin(), dst_size, std::back_inserter(field)); + return *this; + } + // MARK: Data members: presentation::Presentation& presentation_; diff --git a/include/libcyphal/application/node/heartbeat_producer.hpp b/include/libcyphal/application/node/heartbeat_producer.hpp index f501fcac1..de96b29b4 100644 --- a/include/libcyphal/application/node/heartbeat_producer.hpp +++ b/include/libcyphal/application/node/heartbeat_producer.hpp @@ -101,7 +101,7 @@ class HeartbeatProducer final // NOSONAR cpp:S4963 /// struct Arg { - /// Holds current heartbeat message. + /// Holds the current heartbeat message. Message& message; /// Holds the approximate time when the callback was called. @@ -110,7 +110,7 @@ class HeartbeatProducer final // NOSONAR cpp:S4963 /// @brief Defines signature of the heartbeat update callback function. /// - /// Size of the function is arbitrary (4 pointers), but should be enough for simple lambdas. + /// The size of the function is arbitrary (4 pointers), but should be enough for simple lambdas. /// static constexpr std::size_t FunctionSize = sizeof(void*) * 4; using Function = cetl::pmr::function; diff --git a/include/libcyphal/application/registry/register.hpp b/include/libcyphal/application/registry/register.hpp index b2045e763..112af9aaa 100644 --- a/include/libcyphal/application/registry/register.hpp +++ b/include/libcyphal/application/registry/register.hpp @@ -233,11 +233,12 @@ using Register = ImplementationCell #include -#include // NOLINT for NUNAVUT_ASSERT #include #include diff --git a/include/libcyphal/presentation/common_helpers.hpp b/include/libcyphal/presentation/common_helpers.hpp index a526d855d..44c50cef4 100644 --- a/include/libcyphal/presentation/common_helpers.hpp +++ b/include/libcyphal/presentation/common_helpers.hpp @@ -11,7 +11,6 @@ #include -#include // NOLINT for NUNAVUT_ASSERT #include #include diff --git a/include/libcyphal/presentation/publisher.hpp b/include/libcyphal/presentation/publisher.hpp index 68c7669fd..9479b1cbf 100644 --- a/include/libcyphal/presentation/publisher.hpp +++ b/include/libcyphal/presentation/publisher.hpp @@ -16,7 +16,6 @@ #include #include -#include // NOLINT for NUNAVUT_ASSERT #include #include diff --git a/include/libcyphal/presentation/response_promise.hpp b/include/libcyphal/presentation/response_promise.hpp index ec3b7553a..a4f36a643 100644 --- a/include/libcyphal/presentation/response_promise.hpp +++ b/include/libcyphal/presentation/response_promise.hpp @@ -18,7 +18,6 @@ #include #include -#include // NOLINT for NUNAVUT_ASSERT #include #include diff --git a/include/libcyphal/presentation/server.hpp b/include/libcyphal/presentation/server.hpp index 215f4e0c6..32396de92 100644 --- a/include/libcyphal/presentation/server.hpp +++ b/include/libcyphal/presentation/server.hpp @@ -19,7 +19,6 @@ #include #include -#include // NOLINT for NUNAVUT_ASSERT #include #include diff --git a/include/libcyphal/presentation/subscriber.hpp b/include/libcyphal/presentation/subscriber.hpp index 87fef0581..7e75babdf 100644 --- a/include/libcyphal/presentation/subscriber.hpp +++ b/include/libcyphal/presentation/subscriber.hpp @@ -106,9 +106,10 @@ class SubscriberBase : public SubscriberImpl::CallbackNode // NOSONAR cpp:S4963 /// /// @tparam Message The message type of the subscriber. This type has the following requirements: /// - contains nested `allocator_type`, which is a PMR allocator -/// - constructible with the PMR allocator. +/// - constructible with the PMR allocator /// - contains `_traits_::ExtentBytes` constant -/// - has freestanding `deserialize` function under its namespace (so that ADL will find it). +/// - contains `_traits_::FullNameAndVersion()` static constexpr method (-> `const char*`) +/// - has freestanding `deserialize` function under its namespace (so that ADL will find it) /// template class Subscriber final : public detail::SubscriberBase @@ -147,7 +148,7 @@ class Subscriber final : public detail::SubscriberBase explicit Subscriber(detail::SubscriberImpl* const impl) : SubscriberBase{impl, - {Deserializer::getTypeId(), + {Deserializer::TypeIdGenerator::get(), Deserializer::deserializeMsgOnceForManySubs}} { } @@ -209,7 +210,8 @@ class Subscriber final : public detail::SubscriberBase friend class detail::SubscriberImpl; explicit Subscriber(detail::SubscriberImpl* const impl) - : SubscriberBase{impl, {Deserializer::getTypeId(), Deserializer::passRawMessageAsIs}} + : SubscriberBase{impl, + {Deserializer::TypeIdGenerator::get(), Deserializer::passRawMessageAsIs}} { } diff --git a/include/libcyphal/presentation/subscriber_impl.hpp b/include/libcyphal/presentation/subscriber_impl.hpp index c2ccf40d6..19434209d 100644 --- a/include/libcyphal/presentation/subscriber_impl.hpp +++ b/include/libcyphal/presentation/subscriber_impl.hpp @@ -10,6 +10,7 @@ #include "shared_object.hpp" #include "libcyphal/common/cavl/cavl.hpp" +#include "libcyphal/common/crc.hpp" #include "libcyphal/transport/msg_sessions.hpp" #include "libcyphal/transport/scattered_buffer.hpp" #include "libcyphal/transport/types.hpp" @@ -53,18 +54,18 @@ class SubscriberImpl final : public cavl::Node, public SharedObj }; // Context - using TypeId = std::uintptr_t; + using TypeId = std::uint64_t; template - static TypeId getTypeId() noexcept + struct TypeIdGenerator { - static const struct + static TypeId get() noexcept { - } placeholder{}; - // No Lint and Sonar cpp:S3630 "reinterpret_cast" should not be used" b/c it's a part of - // the type id/erasure pattern - we use this cast to be able to compare deserializers. - // NOLINTNEXTLINE(*-pro-type-reinterpret-cast) - return reinterpret_cast(&placeholder); // NOSONAR : cpp:S3630 - } + const cetl::string_view type_name{Message::_traits_::FullNameAndVersion()}; + const common::CRC64WE crc64{type_name.cbegin(), type_name.cend()}; + return crc64.get(); + } + + }; // TypeIdGenerator template static void deserializeMsgOnceForManySubs(Context& context) @@ -302,6 +303,15 @@ class SubscriberImpl final : public cavl::Node, public SharedObj }; // SubscriberImpl +template <> +struct SubscriberImpl::CallbackNode::Deserializer::TypeIdGenerator +{ + static constexpr TypeId get() noexcept + { + return 0U; + } +}; + } // namespace detail } // namespace presentation } // namespace libcyphal diff --git a/include/libcyphal/transport/udp/tx_rx_sockets.hpp b/include/libcyphal/transport/udp/tx_rx_sockets.hpp index cf55fee0f..765e95bf1 100644 --- a/include/libcyphal/transport/udp/tx_rx_sockets.hpp +++ b/include/libcyphal/transport/udp/tx_rx_sockets.hpp @@ -14,10 +14,8 @@ #include #include -#include #include #include -#include // TODO: use CETL pmr instead namespace libcyphal { diff --git a/test/unittest/application/node/test_get_info_provider.cpp b/test/unittest/application/node/test_get_info_provider.cpp index bc5dc4d7b..89034c4f5 100644 --- a/test/unittest/application/node/test_get_info_provider.cpp +++ b/test/unittest/application/node/test_get_info_provider.cpp @@ -24,8 +24,8 @@ #include #include -#include -#include +#include +#include #include #include @@ -38,10 +38,12 @@ using namespace libcyphal::presentation; // NOLINT This our main concern here i using namespace libcyphal::transport; // NOLINT This our main concern here in the unit tests. using testing::_; +using testing::Each; using testing::Invoke; using testing::Return; using testing::IsEmpty; using testing::StrictMock; +using testing::ElementsAre; using testing::VariantWith; // https://github.com/llvm/llvm-project/issues/53444 @@ -88,6 +90,7 @@ class TestGetInfoProvider : public testing::Test // MARK: - Tests: +// NOLINTNEXTLINE(readability-function-cognitive-complexity) TEST_F(TestGetInfoProvider, make) { using Service = uavcan::node::GetInfo_1_0; @@ -142,11 +145,13 @@ TEST_F(TestGetInfoProvider, make) }); scheduler_.scheduleAt(3s, [&](const auto&) { // - auto& info = get_info_provider->response(); - info.software_version.major = 7; - std::copy_n("test", 4, std::back_inserter(info.name)); - - get_info_provider->setResponseTimeout(100ms); + get_info_provider.value() + .setName("test") + .setSoftwareVersion(7, 4) + .setHardwareVersion(2, 3) + .setResponseTimeout(100ms) + .setSoftwareVcsRevisionId(0x12345678) + .setCertificateOfAuthenticity("my_cert"); EXPECT_CALL(res_tx_session_mock, send(ServiceTxMetadataEq({{{124, Priority::Nominal}, now() + 100ms}, NodeId{0x31}}), _)) // @@ -155,8 +160,16 @@ TEST_F(TestGetInfoProvider, make) Service::Response response{Service::Response::allocator_type{&mr_}}; EXPECT_TRUE(libcyphal::verification_utilities::tryDeserialize(response, fragments)); EXPECT_THAT(response.protocol_version.major, 1); + EXPECT_THAT(response.protocol_version.minor, 0); EXPECT_THAT(response.software_version.major, 7); + EXPECT_THAT(response.software_version.minor, 4); + EXPECT_THAT(response.hardware_version.major, 2); + EXPECT_THAT(response.hardware_version.minor, 3); EXPECT_THAT(registry::makeStringView(response.name), "test"); + EXPECT_THAT(registry::makeStringView(response.certificate_of_authenticity), "my_cert"); + EXPECT_THAT(response.unique_id, Each(0)); + EXPECT_THAT(response.software_vcs_revision_id, 0x12345678); + EXPECT_THAT(response.software_image_crc, IsEmpty()); return cetl::nullopt; })); @@ -165,6 +178,22 @@ TEST_F(TestGetInfoProvider, make) request.metadata.rx_meta.timestamp = now(); req_rx_cb_fn({request}); }); + scheduler_.scheduleAt(8s, [&](const auto&) { + // + const auto& response = get_info_provider + .value() // + .setUniqueId(std::array{1, 2, 3, 4}) + .setUniqueId(std::array{1, 2, 3}) + .setProtocolVersion(6, 9) + .setSoftwareImageCrc(0x12345678UL) + .setSoftwareImageCrc(0x98765432UL) + .response(); + + EXPECT_THAT(response.protocol_version.major, 6); + EXPECT_THAT(response.protocol_version.minor, 9); + EXPECT_THAT(response.unique_id, ElementsAre(1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + EXPECT_THAT(response.software_image_crc, ElementsAre(0x98765432UL)); + }); scheduler_.scheduleAt(9s, [&](const auto&) { // EXPECT_CALL(req_rx_session_mock, deinit()).Times(1); diff --git a/test/unittest/presentation/presentation_gtest_helpers.hpp b/test/unittest/presentation/presentation_gtest_helpers.hpp index a04f9247c..834e88f6b 100644 --- a/test/unittest/presentation/presentation_gtest_helpers.hpp +++ b/test/unittest/presentation/presentation_gtest_helpers.hpp @@ -15,7 +15,6 @@ #include #include -#include // NOLINT for NUNAVUT_ASSERT #include #include diff --git a/test/unittest/presentation/test_client.cpp b/test/unittest/presentation/test_client.cpp index 65bef138c..c44dc861f 100644 --- a/test/unittest/presentation/test_client.cpp +++ b/test/unittest/presentation/test_client.cpp @@ -26,7 +26,6 @@ #include #include -#include // NOLINT for NUNAVUT_ASSERT #include #include diff --git a/test/unittest/presentation/test_presentation.cpp b/test/unittest/presentation/test_presentation.cpp index c2d767441..b82cdda74 100644 --- a/test/unittest/presentation/test_presentation.cpp +++ b/test/unittest/presentation/test_presentation.cpp @@ -29,7 +29,6 @@ #include #include -#include // NOLINT for NUNAVUT_ASSERT #include #include #include @@ -86,6 +85,10 @@ struct SubMessage final static constexpr bool HasFixedPortID = true; static constexpr std::uint16_t FixedPortId = 147U; static constexpr std::size_t ExtentBytes = sizeof(std::uint64_t); + static constexpr const char* FullNameAndVersion() + { + return "Custom.SubMessage.1.0"; + } }; explicit SubMessage(const allocator_type& alloc) diff --git a/test/unittest/presentation/test_publisher.cpp b/test/unittest/presentation/test_publisher.cpp index 9a3bc7c4b..1a103371d 100644 --- a/test/unittest/presentation/test_publisher.cpp +++ b/test/unittest/presentation/test_publisher.cpp @@ -20,7 +20,6 @@ #include #include -#include // NOLINT for NUNAVUT_ASSERT #include #include diff --git a/test/unittest/presentation/test_server.cpp b/test/unittest/presentation/test_server.cpp index 78fc98939..f8baba300 100644 --- a/test/unittest/presentation/test_server.cpp +++ b/test/unittest/presentation/test_server.cpp @@ -24,7 +24,6 @@ #include #include -#include // NOLINT for NUNAVUT_ASSERT #include #include diff --git a/test/unittest/verification_utilities.hpp b/test/unittest/verification_utilities.hpp index aa046c378..2ae413dc9 100644 --- a/test/unittest/verification_utilities.hpp +++ b/test/unittest/verification_utilities.hpp @@ -9,7 +9,6 @@ #include #include -#include // NOLINT for NUNAVUT_ASSERT #include #include