From 08a337dfdfff07ad99627e7ca123f06cb3ec6e24 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 11 Apr 2024 08:30:20 +0300 Subject: [PATCH 01/64] #verification #docs --- .devcontainer/devcontainer.json | 2 +- .github/workflows/tests.yml | 6 +++--- CONTRIBUTING.md | 6 +++--- cmake/modules/Findcetl.cmake | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 498042690..bbd7035c3 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "linux development environment for libcyphal", - "image": "ghcr.io/opencyphal/toolshed:ts22.4.3", + "image": "ghcr.io/opencyphal/toolshed:ts22.4.7", "customizations": { "vscode": { "extensions": [ diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 38d26a516..c9f5054ea 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,7 +24,7 @@ jobs: contains(github.ref, '/issue/') || (github.event_name == 'pull_request') runs-on: ubuntu-latest - container: ghcr.io/opencyphal/toolshed:ts22.4.3 + container: ghcr.io/opencyphal/toolshed:ts22.4.7 steps: - uses: actions/checkout@v4 if: ${{ github.event_name != 'act' }} @@ -57,7 +57,7 @@ jobs: contains(github.ref, '/issue/') || (github.event_name == 'pull_request') runs-on: ubuntu-latest - container: ghcr.io/opencyphal/toolshed:ts22.4.3 + container: ghcr.io/opencyphal/toolshed:ts22.4.7 needs: [warmup] strategy: matrix: @@ -115,7 +115,7 @@ jobs: contains(github.ref, '/issue/') || (github.event_name == 'pull_request') runs-on: ubuntu-latest - container: ghcr.io/opencyphal/toolshed:ts22.4.3 + container: ghcr.io/opencyphal/toolshed:ts22.4.7 needs: [warmup] steps: - uses: actions/checkout@v4 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6ef7368c9..7fca7f069 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -147,10 +147,10 @@ and manually run it. ### TLDR ``` -docker pull ghcr.io/opencyphal/toolshed:ts22.4.3 +docker pull ghcr.io/opencyphal/toolshed:ts22.4.7 git clone {this repo} cd {this repo} -docker run --rm -it -v ${PWD}:/repo ghcr.io/opencyphal/toolshed:ts22.4.3 +docker run --rm -it -v ${PWD}:/repo ghcr.io/opencyphal/toolshed:ts22.4.7 mkdir build cd build cmake .. @@ -200,7 +200,7 @@ To ensure that the formatting matches the expectations of the CI suite, invoke Clang-Format of the correct version from the container (be sure to use the correct image tag): ``` -docker run --rm -v ${PWD}:/repo ghcr.io/opencyphal/toolshed:ts22.4.3 ./build-tools/bin/verify.py build-danger-danger-repo-clang-format-in-place +docker run --rm -v ${PWD}:/repo ghcr.io/opencyphal/toolshed:ts22.4.7 ./build-tools/bin/verify.py build-danger-danger-repo-clang-format-in-place ``` ### `issue/*` and hashtag-based CI triggering diff --git a/cmake/modules/Findcetl.cmake b/cmake/modules/Findcetl.cmake index 2c2a574f6..e6e5cd7f0 100644 --- a/cmake/modules/Findcetl.cmake +++ b/cmake/modules/Findcetl.cmake @@ -6,7 +6,7 @@ include(FetchContent) set(cetl_GIT_REPOSITORY "https://github.com/OpenCyphal/cetl.git") -set(cetl_GIT_TAG "c1c2ae21ed446a7b25394d0067f3f4bec43a881b") +set(cetl_GIT_TAG "ca3913eeb48f2630f236dfcc2c2a4e01484eb5b1") FetchContent_Declare( cetl From 88c447803b8fe537897e21b00e38883aae850643 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Tue, 16 Apr 2024 12:08:29 +0300 Subject: [PATCH 02/64] Added `MessageRxSession` implementation. --- cmake/modules/Findlibcanard.cmake | 2 +- include/libcyphal/transport/can/delegate.hpp | 51 +++++ .../transport/can/msg_rx_session.hpp | 122 ++++++++++++ include/libcyphal/transport/can/transport.hpp | 175 +++++++++++++----- include/libcyphal/transport/errors.hpp | 13 +- include/libcyphal/types.hpp | 12 ++ .../transport/can/test_can_transport.cpp | 31 ++++ 7 files changed, 359 insertions(+), 47 deletions(-) create mode 100644 include/libcyphal/transport/can/delegate.hpp create mode 100644 include/libcyphal/transport/can/msg_rx_session.hpp diff --git a/cmake/modules/Findlibcanard.cmake b/cmake/modules/Findlibcanard.cmake index fe52ad866..85fa9f31c 100644 --- a/cmake/modules/Findlibcanard.cmake +++ b/cmake/modules/Findlibcanard.cmake @@ -9,7 +9,7 @@ include(FetchContent) set(libcanard_GIT_REPOSITORY "https://github.com/OpenCyphal/libcanard.git") -set(libcanard_GIT_TAG "69ed329db4ae31d9c85dbe052434f60e552cecbe") +set(libcanard_GIT_TAG "a8b646c97c2cdd668d4ca5d35b55e1361823d41b") FetchContent_Declare( libcanard diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp new file mode 100644 index 000000000..43e65a8c1 --- /dev/null +++ b/include/libcyphal/transport/can/delegate.hpp @@ -0,0 +1,51 @@ +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#ifndef LIBCYPHAL_TRANSPORT_CAN_DELEGATE_HPP_INCLUDED +#define LIBCYPHAL_TRANSPORT_CAN_DELEGATE_HPP_INCLUDED + +#include + +namespace libcyphal +{ +namespace transport +{ +namespace can +{ +namespace detail +{ + +struct TransportDelegate +{ + explicit TransportDelegate(cetl::pmr::memory_resource& memory, CanardInstance canard_instance) + : memory_{memory} + , canard_instance_(canard_instance) + { + } + + static cetl::optional anyErrorFromCanard(const int8_t result) + { + if (result == CANARD_ERROR_INVALID_ARGUMENT) + { + return ArgumentError{}; + } + if (result == CANARD_ERROR_OUT_OF_MEMORY) + { + return MemoryError{}; + } + + return {}; + } + + cetl::pmr::memory_resource& memory_; + CanardInstance canard_instance_; +}; + +} // namespace detail +} // namespace can +} // namespace transport +} // namespace libcyphal + +#endif // LIBCYPHAL_TRANSPORT_CAN_DELEGATE_HPP_INCLUDED diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp new file mode 100644 index 000000000..2701eb478 --- /dev/null +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -0,0 +1,122 @@ +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#ifndef LIBCYPHAL_TRANSPORT_CAN_MSG_RX_SESSION_HPP_INCLUDED +#define LIBCYPHAL_TRANSPORT_CAN_MSG_RX_SESSION_HPP_INCLUDED + +#include "delegate.hpp" +#include "libcyphal/transport/msg_sessions.hpp" + +#include + +namespace libcyphal +{ +namespace transport +{ +namespace can +{ +namespace detail +{ +class MessageRxSession final : public IMessageRxSession +{ + // In use to disable public construction. + // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ + struct Tag + { + explicit Tag() = default; + using Interface = IMessageRxSession; + using Concrete = MessageRxSession; + }; + +public: + CETL_NODISCARD static Expected, AnyError> make(TransportDelegate& delegate, + const MessageRxParams& params) + { + cetl::optional any_error{}; + auto session = libcyphal::detail::makeUniquePtr(delegate.memory_, Tag{}, delegate, params, any_error); + if (any_error.has_value()) + { + return any_error.value(); + } + + return session; + } + + MessageRxSession(Tag, + TransportDelegate& delegate, + const MessageRxParams& params, + cetl::optional& out_error) + : delegate_{delegate} + , params_{params} + { + auto result = canardRxSubscribe(&delegate.canard_instance_, + CanardTransferKindMessage, + params_.subject_id, + params_.extent_bytes, + CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC, + &subscription_); + if (result < 0) + { + out_error = TransportDelegate::anyErrorFromCanard(result); + return; + } + CETL_DEBUG_ASSERT(result > 0, "New subscription supposed to be made."); + + is_subscribed_ = true; + } + + ~MessageRxSession() override + { + if (is_subscribed_) + { + canardRxUnsubscribe(&delegate_.canard_instance_, CanardTransferKindMessage, params_.subject_id); + } + } + +private: + // MARK: IMessageRxSession + + CETL_NODISCARD MessageRxParams getParams() const noexcept override + { + return params_; + } + + CETL_NODISCARD cetl::optional receive() override + { + return {}; + } + + // MARK: IRxSession + + void setTransferIdTimeout(const Duration timeout) override + { + CETL_DEBUG_ASSERT(timeout.count() > 0, "Timeout should be positive."); + if (timeout.count() > 0) + { + subscription_.transfer_id_timeout_usec = static_cast(timeout.count()); + } + } + + // MARK: ISession + + // MARK: IRunnable + + void run(const TimePoint) override {} + +private: + TransportDelegate& delegate_; + const MessageRxParams params_; + + bool is_subscribed_{false}; + CanardRxSubscription subscription_{}; + +}; // MessageRxSession + +} // namespace detail +} // namespace can +} // namespace transport +} // namespace libcyphal + +#endif // LIBCYPHAL_TRANSPORT_CAN_MSG_RX_SESSION_HPP_INCLUDED \ No newline at end of file diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index c3f305f05..20328c616 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -7,6 +7,8 @@ #define LIBCYPHAL_TRANSPORT_CAN_TRANSPORT_HPP_INCLUDED #include "media.hpp" +#include "delegate.hpp" +#include "msg_rx_session.hpp" #include "libcyphal/transport/transport.hpp" #include "libcyphal/transport/multiplexer.hpp" @@ -26,20 +28,72 @@ class ICanTransport : public ITransport namespace detail { -class TransportImpl final : public ICanTransport +class TransportImpl final : public ICanTransport, private TransportDelegate { + // In use to disable public construction. + // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ + struct Tag + { + explicit Tag() = default; + using Interface = ICanTransport; + using Concrete = TransportImpl; + }; + public: - TransportImpl(cetl::pmr::memory_resource& memory, + CETL_NODISCARD static Expected, FactoryError> make( + cetl::pmr::memory_resource& memory, + IMultiplexer& multiplexer, + const std::array& media, + const cetl::optional local_node_id) + { + // Verify input arguments: + // - At least one media interface must be provided. + // - If a local node ID is provided, it must be within the valid range. + // + const auto media_count = static_cast( + std::count_if(media.cbegin(), media.cend(), [](auto media) { return media != nullptr; })); + if (media_count == 0) + { + return ArgumentError{}; + } + if (local_node_id.has_value() && local_node_id.value() > CANARD_NODE_ID_MAX) + { + return ArgumentError{}; + } + + libcyphal::detail::VarArray media_array{MaxMediaInterfaces, &memory}; + media_array.reserve(media_count); + std::copy_if(media.cbegin(), media.cend(), std::back_inserter(media_array), [](auto media) { + return media != nullptr; + }); + CETL_DEBUG_ASSERT(!media_array.empty() && (media_array.size() == media_count), ""); + + const auto canard_node_id = static_cast(local_node_id.value_or(CANARD_NODE_ID_UNSET)); + + return libcyphal::detail::makeUniquePtr(memory, + Tag{}, + memory, + multiplexer, + std::move(media_array), + canard_node_id); + } + + TransportImpl(Tag, + cetl::pmr::memory_resource& memory, + IMultiplexer& multiplexer, libcyphal::detail::VarArray&& media_array, const CanardNodeID canard_node_id) - : memory_{memory} + : TransportDelegate{memory, canardInit(canardMemoryAllocate, canardMemoryFree)} , media_array_{std::move(media_array)} - , canard_instance_{canardInit(canardMemoryAllocate, canardMemoryFree)} { + // TODO: Use it! + (void) multiplexer; + canard_instance_.user_reference = this; canard_instance_.node_id = canard_node_id; } +private: // MARK: ICanTransport // MARK: ITransport @@ -51,36 +105,60 @@ class TransportImpl final : public ICanTransport } CETL_NODISCARD ProtocolParams getProtocolParams() const noexcept override { - const auto min_mtu = reduceMediaInto(std::numeric_limits::max(), - [](auto&& mtu, IMedia& media) { mtu = std::min(mtu, media.getMtu()); }); + const auto min_mtu = reduceMedia(std::numeric_limits::max(), + [](auto mtu, IMedia& media) { return std::min(mtu, media.getMtu()); }); + return ProtocolParams{1 << CANARD_TRANSFER_ID_BIT_LENGTH, min_mtu, CANARD_NODE_ID_MAX + 1}; } CETL_NODISCARD Expected, AnyError> makeMessageRxSession( - const MessageRxParams&) override + const MessageRxParams& params) override { - return NotImplementedError{}; + auto any_error = ensureNewSessionFor(CanardTransferKindMessage, params.subject_id, CANARD_SUBJECT_ID_MAX); + if (any_error.has_value()) + { + return any_error.value(); + } + + return MessageRxSession::make(asDelegate(), params); } + CETL_NODISCARD Expected, AnyError> makeMessageTxSession( const MessageTxParams&) override { return NotImplementedError{}; } + CETL_NODISCARD Expected, AnyError> makeRequestRxSession( - const RequestRxParams&) override + const RequestRxParams& params) override { + auto any_error = ensureNewSessionFor(CanardTransferKindRequest, params.service_id, CANARD_SERVICE_ID_MAX); + if (any_error.has_value()) + { + return any_error.value(); + } + return NotImplementedError{}; } + CETL_NODISCARD Expected, AnyError> makeRequestTxSession( const RequestTxParams&) override { return NotImplementedError{}; } + CETL_NODISCARD Expected, AnyError> makeResponseRxSession( - const ResponseRxParams&) override + const ResponseRxParams& params) override { + auto any_error = ensureNewSessionFor(CanardTransferKindResponse, params.service_id, CANARD_SERVICE_ID_MAX); + if (any_error.has_value()) + { + return any_error.value(); + } + return NotImplementedError{}; } + CETL_NODISCARD Expected, AnyError> makeResponseTxSession( const ResponseTxParams&) override { @@ -91,7 +169,13 @@ class TransportImpl final : public ICanTransport void run(const TimePoint) override {} -private: + // MARK: TransportDelegate + + CETL_NODISCARD inline TransportDelegate& asDelegate() + { + return *this; + } + // MARK: Privates: // Until "canardMemFree must provide size" issue #216 is resolved, @@ -155,21 +239,52 @@ class TransportImpl final : public ICanTransport } template - T reduceMediaInto(T&& init, Reducer reducer) const + void reduceMediaInto(T&& init, Reducer reducer) { for (const auto media : media_array_) { CETL_DEBUG_ASSERT(media != nullptr, "Expected media interface."); reducer(std::forward(init), std::ref(*media)); } - return init; + } + + template + CETL_NODISCARD T reduceMedia(const T init, Reducer reducer) const + { + T acc = init; + for (const auto media : media_array_) + { + CETL_DEBUG_ASSERT(media != nullptr, "Expected media interface."); + acc = reducer(std::forward(acc), std::ref(*media)); + } + return acc; + } + + CETL_NODISCARD cetl::optional ensureNewSessionFor(const CanardTransferKind transfer_kind, + const PortId port_id, + const PortId max_port_id) noexcept + { + if (port_id > max_port_id) + { + return ArgumentError{}; + } + + const auto hasSubscription = canardRxHasSubscription(&canard_instance_, transfer_kind, port_id); + if (hasSubscription < 0) + { + return anyErrorFromCanard(hasSubscription); + } + if (hasSubscription > 0) + { + return SessionAlreadyExistsError{}; + } + + return {}; } // MARK: Data members: - cetl::pmr::memory_resource& memory_; const libcyphal::detail::VarArray media_array_; - CanardInstance canard_instance_; }; // TransportImpl @@ -181,35 +296,7 @@ CETL_NODISCARD inline Expected, FactoryError> makeTrans const std::array& media, // TODO: replace with `cetl::span` const cetl::optional local_node_id) { - // TODO: Use these! - (void) multiplexer; - - // Verify input arguments: - // - At least one media interface must be provided. - // - If a local node ID is provided, it must be within the valid range. - // - const auto media_count = - static_cast(std::count_if(media.cbegin(), media.cend(), [](auto m) { return m != nullptr; })); - if (media_count == 0) - { - return ArgumentError{}; - } - if (local_node_id.has_value() && local_node_id.value() > CANARD_NODE_ID_MAX) - { - return ArgumentError{}; - } - - libcyphal::detail::VarArray media_array{MaxMediaInterfaces, &memory}; - media_array.reserve(media_count); - std::copy_if(media.cbegin(), media.cend(), std::back_inserter(media_array), [](auto m) { return m != nullptr; }); - CETL_DEBUG_ASSERT(!media_array.empty() && (media_array.size() == media_count), ""); - - const auto canard_node_id = static_cast(local_node_id.value_or(CANARD_NODE_ID_UNSET)); - - libcyphal::detail::PmrAllocator allocator{&memory}; - auto transport = cetl::pmr::Factory::make_unique(allocator, memory, std::move(media_array), canard_node_id); - - return UniquePtr{transport.release(), UniquePtr::deleter_type{allocator, 1}}; + return detail::TransportImpl::make(memory, multiplexer, media, local_node_id); } } // namespace can diff --git a/include/libcyphal/transport/errors.hpp b/include/libcyphal/transport/errors.hpp index db99695e0..1a21927cf 100644 --- a/include/libcyphal/transport/errors.hpp +++ b/include/libcyphal/transport/errors.hpp @@ -33,14 +33,23 @@ struct PlatformError std::uint32_t code; }; +struct SessionAlreadyExistsError +{}; + // TODO: Delete it when everything is implemented. struct NotImplementedError {}; /// @brief Defines any possible error at Cyphal transport layer. /// -using AnyError = cetl:: - variant; +using AnyError = cetl::variant; /// @brief Defines any possible factory error at Cyphal transport layer. /// diff --git a/include/libcyphal/types.hpp b/include/libcyphal/types.hpp index 1f10daed5..fae42e2d0 100644 --- a/include/libcyphal/types.hpp +++ b/include/libcyphal/types.hpp @@ -65,6 +65,18 @@ using PmrAllocator = cetl::pmr::polymorphic_allocator; template using VarArray = cetl::VariableLengthArray>; +template +CETL_NODISCARD UniquePtr makeUniquePtr(cetl::pmr::memory_resource& memory, Args&&... args) +{ + PmrAllocator allocator{&memory}; + auto interface_deleter = typename UniquePtr::deleter_type{allocator, 1}; + + auto concrete = cetl::pmr::Factory::make_unique(allocator, std::forward(args)...); + auto interface = UniquePtr{concrete.release(), interface_deleter}; + + return interface; +} + } // namespace detail } // namespace libcyphal diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index 43f809798..c0abad162 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -144,4 +144,35 @@ TEST(test_can_transport, getProtocolParams) } } +TEST(test_can_transport, makeMessageRxSession) +{ + auto mr = cetl::pmr::new_delete_resource(); + + StrictMock mux_mock{}; + StrictMock media_mock{}; + + auto transport = cetl::get>(makeTransport(*mr, mux_mock, {&media_mock}, {})); + + auto maybe_rx_session = transport->makeMessageRxSession({42, 123}); + EXPECT_FALSE(cetl::get_if(&maybe_rx_session)); + + auto session = cetl::get>(std::move(maybe_rx_session)); + EXPECT_TRUE(session); + EXPECT_EQ(42, session->getParams().extent_bytes); + EXPECT_EQ(123, session->getParams().subject_id); +} + +TEST(test_can_transport, makeMessageRxSession_invalid_subject_id) +{ + auto mr = cetl::pmr::new_delete_resource(); + + StrictMock mux_mock{}; + StrictMock media_mock{}; + + auto transport = cetl::get>(makeTransport(*mr, mux_mock, {&media_mock}, {})); + + auto maybe_rx_session = transport->makeMessageRxSession({0, CANARD_SUBJECT_ID_MAX + 1}); + EXPECT_TRUE(cetl::get_if(cetl::get_if(&maybe_rx_session))); +} + } // namespace From 74d030ccbd457383e9dc1e4f077fdf60ff50edbe Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Wed, 17 Apr 2024 09:16:46 +0300 Subject: [PATCH 03/64] Implemented `CanardMemory` for `DynamicBuffer`. --- include/libcyphal/transport/can/delegate.hpp | 154 +++++++++++++++++- .../transport/can/msg_rx_session.hpp | 47 ++++-- include/libcyphal/transport/can/transport.hpp | 130 ++++++--------- include/libcyphal/transport/defines.hpp | 6 + include/libcyphal/transport/msg_sessions.hpp | 5 +- include/libcyphal/transport/udp/transport.hpp | 25 ++- 6 files changed, 269 insertions(+), 98 deletions(-) diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp index 43e65a8c1..23c7ac99c 100644 --- a/include/libcyphal/transport/can/delegate.hpp +++ b/include/libcyphal/transport/can/delegate.hpp @@ -19,10 +19,84 @@ namespace detail struct TransportDelegate { - explicit TransportDelegate(cetl::pmr::memory_resource& memory, CanardInstance canard_instance) + // TODO: Set proper type id. + class CanardMemory final : public cetl::rtti_helper, DynamicBuffer::Interface> + { + public: + CanardMemory(TransportDelegate& delegate, void* buffer, const std::size_t payload_size) + : delegate_{delegate} + , buffer_{buffer} + , payload_size_{payload_size} + { + } + CanardMemory(CanardMemory&& other) noexcept + : delegate_{other.delegate_} + , buffer_{other.buffer_} + , payload_size_{other.payload_size_} + { + other.buffer_ = nullptr; + other.payload_size_ = 0; + } + CanardMemory(const CanardMemory& other) = delete; + + ~CanardMemory() override + { + if (buffer_ != nullptr) + { + delegate_.canardMemoryFree(buffer_); + } + } + + CanardMemory& operator=(const CanardMemory&) = delete; + CanardMemory& operator=(CanardMemory&&) noexcept = delete; + + // MARK: DynamicBuffer::Interface + + CETL_NODISCARD std::size_t size() const noexcept override + { + return payload_size_; + } + + CETL_NODISCARD std::size_t copy(const std::size_t offset_bytes, + void* const destination, + const std::size_t length_bytes) const override + { + (void) offset_bytes; + (void) destination; + (void) length_bytes; + + // TODO: Implement this method. + return 0; + } + + private: + // MARK: Data members: + + TransportDelegate& delegate_; + void* buffer_; + std::size_t payload_size_; + + }; // CanardMemory + + explicit TransportDelegate(cetl::pmr::memory_resource& memory) : memory_{memory} - , canard_instance_(canard_instance) + , canard_instance_(canardInit(canardMemoryAllocate, canardMemoryFree)) + { + } + + CETL_NODISCARD inline CanardInstance& canard_instance() noexcept + { + return canard_instance_; + } + + CETL_NODISCARD inline const CanardInstance& canard_instance() const noexcept + { + return canard_instance_; + } + + CETL_NODISCARD inline cetl::pmr::memory_resource& memory() const noexcept { + return memory_; } static cetl::optional anyErrorFromCanard(const int8_t result) @@ -39,9 +113,83 @@ struct TransportDelegate return {}; } + void canardMemoryFree(void* pointer) + { + if (pointer == nullptr) + { + return; + } + + auto memory_header = static_cast(pointer); + --memory_header; + + memory_.deallocate(memory_header, memory_header->size); + } + +private: + // Until "canardMemFree must provide size" issue #216 is resolved, + // we need to store the size of the memory allocated. + // TODO: Remove this workaround when the issue is resolved. + // see https://github.com/OpenCyphal/libcanard/issues/216 + // + struct CanardMemoryHeader final + { + alignas(std::max_align_t) std::size_t size; + }; + + CETL_NODISCARD static inline TransportDelegate& getSelfFrom(const CanardInstance* const ins) + { + CETL_DEBUG_ASSERT(ins != nullptr, "Expected canard instance."); + CETL_DEBUG_ASSERT(ins->user_reference != nullptr, "Expected `this` transport as user reference."); + + return *static_cast(ins->user_reference); + } + + CETL_NODISCARD static void* canardMemoryAllocate(CanardInstance* ins, size_t amount) + { + auto& self = getSelfFrom(ins); + + const auto memory_size = sizeof(CanardMemoryHeader) + amount; + auto memory_header = static_cast(self.memory_.allocate(memory_size)); + if (memory_header == nullptr) + { + return nullptr; + } + + // Return the memory after the `CanardMemoryHeader` struct (containing its size). + // The size is used in `canardMemoryFree` to deallocate the memory. + // + memory_header->size = memory_size; + return ++memory_header; + } + + static void canardMemoryFree(CanardInstance* ins, void* pointer) + { + auto& self = getSelfFrom(ins); + self.canardMemoryFree(pointer); + } + + // MARK: Data members: + cetl::pmr::memory_resource& memory_; CanardInstance canard_instance_; -}; + +}; // TransportDelegate + +struct SessionDelegate +{ + SessionDelegate(SessionDelegate&&) = delete; + SessionDelegate(const SessionDelegate&) = delete; + SessionDelegate& operator=(SessionDelegate&&) = delete; + SessionDelegate& operator=(const SessionDelegate&) = delete; + + virtual void acceptRxTransfer(const CanardRxTransfer& transfer) = 0; + +protected: + SessionDelegate() = default; + virtual ~SessionDelegate() = default; + +}; // SessionDelegate } // namespace detail } // namespace can diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp index 2701eb478..1d8ce5370 100644 --- a/include/libcyphal/transport/can/msg_rx_session.hpp +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -11,6 +11,8 @@ #include +#include + namespace libcyphal { namespace transport @@ -19,7 +21,7 @@ namespace can { namespace detail { -class MessageRxSession final : public IMessageRxSession +class MessageRxSession final : public IMessageRxSession, private SessionDelegate { // In use to disable public construction. // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ @@ -35,7 +37,7 @@ class MessageRxSession final : public IMessageRxSession const MessageRxParams& params) { cetl::optional any_error{}; - auto session = libcyphal::detail::makeUniquePtr(delegate.memory_, Tag{}, delegate, params, any_error); + auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Tag{}, delegate, params, any_error); if (any_error.has_value()) { return any_error.value(); @@ -51,7 +53,7 @@ class MessageRxSession final : public IMessageRxSession : delegate_{delegate} , params_{params} { - auto result = canardRxSubscribe(&delegate.canard_instance_, + auto result = canardRxSubscribe(&delegate.canard_instance(), CanardTransferKindMessage, params_.subject_id, params_.extent_bytes, @@ -64,14 +66,15 @@ class MessageRxSession final : public IMessageRxSession } CETL_DEBUG_ASSERT(result > 0, "New subscription supposed to be made."); - is_subscribed_ = true; + is_subscribed_ = true; + subscription_.user_reference = static_cast(this); } ~MessageRxSession() override { if (is_subscribed_) { - canardRxUnsubscribe(&delegate_.canard_instance_, CanardTransferKindMessage, params_.subject_id); + canardRxUnsubscribe(&delegate_.canard_instance(), CanardTransferKindMessage, params_.subject_id); } } @@ -85,32 +88,50 @@ class MessageRxSession final : public IMessageRxSession CETL_NODISCARD cetl::optional receive() override { - return {}; + return std::move(last_rx_transfer_); } // MARK: IRxSession void setTransferIdTimeout(const Duration timeout) override { - CETL_DEBUG_ASSERT(timeout.count() > 0, "Timeout should be positive."); - if (timeout.count() > 0) + const auto timeout_us = std::chrono::duration_cast(timeout); + if (timeout_us.count() > 0) { subscription_.transfer_id_timeout_usec = static_cast(timeout.count()); } } - // MARK: ISession - // MARK: IRunnable void run(const TimePoint) override {} -private: + // MARK: SessionDelegate + + void acceptRxTransfer(const CanardRxTransfer& transfer) override + { + const auto priority{static_cast(transfer.metadata.priority)}; + const auto transfer_id{static_cast(transfer.metadata.transfer_id)}; + const auto timestamp = TimePoint{std::chrono::microseconds{transfer.timestamp_usec}}; + + const auto publisher_node_id = transfer.metadata.remote_node_id > CANARD_NODE_ID_MAX + ? cetl::nullopt + : cetl::make_optional(transfer.metadata.remote_node_id); + + const MessageTransferMetadata meta{{transfer_id, timestamp, priority}, publisher_node_id}; + TransportDelegate::CanardMemory canard_memory{delegate_, transfer.payload, transfer.payload_size}; + + last_rx_transfer_.emplace(MessageRxTransfer{meta, DynamicBuffer{std::move(canard_memory)}}); + } + + // MARK: Data members: + TransportDelegate& delegate_; const MessageRxParams params_; - bool is_subscribed_{false}; - CanardRxSubscription subscription_{}; + bool is_subscribed_{false}; + CanardRxSubscription subscription_{}; + cetl::optional last_rx_transfer_{}; }; // MessageRxSession diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index 20328c616..e902e0ee4 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -15,6 +15,7 @@ #include #include +#include namespace libcyphal { @@ -83,25 +84,23 @@ class TransportImpl final : public ICanTransport, private TransportDelegate IMultiplexer& multiplexer, libcyphal::detail::VarArray&& media_array, const CanardNodeID canard_node_id) - : TransportDelegate{memory, canardInit(canardMemoryAllocate, canardMemoryFree)} + : TransportDelegate{memory} , media_array_{std::move(media_array)} { // TODO: Use it! (void) multiplexer; - canard_instance_.user_reference = this; - canard_instance_.node_id = canard_node_id; + canard_instance().user_reference = this; + canard_instance().node_id = canard_node_id; } private: - // MARK: ICanTransport - // MARK: ITransport CETL_NODISCARD cetl::optional getLocalNodeId() const noexcept override { - return canard_instance_.node_id > CANARD_NODE_ID_MAX ? cetl::nullopt - : cetl::make_optional(canard_instance_.node_id); + return canard_instance().node_id > CANARD_NODE_ID_MAX ? cetl::nullopt + : cetl::make_optional(canard_instance().node_id); } CETL_NODISCARD ProtocolParams getProtocolParams() const noexcept override { @@ -167,86 +166,59 @@ class TransportImpl final : public ICanTransport, private TransportDelegate // MARK: IRunnable - void run(const TimePoint) override {} - - // MARK: TransportDelegate - - CETL_NODISCARD inline TransportDelegate& asDelegate() + void run(const TimePoint) override { - return *this; - } - - // MARK: Privates: + std::array payload{}; - // Until "canardMemFree must provide size" issue #216 is resolved, - // we need to store the size of the memory allocated. - // TODO: Remove this workaround when the issue is resolved. - // see https://github.com/OpenCyphal/libcanard/issues/216 - // - struct CanardMemory - { - alignas(std::max_align_t) std::size_t size; - }; - - CETL_NODISCARD static inline TransportImpl& getSelfFrom(const CanardInstance* const ins) - { - CETL_DEBUG_ASSERT(ins != nullptr, "Expected canard instance."); - CETL_DEBUG_ASSERT(ins->user_reference != nullptr, "Expected `this` transport as user reference."); - - return *static_cast(ins->user_reference); - } - - CETL_NODISCARD static void* canardMemoryAllocate(CanardInstance* ins, size_t amount) - { - auto& self = getSelfFrom(ins); - - const auto memory_size = sizeof(CanardMemory) + amount; - auto canard_memory = static_cast(self.memory_.allocate(memory_size)); - if (canard_memory == nullptr) + for (std::size_t media_index = 0; media_index < media_array_.size(); media_index++) { - return nullptr; - } - - // Return the memory after the `CanardMemory` struct (containing its size). - // The size is used in `canardMemoryFree` to deallocate the memory. - // - canard_memory->size = memory_size; - return ++canard_memory; + CETL_DEBUG_ASSERT(media_array_[media_index] != nullptr, "Expected media interface."); + auto& media = *media_array_[media_index]; + + // TODO: Handle errors. + const auto pop_result = media.pop(payload); + if (const auto opt_rx_meta = cetl::get_if>(&pop_result)) + { + if (opt_rx_meta->has_value()) + { + auto& rx_meta = opt_rx_meta->value(); + + const auto timestamp_us = + std::chrono::duration_cast(rx_meta.timestamp.time_since_epoch()); + const CanardFrame canard_frame{rx_meta.can_id, rx_meta.payload_size, payload.cbegin()}; + + CanardRxTransfer out_transfer{}; + CanardRxSubscription* out_subscription{}; + + // TODO: Handle errors. + const auto result = canardRxAccept(&canard_instance(), + static_cast(timestamp_us.count()), + &canard_frame, + static_cast(media_index), + &out_transfer, + &out_subscription); + if (result > 0) + { + CETL_DEBUG_ASSERT(out_subscription != nullptr, "Expected subscription."); + CETL_DEBUG_ASSERT(out_subscription->user_reference != nullptr, "Expected session delegate."); + + const auto delegate = static_cast(out_subscription->user_reference); + delegate->acceptRxTransfer(out_transfer); + } + } + } + + } // for each media } - static void canardMemoryFree(CanardInstance* ins, void* pointer) - { - if (pointer == nullptr) - { - return; - } - - auto canard_memory = static_cast(pointer); - --canard_memory; - - auto& self = getSelfFrom(ins); - self.memory_.deallocate(canard_memory, canard_memory->size); - } + // MARK: TransportDelegate - template - void forEachMedia(Action action) + CETL_NODISCARD inline TransportDelegate& asDelegate() { - for (const auto media : media_array_) - { - CETL_DEBUG_ASSERT(media != nullptr, "Expected media interface."); - action(std::ref(*media)); - } + return *this; } - template - void reduceMediaInto(T&& init, Reducer reducer) - { - for (const auto media : media_array_) - { - CETL_DEBUG_ASSERT(media != nullptr, "Expected media interface."); - reducer(std::forward(init), std::ref(*media)); - } - } + // MARK: Privates: template CETL_NODISCARD T reduceMedia(const T init, Reducer reducer) const @@ -269,7 +241,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate return ArgumentError{}; } - const auto hasSubscription = canardRxHasSubscription(&canard_instance_, transfer_kind, port_id); + const auto hasSubscription = canardRxHasSubscription(&canard_instance(), transfer_kind, port_id); if (hasSubscription < 0) { return anyErrorFromCanard(hasSubscription); diff --git a/include/libcyphal/transport/defines.hpp b/include/libcyphal/transport/defines.hpp index 6377697e3..98706468b 100644 --- a/include/libcyphal/transport/defines.hpp +++ b/include/libcyphal/transport/defines.hpp @@ -57,6 +57,12 @@ struct TransferMetadata struct MessageTransferMetadata final : TransferMetadata { + MessageTransferMetadata(const TransferMetadata& transfer_metadata, cetl::optional _publisher_node_id) + : TransferMetadata{transfer_metadata} + , publisher_node_id{_publisher_node_id} + { + } + cetl::optional publisher_node_id; }; diff --git a/include/libcyphal/transport/msg_sessions.hpp b/include/libcyphal/transport/msg_sessions.hpp index 358d27272..b6311cd58 100644 --- a/include/libcyphal/transport/msg_sessions.hpp +++ b/include/libcyphal/transport/msg_sessions.hpp @@ -36,7 +36,8 @@ class IMessageRxSession : public IRxSession /// @return A message transfer if available; otherwise an empty optional. /// CETL_NODISCARD virtual cetl::optional receive() = 0; -}; + +}; // IMessageRxSession class IMessageTxSession : public IRunnable { @@ -51,7 +52,7 @@ class IMessageTxSession : public IRunnable /// CETL_NODISCARD virtual cetl::optional send(const TransferMetadata& metadata, const PayloadFragments payload_fragments) = 0; -}; +}; // IMessageTxSession } // namespace transport } // namespace libcyphal diff --git a/include/libcyphal/transport/udp/transport.hpp b/include/libcyphal/transport/udp/transport.hpp index d7e6d83e4..ab86f51d5 100644 --- a/include/libcyphal/transport/udp/transport.hpp +++ b/include/libcyphal/transport/udp/transport.hpp @@ -10,6 +10,8 @@ #include "libcyphal/transport/transport.hpp" #include "libcyphal/transport/multiplexer.hpp" +#include + namespace libcyphal { namespace transport @@ -25,9 +27,30 @@ namespace detail class TransportImpl final : public IUdpTransport { + // In use to disable public construction. + // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ + struct Tag + { + explicit Tag() = default; + using Interface = IUdpTransport; + using Concrete = TransportImpl; + }; + public: - // MARK: IUpdTransport + TransportImpl(Tag, + cetl::pmr::memory_resource& memory, + IMultiplexer& multiplexer, + libcyphal::detail::VarArray&& media_array, + const UdpardNodeID udpard_node_id) + { + // TODO: Use them! + (void) memory; + (void) multiplexer; + (void) media_array; + (void) udpard_node_id; + } +private: // MARK: ITransport CETL_NODISCARD cetl::optional getLocalNodeId() const noexcept override From a90c2fe7f3bd6f2b6f0f54440e5ff859458fcd4a Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Wed, 17 Apr 2024 09:27:36 +0300 Subject: [PATCH 04/64] set proper type id --- include/libcyphal/transport/can/delegate.hpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp index 23c7ac99c..1fb5b1974 100644 --- a/include/libcyphal/transport/can/delegate.hpp +++ b/include/libcyphal/transport/can/delegate.hpp @@ -19,10 +19,12 @@ namespace detail struct TransportDelegate { - // TODO: Set proper type id. - class CanardMemory final : public cetl::rtti_helper, DynamicBuffer::Interface> + // 1141F5C0-2E61-44BF-9F0E-FA1C518CD517 + using CanardMemoryTypeIdType = cetl:: + type_id_type<0x11, 0x41, 0xF5, 0xC0, 0x2E, 0x61, 0x44, 0xBF, 0x9F, 0x0E, 0xFA, 0x1C, 0x51, 0x8C, 0xD5, 0x17>; + + struct CanardMemory final : public cetl::rtti_helper { - public: CanardMemory(TransportDelegate& delegate, void* buffer, const std::size_t payload_size) : delegate_{delegate} , buffer_{buffer} From 47c42186ea7c34026296de05678b92506dd16e6f Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Wed, 17 Apr 2024 10:08:10 +0300 Subject: [PATCH 05/64] minor fixes --- include/libcyphal/transport/can/delegate.hpp | 6 +++--- .../libcyphal/transport/can/msg_rx_session.hpp | 18 ++++++++++-------- include/libcyphal/transport/can/transport.hpp | 12 ++++++++---- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp index 1fb5b1974..fcd2ce71b 100644 --- a/include/libcyphal/transport/can/delegate.hpp +++ b/include/libcyphal/transport/can/delegate.hpp @@ -25,7 +25,7 @@ struct TransportDelegate struct CanardMemory final : public cetl::rtti_helper { - CanardMemory(TransportDelegate& delegate, void* buffer, const std::size_t payload_size) + CanardMemory(TransportDelegate& delegate, void* const buffer, const std::size_t payload_size) : delegate_{delegate} , buffer_{buffer} , payload_size_{payload_size} @@ -39,7 +39,7 @@ struct TransportDelegate other.buffer_ = nullptr; other.payload_size_ = 0; } - CanardMemory(const CanardMemory& other) = delete; + CanardMemory(const CanardMemory&) = delete; ~CanardMemory() override { @@ -115,7 +115,7 @@ struct TransportDelegate return {}; } - void canardMemoryFree(void* pointer) + void canardMemoryFree(void* const pointer) { if (pointer == nullptr) { diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp index 1d8ce5370..78c1e282e 100644 --- a/include/libcyphal/transport/can/msg_rx_session.hpp +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -53,12 +53,12 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate : delegate_{delegate} , params_{params} { - auto result = canardRxSubscribe(&delegate.canard_instance(), - CanardTransferKindMessage, - params_.subject_id, - params_.extent_bytes, - CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC, - &subscription_); + const auto result = canardRxSubscribe(&delegate.canard_instance(), + CanardTransferKindMessage, + static_cast(params_.subject_id), + static_cast(params_.extent_bytes), + CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC, + &subscription_); if (result < 0) { out_error = TransportDelegate::anyErrorFromCanard(result); @@ -74,7 +74,9 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate { if (is_subscribed_) { - canardRxUnsubscribe(&delegate_.canard_instance(), CanardTransferKindMessage, params_.subject_id); + canardRxUnsubscribe(&delegate_.canard_instance(), + CanardTransferKindMessage, + static_cast(params_.subject_id)); } } @@ -98,7 +100,7 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate const auto timeout_us = std::chrono::duration_cast(timeout); if (timeout_us.count() > 0) { - subscription_.transfer_id_timeout_usec = static_cast(timeout.count()); + subscription_.transfer_id_timeout_usec = static_cast(timeout_us.count()); } } diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index e902e0ee4..445f2da89 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -99,8 +99,12 @@ class TransportImpl final : public ICanTransport, private TransportDelegate CETL_NODISCARD cetl::optional getLocalNodeId() const noexcept override { - return canard_instance().node_id > CANARD_NODE_ID_MAX ? cetl::nullopt - : cetl::make_optional(canard_instance().node_id); + if (canard_instance().node_id > CANARD_NODE_ID_MAX) + { + return cetl::nullopt; + } + + return cetl::make_optional(static_cast(canard_instance().node_id)); } CETL_NODISCARD ProtocolParams getProtocolParams() const noexcept override { @@ -170,7 +174,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate { std::array payload{}; - for (std::size_t media_index = 0; media_index < media_array_.size(); media_index++) + for (std::size_t media_index = 0; media_index < media_array_.size(); ++media_index) { CETL_DEBUG_ASSERT(media_array_[media_index] != nullptr, "Expected media interface."); auto& media = *media_array_[media_index]; @@ -181,7 +185,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate { if (opt_rx_meta->has_value()) { - auto& rx_meta = opt_rx_meta->value(); + const auto& rx_meta = opt_rx_meta->value(); const auto timestamp_us = std::chrono::duration_cast(rx_meta.timestamp.time_since_epoch()); From e9c5133c8cd6b3fd54cae4cfe67dddd0673e1b98 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Wed, 17 Apr 2024 10:27:10 +0300 Subject: [PATCH 06/64] introduce `TestCanTransport` test fixture --- .../transport/can/test_can_transport.cpp | 67 +++++++------------ 1 file changed, 26 insertions(+), 41 deletions(-) diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index c0abad162..49f0e02c0 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -20,16 +20,19 @@ using namespace libcyphal::transport::can; using testing::StrictMock; -TEST(test_can_transport, makeTransport_getLocalNodeId) +class TestCanTransport : public testing::Test { - auto mr = cetl::pmr::new_delete_resource(); - - StrictMock mux_mock{}; - StrictMock media_mock{}; +protected: + StrictMock media_mock_{}; + StrictMock mux_mock_{}; + cetl::pmr::memory_resource& mr_{*cetl::pmr::new_delete_resource()}; +}; +TEST_F(TestCanTransport, makeTransport_getLocalNodeId) +{ // Anonymous node { - auto maybe_transport = makeTransport(*mr, mux_mock, {&media_mock}, {}); + auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, {}); EXPECT_FALSE(cetl::get_if(&maybe_transport)); auto transport = cetl::get>(std::move(maybe_transport)); @@ -41,7 +44,7 @@ TEST(test_can_transport, makeTransport_getLocalNodeId) { const auto node_id = cetl::make_optional(static_cast(42)); - auto maybe_transport = makeTransport(*mr, mux_mock, {&media_mock}, node_id); + auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, node_id); EXPECT_FALSE(cetl::get_if(&maybe_transport)); auto transport = cetl::get>(std::move(maybe_transport)); @@ -53,7 +56,7 @@ TEST(test_can_transport, makeTransport_getLocalNodeId) { StrictMock media_mock2; - auto maybe_transport = makeTransport(*mr, mux_mock, {&media_mock, nullptr, &media_mock2}, {}); + auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_, nullptr, &media_mock2}, {}); EXPECT_FALSE(cetl::get_if(&maybe_transport)); auto transport = cetl::get>(std::move(maybe_transport)); @@ -64,7 +67,7 @@ TEST(test_can_transport, makeTransport_getLocalNodeId) { StrictMock media_mock2{}, media_mock3{}; - auto maybe_transport = makeTransport(*mr, mux_mock, {&media_mock, &media_mock2, &media_mock3}, {}); + auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_, &media_mock2, &media_mock3}, {}); EXPECT_FALSE(cetl::get_if(&maybe_transport)); auto transport = cetl::get>(std::move(maybe_transport)); @@ -72,18 +75,13 @@ TEST(test_can_transport, makeTransport_getLocalNodeId) } } -TEST(test_can_transport, makeTransport_with_invalid_arguments) +TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) { - auto mr = cetl::pmr::new_delete_resource(); - - StrictMock mux_mock{}; - StrictMock media_mock{}; - // No media { const auto node_id = cetl::make_optional(static_cast(CANARD_NODE_ID_MAX)); - const auto maybe_transport = makeTransport(*mr, mux_mock, {}, node_id); + const auto maybe_transport = makeTransport(mr_, mux_mock_, {}, node_id); EXPECT_TRUE(cetl::get_if(cetl::get_if(&maybe_transport))); } @@ -91,7 +89,7 @@ TEST(test_can_transport, makeTransport_with_invalid_arguments) { const auto node_id = cetl::make_optional(static_cast(CANARD_NODE_ID_MAX + 1)); - const auto maybe_transport = makeTransport(*mr, mux_mock, {&media_mock}, node_id); + const auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, node_id); EXPECT_TRUE(cetl::get_if(cetl::get_if(&maybe_transport))); } @@ -99,7 +97,7 @@ TEST(test_can_transport, makeTransport_with_invalid_arguments) { const auto node_id = cetl::make_optional(static_cast(CANARD_NODE_ID_UNSET)); - const auto maybe_transport = makeTransport(*mr, mux_mock, {&media_mock}, node_id); + const auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, node_id); EXPECT_TRUE(cetl::get_if(cetl::get_if(&maybe_transport))); } @@ -108,22 +106,19 @@ TEST(test_can_transport, makeTransport_with_invalid_arguments) const NodeId too_big = static_cast(std::numeric_limits::max()) + 1; const auto node_id = cetl::make_optional(too_big); - const auto maybe_transport = makeTransport(*mr, mux_mock, {&media_mock}, node_id); + const auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, node_id); EXPECT_TRUE(cetl::get_if(cetl::get_if(&maybe_transport))); } } -TEST(test_can_transport, getProtocolParams) +TEST_F(TestCanTransport, getProtocolParams) { - auto mr = cetl::pmr::new_delete_resource(); - - StrictMock mux_mock{}; - StrictMock media_mock1{}, media_mock2{}; + StrictMock media_mock2{}; auto transport = - cetl::get>(makeTransport(*mr, mux_mock, {&media_mock1, &media_mock2}, {})); + cetl::get>(makeTransport(mr_, mux_mock_, {&media_mock_, &media_mock2}, {})); - EXPECT_CALL(media_mock1, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_FD)); + EXPECT_CALL(media_mock_, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_FD)); EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_CLASSIC)); auto params = transport->getProtocolParams(); @@ -136,7 +131,7 @@ TEST(test_can_transport, getProtocolParams) EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_FD)); EXPECT_EQ(CANARD_MTU_CAN_FD, transport->getProtocolParams().mtu_bytes); - EXPECT_CALL(media_mock1, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_CLASSIC)); + EXPECT_CALL(media_mock_, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_CLASSIC)); EXPECT_EQ(CANARD_MTU_CAN_CLASSIC, transport->getProtocolParams().mtu_bytes); EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_CLASSIC)); @@ -144,14 +139,9 @@ TEST(test_can_transport, getProtocolParams) } } -TEST(test_can_transport, makeMessageRxSession) +TEST_F(TestCanTransport, makeMessageRxSession) { - auto mr = cetl::pmr::new_delete_resource(); - - StrictMock mux_mock{}; - StrictMock media_mock{}; - - auto transport = cetl::get>(makeTransport(*mr, mux_mock, {&media_mock}, {})); + auto transport = cetl::get>(makeTransport(mr_, mux_mock_, {&media_mock_}, {})); auto maybe_rx_session = transport->makeMessageRxSession({42, 123}); EXPECT_FALSE(cetl::get_if(&maybe_rx_session)); @@ -162,14 +152,9 @@ TEST(test_can_transport, makeMessageRxSession) EXPECT_EQ(123, session->getParams().subject_id); } -TEST(test_can_transport, makeMessageRxSession_invalid_subject_id) +TEST_F(TestCanTransport, makeMessageRxSession_invalid_subject_id) { - auto mr = cetl::pmr::new_delete_resource(); - - StrictMock mux_mock{}; - StrictMock media_mock{}; - - auto transport = cetl::get>(makeTransport(*mr, mux_mock, {&media_mock}, {})); + auto transport = cetl::get>(makeTransport(mr_, mux_mock_, {&media_mock_}, {})); auto maybe_rx_session = transport->makeMessageRxSession({0, CANARD_SUBJECT_ID_MAX + 1}); EXPECT_TRUE(cetl::get_if(cetl::get_if(&maybe_rx_session))); From ec91d2aba694c31a9133c429763d914b6033154c Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Wed, 17 Apr 2024 16:50:30 +0300 Subject: [PATCH 07/64] Added `TrackingMemoryResource` test helper --- test/unittest/test_libcyphal.cpp | 2 + test/unittest/tracking_memory_resource.hpp | 65 +++++++++++++++++++ .../transport/can/test_can_msg_rx_session.cpp | 55 ++++++++++++++++ .../transport/can/test_can_transport.cpp | 2 + .../transport/test_dynamic_buffer.cpp | 2 + .../transport/udp/test_udp_transport.cpp | 2 + 6 files changed, 128 insertions(+) create mode 100644 test/unittest/tracking_memory_resource.hpp create mode 100644 test/unittest/transport/can/test_can_msg_rx_session.cpp diff --git a/test/unittest/test_libcyphal.cpp b/test/unittest/test_libcyphal.cpp index 913759561..dacf56ffa 100644 --- a/test/unittest/test_libcyphal.cpp +++ b/test/unittest/test_libcyphal.cpp @@ -13,6 +13,8 @@ namespace { +// MARK: Tests: + // TODO: Add tests here TEST(test_libcyphal, rename_me) {} diff --git a/test/unittest/tracking_memory_resource.hpp b/test/unittest/tracking_memory_resource.hpp new file mode 100644 index 000000000..afb0c075c --- /dev/null +++ b/test/unittest/tracking_memory_resource.hpp @@ -0,0 +1,65 @@ +/// @file +/// libcyphal common header. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#ifndef LIBCYPHAL_TRACKING_MEMORY_RESOURCE_HPP_INCLUDED +#define LIBCYPHAL_TRACKING_MEMORY_RESOURCE_HPP_INCLUDED + +#include + +class TrackingMemoryResource final : public cetl::pmr::memory_resource +{ +public: + std::size_t total_allocated_bytes = 0; + std::size_t total_deallocated_bytes = 0; + +private: + // MARK: cetl::pmr::memory_resource + + void* do_allocate(std::size_t size_bytes, std::size_t alignment) override + { + if (alignment > alignof(std::max_align_t)) + { +#if defined(__cpp_exceptions) + throw std::bad_alloc(); +#endif + return nullptr; + } + + total_allocated_bytes += size_bytes; + return std::malloc(size_bytes); + } + + void do_deallocate(void* p, std::size_t size_bytes, std::size_t) override + { + total_deallocated_bytes += size_bytes; + std::free(p); + } + +#if (__cplusplus < CETL_CPP_STANDARD_17) + + void* do_reallocate(void* ptr, + std::size_t old_size_bytes, + std::size_t new_size_bytes, + std::size_t) override + { + total_allocated_bytes -= old_size_bytes; + total_allocated_bytes += new_size_bytes; + + return std::realloc(ptr, new_size_bytes); + } + +#endif + + bool do_is_equal(const memory_resource& rhs) const noexcept override + { + return (&rhs == this); + } + +}; // TrackingMemoryResource + +#endif // LIBCYPHAL_TRACKING_MEMORY_RESOURCE_HPP_INCLUDED diff --git a/test/unittest/transport/can/test_can_msg_rx_session.cpp b/test/unittest/transport/can/test_can_msg_rx_session.cpp new file mode 100644 index 000000000..53a7acc85 --- /dev/null +++ b/test/unittest/transport/can/test_can_msg_rx_session.cpp @@ -0,0 +1,55 @@ +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#include "media_mock.hpp" +#include "../multiplexer_mock.hpp" +#include "../../tracking_memory_resource.hpp" + +#include +#include + +#include + +namespace +{ +using namespace libcyphal; +using namespace libcyphal::transport; +using namespace libcyphal::transport::can; + +using testing::StrictMock; + +class TestCanMsgRxSession : public testing::Test +{ +protected: + void TearDown() override + { + EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); + } + + CETL_NODISCARD UniquePtr makeTransport() + { + return cetl::get>(can::makeTransport(mr_, mux_mock_, {&media_mock_}, {})); + } + + TrackingMemoryResource mr_; + StrictMock media_mock_{}; + StrictMock mux_mock_{}; +}; + +// MARK: Tests: + +TEST_F(TestCanMsgRxSession, make) +{ + auto transport = makeTransport(); + + auto maybe_rx_session = transport->makeMessageRxSession({42, 123}); + auto session = cetl::get>(std::move(maybe_rx_session)); + EXPECT_TRUE(session); + + EXPECT_EQ(42, session->getParams().extent_bytes); + EXPECT_EQ(123, session->getParams().subject_id); +} + +} // namespace diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index 49f0e02c0..2fcd34fd6 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -28,6 +28,8 @@ class TestCanTransport : public testing::Test cetl::pmr::memory_resource& mr_{*cetl::pmr::new_delete_resource()}; }; +// MARK: Tests: + TEST_F(TestCanTransport, makeTransport_getLocalNodeId) { // Anonymous node diff --git a/test/unittest/transport/test_dynamic_buffer.cpp b/test/unittest/transport/test_dynamic_buffer.cpp index be0d30fcc..4617a5c97 100644 --- a/test/unittest/transport/test_dynamic_buffer.cpp +++ b/test/unittest/transport/test_dynamic_buffer.cpp @@ -84,6 +84,8 @@ class InterfaceWrapper : public rtti_helper interface_mock{}; diff --git a/test/unittest/transport/udp/test_udp_transport.cpp b/test/unittest/transport/udp/test_udp_transport.cpp index 1a443fbc9..5db84d097 100644 --- a/test/unittest/transport/udp/test_udp_transport.cpp +++ b/test/unittest/transport/udp/test_udp_transport.cpp @@ -19,6 +19,8 @@ using namespace libcyphal::transport::udp; using testing::StrictMock; +// MARK: Tests: + TEST(test_udp_transport, makeTransport) { auto mr = cetl::pmr::new_delete_resource(); From dd2e74c01729171077893ca3e389aad69e5a1171 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Wed, 17 Apr 2024 17:17:39 +0300 Subject: [PATCH 08/64] temporary disable memory tracking #verification --- test/unittest/transport/can/test_can_msg_rx_session.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unittest/transport/can/test_can_msg_rx_session.cpp b/test/unittest/transport/can/test_can_msg_rx_session.cpp index 53a7acc85..3d2fe8279 100644 --- a/test/unittest/transport/can/test_can_msg_rx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_rx_session.cpp @@ -25,7 +25,8 @@ class TestCanMsgRxSession : public testing::Test protected: void TearDown() override { - EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); + // TODO: Uncomment this when PMR deleter is fixed. + // EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); } CETL_NODISCARD UniquePtr makeTransport() From ec53ae0252a3ff67982df77d9340de381bf131c1 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Wed, 17 Apr 2024 19:48:09 +0300 Subject: [PATCH 09/64] implemented memory copying #verification --- include/libcyphal/transport/can/delegate.hpp | 13 ++++--- include/libcyphal/transport/can/transport.hpp | 3 +- .../transport/can/test_can_msg_rx_session.cpp | 39 +++++++++++++++++++ 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp index fcd2ce71b..afc2829d4 100644 --- a/include/libcyphal/transport/can/delegate.hpp +++ b/include/libcyphal/transport/can/delegate.hpp @@ -63,12 +63,14 @@ struct TransportDelegate void* const destination, const std::size_t length_bytes) const override { - (void) offset_bytes; - (void) destination; - (void) length_bytes; + if ((destination == nullptr) || (buffer_ == nullptr) || (payload_size_ <= offset_bytes)) + { + return 0; + } - // TODO: Implement this method. - return 0; + const auto bytes_to_copy = std::min(length_bytes, payload_size_ - offset_bytes); + memcpy(destination, static_cast(buffer_) + offset_bytes, bytes_to_copy); + return bytes_to_copy; } private: @@ -84,6 +86,7 @@ struct TransportDelegate : memory_{memory} , canard_instance_(canardInit(canardMemoryAllocate, canardMemoryFree)) { + canard_instance().user_reference = this; } CETL_NODISCARD inline CanardInstance& canard_instance() noexcept diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index 445f2da89..012478b33 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -90,8 +90,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate // TODO: Use it! (void) multiplexer; - canard_instance().user_reference = this; - canard_instance().node_id = canard_node_id; + canard_instance().node_id = canard_node_id; } private: diff --git a/test/unittest/transport/can/test_can_msg_rx_session.cpp b/test/unittest/transport/can/test_can_msg_rx_session.cpp index 3d2fe8279..7ed273f7f 100644 --- a/test/unittest/transport/can/test_can_msg_rx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_rx_session.cpp @@ -10,6 +10,7 @@ #include #include +#include #include namespace @@ -18,8 +19,11 @@ using namespace libcyphal; using namespace libcyphal::transport; using namespace libcyphal::transport::can; +using testing::_; using testing::StrictMock; +using namespace std::chrono_literals; + class TestCanMsgRxSession : public testing::Test { protected: @@ -53,4 +57,39 @@ TEST_F(TestCanMsgRxSession, make) EXPECT_EQ(123, session->getParams().subject_id); } +TEST_F(TestCanMsgRxSession, run_receive) +{ + auto transport = makeTransport(); + + auto maybe_session = transport->makeMessageRxSession({4, 0x23}); + auto session = cetl::get>(std::move(maybe_session)); + EXPECT_TRUE(session); + + EXPECT_CALL(media_mock_, pop(_)).WillOnce([](const cetl::span payload) { + EXPECT_EQ(CANARD_MTU_MAX, payload.size()); + + payload[0] = static_cast(42); + payload[1] = static_cast(147); + payload[2] = static_cast(0xED); + return RxMetadata{TimePoint{10s}, 0x0C002345, 3}; + }); + + transport->run(TimePoint{10s + 2ms}); + + auto maybe_rx_transfer = session->receive(); + EXPECT_TRUE(maybe_rx_transfer.has_value()); + const auto& rx_transfer = maybe_rx_transfer.value(); + + EXPECT_EQ(TimePoint{10s}, rx_transfer.metadata.timestamp); + EXPECT_EQ(0x0D, rx_transfer.metadata.transfer_id); + EXPECT_EQ(Priority::High, rx_transfer.metadata.priority); + EXPECT_EQ(0x45, rx_transfer.metadata.publisher_node_id); + + std::array buffer{}; + EXPECT_EQ(2, rx_transfer.payload.size()); + EXPECT_EQ(2, rx_transfer.payload.copy(0, buffer.data(), buffer.size())); + EXPECT_EQ(42, buffer[0]); + EXPECT_EQ(147, buffer[1]); +} + } // namespace From a34dd903a94fc212bc64aa4a02490121bff9b8c0 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Wed, 17 Apr 2024 21:57:59 +0300 Subject: [PATCH 10/64] more unit tests #verification --- .../transport/can/msg_rx_session.hpp | 4 +- include/libcyphal/transport/defines.hpp | 1 - test/unittest/gtest_helpers.hpp | 53 ++++++++++++ .../transport/can/test_can_msg_rx_session.cpp | 84 ++++++++++++------- .../transport/can/test_can_transport.cpp | 59 ++++++------- .../transport/test_dynamic_buffer.cpp | 15 ++-- .../transport/udp/test_udp_transport.cpp | 11 +-- 7 files changed, 156 insertions(+), 71 deletions(-) create mode 100644 test/unittest/gtest_helpers.hpp diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp index 78c1e282e..47f658df5 100644 --- a/include/libcyphal/transport/can/msg_rx_session.hpp +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -90,7 +90,9 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate CETL_NODISCARD cetl::optional receive() override { - return std::move(last_rx_transfer_); + cetl::optional result{}; + result.swap(last_rx_transfer_); + return result; } // MARK: IRxSession diff --git a/include/libcyphal/transport/defines.hpp b/include/libcyphal/transport/defines.hpp index 5ff50d1df..6cce553f5 100644 --- a/include/libcyphal/transport/defines.hpp +++ b/include/libcyphal/transport/defines.hpp @@ -30,7 +30,6 @@ using TransferId = std::uint64_t; enum class Priority { - Exceptional = 0, Immediate = 1, Fast = 2, diff --git a/test/unittest/gtest_helpers.hpp b/test/unittest/gtest_helpers.hpp new file mode 100644 index 000000000..5819150cc --- /dev/null +++ b/test/unittest/gtest_helpers.hpp @@ -0,0 +1,53 @@ +/// @file +/// libcyphal common header. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#ifndef LIBCYPHAL_GTEST_HELPERS_HPP +#define LIBCYPHAL_GTEST_HELPERS_HPP + +#include +#include + +#include + +namespace libcyphal +{ + +void PrintTo(const Duration duration, std::ostream* os) +{ + auto locale = os->imbue(std::locale("en_US")); + *os << std::chrono::duration_cast(duration).count() << "_us"; + os->imbue(locale); +} + +void PrintTo(const TimePoint time_point, std::ostream* os) +{ + PrintTo(time_point.time_since_epoch(), os); +} + +namespace transport +{ + +void PrintTo(const Priority priority, std::ostream* os) +{ + static constexpr std::array names{ + "Exceptional (0)", + "Immediate (1)", + "Fast (2)", + "High (3)", + "Nominal (4)", + "Low (5)", + "Slow (6)", + "Optional (7)", + }; + *os << names[static_cast(priority)]; +} + +} // namespace transport +} // namespace libcyphal + +#endif // LIBCYPHAL_GTEST_HELPERS_HPP diff --git a/test/unittest/transport/can/test_can_msg_rx_session.cpp b/test/unittest/transport/can/test_can_msg_rx_session.cpp index 7ed273f7f..98b7990dc 100644 --- a/test/unittest/transport/can/test_can_msg_rx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_rx_session.cpp @@ -3,14 +3,13 @@ /// Copyright Amazon.com Inc. or its affiliates. /// SPDX-License-Identifier: MIT +#include + #include "media_mock.hpp" #include "../multiplexer_mock.hpp" +#include "../../gtest_helpers.hpp" #include "../../tracking_memory_resource.hpp" -#include -#include - -#include #include namespace @@ -20,6 +19,10 @@ using namespace libcyphal::transport; using namespace libcyphal::transport::can; using testing::_; +using testing::Eq; +using testing::IsNull; +using testing::NotNull; +using testing::Optional; using testing::StrictMock; using namespace std::chrono_literals; @@ -45,16 +48,19 @@ class TestCanMsgRxSession : public testing::Test // MARK: Tests: -TEST_F(TestCanMsgRxSession, make) +TEST_F(TestCanMsgRxSession, make_setTransferIdTimeout) { auto transport = makeTransport(); auto maybe_rx_session = transport->makeMessageRxSession({42, 123}); auto session = cetl::get>(std::move(maybe_rx_session)); - EXPECT_TRUE(session); + EXPECT_THAT(session, NotNull()); - EXPECT_EQ(42, session->getParams().extent_bytes); - EXPECT_EQ(123, session->getParams().subject_id); + EXPECT_THAT(session->getParams().extent_bytes, Eq(42)); + EXPECT_THAT(session->getParams().subject_id, Eq(123)); + + session->setTransferIdTimeout(0s); + session->setTransferIdTimeout(500ms); } TEST_F(TestCanMsgRxSession, run_receive) @@ -63,33 +69,53 @@ TEST_F(TestCanMsgRxSession, run_receive) auto maybe_session = transport->makeMessageRxSession({4, 0x23}); auto session = cetl::get>(std::move(maybe_session)); - EXPECT_TRUE(session); + EXPECT_THAT(session, NotNull()); + + // 1-st iteration: one frame available @ 1s + { + EXPECT_CALL(media_mock_, pop(_)).WillOnce([](const cetl::span payload) { + EXPECT_THAT(payload.size(), Eq(CANARD_MTU_MAX)); - EXPECT_CALL(media_mock_, pop(_)).WillOnce([](const cetl::span payload) { - EXPECT_EQ(CANARD_MTU_MAX, payload.size()); + payload[0] = static_cast(42); + payload[1] = static_cast(147); + payload[2] = static_cast(0xED); + return RxMetadata{TimePoint{1s}, 0x0C002345, 3}; + }); - payload[0] = static_cast(42); - payload[1] = static_cast(147); - payload[2] = static_cast(0xED); - return RxMetadata{TimePoint{10s}, 0x0C002345, 3}; - }); + transport->run(TimePoint{1s + 10ms}); - transport->run(TimePoint{10s + 2ms}); + session->run(TimePoint{1s + 20ms}); - auto maybe_rx_transfer = session->receive(); - EXPECT_TRUE(maybe_rx_transfer.has_value()); - const auto& rx_transfer = maybe_rx_transfer.value(); + const auto maybe_rx_transfer = session->receive(); + EXPECT_TRUE(maybe_rx_transfer.has_value()); + const auto& rx_transfer = maybe_rx_transfer.value(); - EXPECT_EQ(TimePoint{10s}, rx_transfer.metadata.timestamp); - EXPECT_EQ(0x0D, rx_transfer.metadata.transfer_id); - EXPECT_EQ(Priority::High, rx_transfer.metadata.priority); - EXPECT_EQ(0x45, rx_transfer.metadata.publisher_node_id); + EXPECT_THAT(rx_transfer.metadata.timestamp, Eq(TimePoint{1s})); + EXPECT_THAT(rx_transfer.metadata.transfer_id, Eq(0x0D)); + EXPECT_THAT(rx_transfer.metadata.priority, Eq(Priority::High)); + EXPECT_THAT(rx_transfer.metadata.publisher_node_id, Optional(0x45)); - std::array buffer{}; - EXPECT_EQ(2, rx_transfer.payload.size()); - EXPECT_EQ(2, rx_transfer.payload.copy(0, buffer.data(), buffer.size())); - EXPECT_EQ(42, buffer[0]); - EXPECT_EQ(147, buffer[1]); + std::array buffer{}; + EXPECT_THAT(rx_transfer.payload.size(), Eq(2)); + EXPECT_THAT(rx_transfer.payload.copy(0, buffer.data(), buffer.size()), Eq(2)); + EXPECT_THAT(buffer[0], Eq(42)); + EXPECT_THAT(buffer[1], Eq(147)); + } + + // 2-nd iteration: no frames available + { + EXPECT_CALL(media_mock_, pop(_)).WillOnce([](const cetl::span payload) { + EXPECT_THAT(payload.size(), Eq(CANARD_MTU_MAX)); + return cetl::nullopt; + }); + + transport->run(TimePoint{2s + 10ms}); + + session->run(TimePoint{2s + 20ms}); + + const auto maybe_rx_transfer = session->receive(); + EXPECT_FALSE(maybe_rx_transfer.has_value()); + } } } // namespace diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index 2fcd34fd6..0d322cff4 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -3,11 +3,10 @@ /// Copyright Amazon.com Inc. or its affiliates. /// SPDX-License-Identifier: MIT -#include "media_mock.hpp" -#include "../multiplexer_mock.hpp" #include -#include +#include "media_mock.hpp" +#include "../multiplexer_mock.hpp" #include #include @@ -18,6 +17,10 @@ using namespace libcyphal; using namespace libcyphal::transport; using namespace libcyphal::transport::can; +using testing::Eq; +using testing::IsNull; +using testing::NotNull; +using testing::Optional; using testing::StrictMock; class TestCanTransport : public testing::Test @@ -35,11 +38,11 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) // Anonymous node { auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, {}); - EXPECT_FALSE(cetl::get_if(&maybe_transport)); + EXPECT_THAT(cetl::get_if(&maybe_transport), IsNull()); auto transport = cetl::get>(std::move(maybe_transport)); - EXPECT_TRUE(transport); - EXPECT_EQ(cetl::nullopt, transport->getLocalNodeId()); + EXPECT_THAT(transport, NotNull()); + EXPECT_THAT(transport->getLocalNodeId(), Eq(cetl::nullopt)); } // Node with ID @@ -47,11 +50,11 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) const auto node_id = cetl::make_optional(static_cast(42)); auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, node_id); - EXPECT_FALSE(cetl::get_if(&maybe_transport)); + EXPECT_THAT(cetl::get_if(&maybe_transport), IsNull()); auto transport = cetl::get>(std::move(maybe_transport)); - EXPECT_TRUE(transport); - EXPECT_EQ(42, transport->getLocalNodeId().value()); + EXPECT_THAT(transport, NotNull()); + EXPECT_THAT(transport->getLocalNodeId(), Optional(42)); } // Two media interfaces @@ -59,10 +62,10 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) StrictMock media_mock2; auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_, nullptr, &media_mock2}, {}); - EXPECT_FALSE(cetl::get_if(&maybe_transport)); + EXPECT_THAT(cetl::get_if(&maybe_transport), IsNull()); auto transport = cetl::get>(std::move(maybe_transport)); - EXPECT_TRUE(transport); + EXPECT_THAT(transport, NotNull()); } // All 3 maximum number of media interfaces @@ -70,10 +73,10 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) StrictMock media_mock2{}, media_mock3{}; auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_, &media_mock2, &media_mock3}, {}); - EXPECT_FALSE(cetl::get_if(&maybe_transport)); + EXPECT_THAT(cetl::get_if(&maybe_transport), IsNull()); auto transport = cetl::get>(std::move(maybe_transport)); - EXPECT_TRUE(transport); + EXPECT_THAT(transport, NotNull()); } } @@ -84,7 +87,7 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) const auto node_id = cetl::make_optional(static_cast(CANARD_NODE_ID_MAX)); const auto maybe_transport = makeTransport(mr_, mux_mock_, {}, node_id); - EXPECT_TRUE(cetl::get_if(cetl::get_if(&maybe_transport))); + EXPECT_THAT(cetl::get_if(cetl::get_if(&maybe_transport)), NotNull()); } // try just a bit bigger than max canard id (aka 128) @@ -92,7 +95,7 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) const auto node_id = cetl::make_optional(static_cast(CANARD_NODE_ID_MAX + 1)); const auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, node_id); - EXPECT_TRUE(cetl::get_if(cetl::get_if(&maybe_transport))); + EXPECT_THAT(cetl::get_if(cetl::get_if(&maybe_transport)), NotNull()); } // magic 255 number (aka CANARD_NODE_ID_UNSET) can't be used as well @@ -100,7 +103,7 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) const auto node_id = cetl::make_optional(static_cast(CANARD_NODE_ID_UNSET)); const auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, node_id); - EXPECT_TRUE(cetl::get_if(cetl::get_if(&maybe_transport))); + EXPECT_THAT(cetl::get_if(cetl::get_if(&maybe_transport)), NotNull()); } // just in case try 0x100 (aka overflow) @@ -109,7 +112,7 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) const auto node_id = cetl::make_optional(too_big); const auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, node_id); - EXPECT_TRUE(cetl::get_if(cetl::get_if(&maybe_transport))); + EXPECT_THAT(cetl::get_if(cetl::get_if(&maybe_transport)), NotNull()); } } @@ -124,20 +127,20 @@ TEST_F(TestCanTransport, getProtocolParams) EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_CLASSIC)); auto params = transport->getProtocolParams(); - EXPECT_EQ(1 << CANARD_TRANSFER_ID_BIT_LENGTH, params.transfer_id_modulo); - EXPECT_EQ(CANARD_NODE_ID_MAX + 1, params.max_nodes); - EXPECT_EQ(CANARD_MTU_CAN_CLASSIC, params.mtu_bytes); + EXPECT_THAT(params.transfer_id_modulo, Eq(1 << CANARD_TRANSFER_ID_BIT_LENGTH)); + EXPECT_THAT(params.max_nodes, Eq(CANARD_NODE_ID_MAX + 1)); + EXPECT_THAT(params.mtu_bytes, Eq(CANARD_MTU_CAN_CLASSIC)); // Manipulate MTU values on fly { EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_FD)); - EXPECT_EQ(CANARD_MTU_CAN_FD, transport->getProtocolParams().mtu_bytes); + EXPECT_THAT(transport->getProtocolParams().mtu_bytes, Eq(CANARD_MTU_CAN_FD)); EXPECT_CALL(media_mock_, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_CLASSIC)); - EXPECT_EQ(CANARD_MTU_CAN_CLASSIC, transport->getProtocolParams().mtu_bytes); + EXPECT_THAT(transport->getProtocolParams().mtu_bytes, Eq(CANARD_MTU_CAN_CLASSIC)); EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_CLASSIC)); - EXPECT_EQ(CANARD_MTU_CAN_CLASSIC, transport->getProtocolParams().mtu_bytes); + EXPECT_THAT(transport->getProtocolParams().mtu_bytes, Eq(CANARD_MTU_CAN_CLASSIC)); } } @@ -146,12 +149,12 @@ TEST_F(TestCanTransport, makeMessageRxSession) auto transport = cetl::get>(makeTransport(mr_, mux_mock_, {&media_mock_}, {})); auto maybe_rx_session = transport->makeMessageRxSession({42, 123}); - EXPECT_FALSE(cetl::get_if(&maybe_rx_session)); + EXPECT_THAT(cetl::get_if(&maybe_rx_session), IsNull()); auto session = cetl::get>(std::move(maybe_rx_session)); - EXPECT_TRUE(session); - EXPECT_EQ(42, session->getParams().extent_bytes); - EXPECT_EQ(123, session->getParams().subject_id); + EXPECT_THAT(session, NotNull()); + EXPECT_THAT(session->getParams().extent_bytes, Eq(42)); + EXPECT_THAT(session->getParams().subject_id, Eq(123)); } TEST_F(TestCanTransport, makeMessageRxSession_invalid_subject_id) @@ -159,7 +162,7 @@ TEST_F(TestCanTransport, makeMessageRxSession_invalid_subject_id) auto transport = cetl::get>(makeTransport(mr_, mux_mock_, {&media_mock_}, {})); auto maybe_rx_session = transport->makeMessageRxSession({0, CANARD_SUBJECT_ID_MAX + 1}); - EXPECT_TRUE(cetl::get_if(cetl::get_if(&maybe_rx_session))); + EXPECT_THAT(cetl::get_if(cetl::get_if(&maybe_rx_session)), NotNull()); } } // namespace diff --git a/test/unittest/transport/test_dynamic_buffer.cpp b/test/unittest/transport/test_dynamic_buffer.cpp index 4617a5c97..1bca2ba96 100644 --- a/test/unittest/transport/test_dynamic_buffer.cpp +++ b/test/unittest/transport/test_dynamic_buffer.cpp @@ -14,6 +14,7 @@ using cetl::rtti_helper; using DynamicBuffer = libcyphal::transport::DynamicBuffer; +using testing::Eq; using testing::Return; using testing::StrictMock; @@ -94,15 +95,15 @@ TEST(test_dynamic_buffer, move_ctor_assign_size) EXPECT_CALL(interface_mock, size()).Times(3).WillRepeatedly(Return(42)); { DynamicBuffer src{InterfaceWrapper{&interface_mock}}; //< +1 move - EXPECT_EQ(42, src.size()); + EXPECT_THAT(src.size(), Eq(42)); DynamicBuffer dst{std::move(src)}; //< +2 moves b/c of `cetl::any` specifics (via swap with tmp) - EXPECT_EQ(0, src.size()); - EXPECT_EQ(42, dst.size()); + EXPECT_THAT(src.size(), Eq(0)); + EXPECT_THAT(dst.size(), Eq(42)); src = std::move(dst); //< +2 moves - EXPECT_EQ(42, src.size()); - EXPECT_EQ(0, dst.size()); + EXPECT_THAT(src.size(), Eq(42)); + EXPECT_THAT(dst.size(), Eq(0)); } } @@ -118,11 +119,11 @@ TEST(test_dynamic_buffer, copy_reset) DynamicBuffer buffer{InterfaceWrapper{&interface_mock}}; auto copied_bytes = buffer.copy(13, test_dst.data(), test_dst.size()); - EXPECT_EQ(7, copied_bytes); + EXPECT_THAT(copied_bytes, Eq(7)); buffer.reset(); copied_bytes = buffer.copy(13, test_dst.data(), test_dst.size()); - EXPECT_EQ(0, copied_bytes); + EXPECT_THAT(copied_bytes, Eq(0)); } } diff --git a/test/unittest/transport/udp/test_udp_transport.cpp b/test/unittest/transport/udp/test_udp_transport.cpp index 5db84d097..e2bbb6007 100644 --- a/test/unittest/transport/udp/test_udp_transport.cpp +++ b/test/unittest/transport/udp/test_udp_transport.cpp @@ -3,11 +3,10 @@ /// Copyright Amazon.com Inc. or its affiliates. /// SPDX-License-Identifier: MIT -#include "media_mock.hpp" -#include "../multiplexer_mock.hpp" #include -#include +#include "media_mock.hpp" +#include "../multiplexer_mock.hpp" #include @@ -17,6 +16,8 @@ using namespace libcyphal; using namespace libcyphal::transport; using namespace libcyphal::transport::udp; +using testing::IsNull; +using testing::NotNull; using testing::StrictMock; // MARK: Tests: @@ -31,8 +32,8 @@ TEST(test_udp_transport, makeTransport) { auto maybe_transport = makeTransport(*mr, multiplex_mock, {&media_mock}, {}); - EXPECT_FALSE(cetl::get_if>(&maybe_transport)); - EXPECT_TRUE(cetl::get_if(cetl::get_if(&maybe_transport))); + EXPECT_THAT(cetl::get_if>(&maybe_transport), IsNull()); + EXPECT_THAT(cetl::get_if(cetl::get_if(&maybe_transport)), NotNull()); } } From fdb74805f5f884ba82097c4bf8b1da4e4ea3739d Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Wed, 17 Apr 2024 22:05:43 +0300 Subject: [PATCH 11/64] try fix gcc build #verification --- test/unittest/gtest_helpers.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unittest/gtest_helpers.hpp b/test/unittest/gtest_helpers.hpp index 5819150cc..fb9120351 100644 --- a/test/unittest/gtest_helpers.hpp +++ b/test/unittest/gtest_helpers.hpp @@ -17,14 +17,14 @@ namespace libcyphal { -void PrintTo(const Duration duration, std::ostream* os) +inline void PrintTo(const Duration duration, std::ostream* os) { auto locale = os->imbue(std::locale("en_US")); *os << std::chrono::duration_cast(duration).count() << "_us"; os->imbue(locale); } -void PrintTo(const TimePoint time_point, std::ostream* os) +inline void PrintTo(const TimePoint time_point, std::ostream* os) { PrintTo(time_point.time_since_epoch(), os); } @@ -32,7 +32,7 @@ void PrintTo(const TimePoint time_point, std::ostream* os) namespace transport { -void PrintTo(const Priority priority, std::ostream* os) +inline void PrintTo(const Priority priority, std::ostream* os) { static constexpr std::array names{ "Exceptional (0)", From 478e36df42db81feb058480b21fd55e5906a4460 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 18 Apr 2024 09:30:42 +0300 Subject: [PATCH 12/64] #verification --- test/unittest/gtest_helpers.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unittest/gtest_helpers.hpp b/test/unittest/gtest_helpers.hpp index fb9120351..14a18c809 100644 --- a/test/unittest/gtest_helpers.hpp +++ b/test/unittest/gtest_helpers.hpp @@ -42,7 +42,7 @@ inline void PrintTo(const Priority priority, std::ostream* os) "Nominal (4)", "Low (5)", "Slow (6)", - "Optional (7)", + "Optional (7)", }; *os << names[static_cast(priority)]; } From 34b6422c57c732f447ac0d4787871eab90dbbc6e Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 18 Apr 2024 09:31:40 +0300 Subject: [PATCH 13/64] #verification --- test/unittest/gtest_helpers.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unittest/gtest_helpers.hpp b/test/unittest/gtest_helpers.hpp index 14a18c809..fb9120351 100644 --- a/test/unittest/gtest_helpers.hpp +++ b/test/unittest/gtest_helpers.hpp @@ -42,7 +42,7 @@ inline void PrintTo(const Priority priority, std::ostream* os) "Nominal (4)", "Low (5)", "Slow (6)", - "Optional (7)", + "Optional (7)", }; *os << names[static_cast(priority)]; } From d318b1e0fe7fd58086a79f3ab63c02b7cd053986 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 18 Apr 2024 13:11:41 +0300 Subject: [PATCH 14/64] cover delegate with unit tests #verification --- include/libcyphal/transport/can/delegate.hpp | 15 +- .../transport/can/test_can_delegate.cpp | 167 ++++++++++++++++++ .../transport/can/test_can_msg_rx_session.cpp | 17 +- .../transport/can/test_can_transport.cpp | 42 ++--- .../transport/udp/test_udp_transport.cpp | 35 ++-- 5 files changed, 237 insertions(+), 39 deletions(-) create mode 100644 test/unittest/transport/can/test_can_delegate.cpp diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp index afc2829d4..4324a9bbc 100644 --- a/include/libcyphal/transport/can/delegate.hpp +++ b/include/libcyphal/transport/can/delegate.hpp @@ -6,6 +6,11 @@ #ifndef LIBCYPHAL_TRANSPORT_CAN_DELEGATE_HPP_INCLUDED #define LIBCYPHAL_TRANSPORT_CAN_DELEGATE_HPP_INCLUDED +#include +#include + +#include + #include namespace libcyphal @@ -63,6 +68,9 @@ struct TransportDelegate void* const destination, const std::size_t length_bytes) const override { + CETL_DEBUG_ASSERT((destination != nullptr) || (length_bytes == 0), + "Destination could be null only with zero bytes ask."); + if ((destination == nullptr) || (buffer_ == nullptr) || (payload_size_ <= offset_bytes)) { return 0; @@ -106,11 +114,14 @@ struct TransportDelegate static cetl::optional anyErrorFromCanard(const int8_t result) { - if (result == CANARD_ERROR_INVALID_ARGUMENT) + // Canard error results are negative, so we need to negate them to get the error code. + const auto canard_error = static_cast(-result); + + if (canard_error == CANARD_ERROR_INVALID_ARGUMENT) { return ArgumentError{}; } - if (result == CANARD_ERROR_OUT_OF_MEMORY) + if (canard_error == CANARD_ERROR_OUT_OF_MEMORY) { return MemoryError{}; } diff --git a/test/unittest/transport/can/test_can_delegate.cpp b/test/unittest/transport/can/test_can_delegate.cpp new file mode 100644 index 000000000..8585db85a --- /dev/null +++ b/test/unittest/transport/can/test_can_delegate.cpp @@ -0,0 +1,167 @@ +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#include + +#include "../../tracking_memory_resource.hpp" + +#include +#include + +namespace +{ +using namespace libcyphal::transport; +using namespace libcyphal::transport::can; + +using testing::_; +using testing::Eq; +using testing::Each; +using testing::IsNull; +using testing::Return; +using testing::Optional; +using testing::StrictMock; +using testing::ElementsAre; +using testing::VariantWith; + +class TestCanDelegate : public testing::Test +{ +protected: + void TearDown() override + { + EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); + } + + // MARK: Data members: + + TrackingMemoryResource mr_; +}; + +// MARK: Tests: + +TEST_F(TestCanDelegate, CanardMemory_copy) +{ + using CanardMemory = detail::TransportDelegate::CanardMemory; + + detail::TransportDelegate delegate{mr_}; + auto& canard_instance = delegate.canard_instance(); + + const auto payload = static_cast(canard_instance.memory_allocate(&canard_instance, 8)); + std::iota(payload, payload + 8, '0'); + + const std::size_t payload_size = 4; + const CanardMemory canard_memory{delegate, payload, payload_size}; + EXPECT_THAT(canard_memory.size(), Eq(payload_size)); + + // Ask exactly as payload + { + const std::size_t ask_size = payload_size; + std::array buffer{}; + + EXPECT_THAT(canard_memory.copy(0, buffer.data(), ask_size), Eq(ask_size)); + EXPECT_THAT(buffer, ElementsAre('0', '1', '2', '3')); + } + + // Ask more than payload + { + const std::size_t ask_size = payload_size + 2; + std::array buffer{}; + + EXPECT_THAT(canard_memory.copy(0, buffer.data(), ask_size), Eq(payload_size)); + EXPECT_THAT(buffer, ElementsAre('0', '1', '2', '3', '\0', '\0')); + } + + // Ask less than payload (with different offsets) + { + const std::size_t ask_size = payload_size - 2; + std::array buffer{}; + + EXPECT_THAT(canard_memory.copy(0, buffer.data(), ask_size), Eq(ask_size)); + EXPECT_THAT(buffer, ElementsAre('0', '1')); + + EXPECT_THAT(canard_memory.copy(3, buffer.data(), buffer.size()), Eq(1)); + EXPECT_THAT(buffer, ElementsAre('3', '1')); + + EXPECT_THAT(canard_memory.copy(2, buffer.data(), ask_size), Eq(ask_size)); + EXPECT_THAT(buffer, ElementsAre('2', '3')); + + EXPECT_THAT(canard_memory.copy(payload_size, buffer.data(), ask_size), Eq(0)); + EXPECT_THAT(buffer, ElementsAre('2', '3')); + + // Ask nothing + EXPECT_THAT(canard_memory.copy(0, buffer.data(), 0), Eq(0)); + EXPECT_THAT(buffer, ElementsAre('2', '3')); + + // No output buffer + EXPECT_THAT(canard_memory.copy(0, nullptr, 0), Eq(0)); + } +} + +TEST_F(TestCanDelegate, CanardMemory_copy_on_moved) +{ + using CanardMemory = detail::TransportDelegate::CanardMemory; + + detail::TransportDelegate delegate{mr_}; + auto& canard_instance = delegate.canard_instance(); + + const std::size_t payload_size = 4; + const auto payload = static_cast(canard_instance.memory_allocate(&canard_instance, payload_size)); + std::iota(payload, payload + payload_size, '0'); + + CanardMemory old_canard_memory{delegate, payload, payload_size}; + EXPECT_THAT(old_canard_memory.size(), Eq(payload_size)); + + CanardMemory new_canard_memory{std::move(old_canard_memory)}; + EXPECT_THAT(old_canard_memory.size(), Eq(0)); + EXPECT_THAT(new_canard_memory.size(), Eq(payload_size)); + + // Try old one + { + std::array buffer{}; + EXPECT_THAT(old_canard_memory.copy(0, buffer.data(), buffer.size()), Eq(0)); + EXPECT_THAT(buffer, Each('\0')); + } + + // Try new one + { + std::array buffer{}; + EXPECT_THAT(new_canard_memory.copy(0, buffer.data(), buffer.size()), Eq(payload_size)); + EXPECT_THAT(buffer, ElementsAre('0', '1', '2', '3')); + } +} + +TEST_F(TestCanDelegate, anyErrorFromCanard) +{ + EXPECT_THAT(detail::TransportDelegate::anyErrorFromCanard(-CANARD_ERROR_OUT_OF_MEMORY), + Optional(VariantWith(_))); + + EXPECT_THAT(detail::TransportDelegate::anyErrorFromCanard(-CANARD_ERROR_INVALID_ARGUMENT), + Optional(VariantWith(_))); + + EXPECT_THAT(detail::TransportDelegate::anyErrorFromCanard(0), Eq(cetl::nullopt)); + EXPECT_THAT(detail::TransportDelegate::anyErrorFromCanard(1), Eq(cetl::nullopt)); + EXPECT_THAT(detail::TransportDelegate::anyErrorFromCanard(-1), Eq(cetl::nullopt)); +} + +TEST_F(TestCanDelegate, canardMemoryAllocate_no_memory) +{ + class NoMemoryResource final : public cetl::pmr::memory_resource + { + public: + MOCK_METHOD(void*, do_allocate, (std::size_t, std::size_t), (override)); + MOCK_METHOD(void, do_deallocate, (void*, std::size_t, std::size_t), (override)); + MOCK_METHOD(bool, do_is_equal, (const memory_resource&), (const, noexcept, override)); + MOCK_METHOD(std::size_t, do_max_size, (), (const, noexcept, override)); + MOCK_METHOD(void*, do_reallocate, (void*, std::size_t, std::size_t, std::size_t)); + }; + NoMemoryResource no_memory_resource; + + detail::TransportDelegate delegate{no_memory_resource}; + auto& canard_instance = delegate.canard_instance(); + + EXPECT_CALL(no_memory_resource, do_allocate(_, _)).WillOnce(Return(nullptr)); + EXPECT_THAT(canard_instance.memory_allocate(&canard_instance, 1), IsNull()); +} + +} // namespace diff --git a/test/unittest/transport/can/test_can_msg_rx_session.cpp b/test/unittest/transport/can/test_can_msg_rx_session.cpp index 98b7990dc..df358a09d 100644 --- a/test/unittest/transport/can/test_can_msg_rx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_rx_session.cpp @@ -24,6 +24,7 @@ using testing::IsNull; using testing::NotNull; using testing::Optional; using testing::StrictMock; +using testing::VariantWith; using namespace std::chrono_literals; @@ -38,9 +39,13 @@ class TestCanMsgRxSession : public testing::Test CETL_NODISCARD UniquePtr makeTransport() { - return cetl::get>(can::makeTransport(mr_, mux_mock_, {&media_mock_}, {})); + auto maybe_transport = can::makeTransport(mr_, mux_mock_, {&media_mock_}, {}); + EXPECT_THAT(maybe_transport, VariantWith>(_)); + return cetl::get>(std::move(maybe_transport)); } + // MARK: Data members: + TrackingMemoryResource mr_; StrictMock media_mock_{}; StrictMock mux_mock_{}; @@ -52,8 +57,9 @@ TEST_F(TestCanMsgRxSession, make_setTransferIdTimeout) { auto transport = makeTransport(); - auto maybe_rx_session = transport->makeMessageRxSession({42, 123}); - auto session = cetl::get>(std::move(maybe_rx_session)); + auto maybe_session = transport->makeMessageRxSession({42, 123}); + EXPECT_THAT(maybe_session, VariantWith>(_)); + auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); EXPECT_THAT(session->getParams().extent_bytes, Eq(42)); @@ -68,6 +74,7 @@ TEST_F(TestCanMsgRxSession, run_receive) auto transport = makeTransport(); auto maybe_session = transport->makeMessageRxSession({4, 0x23}); + EXPECT_THAT(maybe_session, VariantWith>(_)); auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); @@ -87,7 +94,7 @@ TEST_F(TestCanMsgRxSession, run_receive) session->run(TimePoint{1s + 20ms}); const auto maybe_rx_transfer = session->receive(); - EXPECT_TRUE(maybe_rx_transfer.has_value()); + EXPECT_THAT(maybe_rx_transfer, Optional(_)); const auto& rx_transfer = maybe_rx_transfer.value(); EXPECT_THAT(rx_transfer.metadata.timestamp, Eq(TimePoint{1s})); @@ -114,7 +121,7 @@ TEST_F(TestCanMsgRxSession, run_receive) session->run(TimePoint{2s + 20ms}); const auto maybe_rx_transfer = session->receive(); - EXPECT_FALSE(maybe_rx_transfer.has_value()); + EXPECT_THAT(maybe_rx_transfer, Eq(cetl::nullopt)); } } diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index 0d322cff4..ef73475f4 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -7,6 +7,7 @@ #include "media_mock.hpp" #include "../multiplexer_mock.hpp" +#include "../../tracking_memory_resource.hpp" #include #include @@ -17,18 +18,28 @@ using namespace libcyphal; using namespace libcyphal::transport; using namespace libcyphal::transport::can; +using testing::_; using testing::Eq; using testing::IsNull; using testing::NotNull; using testing::Optional; using testing::StrictMock; +using testing::VariantWith; class TestCanTransport : public testing::Test { protected: + void TearDown() override + { + // TODO: Uncomment this when PMR deleter is fixed. + // EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); + } + + // MARK: Data members: + + TrackingMemoryResource mr_; StrictMock media_mock_{}; StrictMock mux_mock_{}; - cetl::pmr::memory_resource& mr_{*cetl::pmr::new_delete_resource()}; }; // MARK: Tests: @@ -38,10 +49,9 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) // Anonymous node { auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, {}); - EXPECT_THAT(cetl::get_if(&maybe_transport), IsNull()); + EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); auto transport = cetl::get>(std::move(maybe_transport)); - EXPECT_THAT(transport, NotNull()); EXPECT_THAT(transport->getLocalNodeId(), Eq(cetl::nullopt)); } @@ -50,10 +60,9 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) const auto node_id = cetl::make_optional(static_cast(42)); auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, node_id); - EXPECT_THAT(cetl::get_if(&maybe_transport), IsNull()); + EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); auto transport = cetl::get>(std::move(maybe_transport)); - EXPECT_THAT(transport, NotNull()); EXPECT_THAT(transport->getLocalNodeId(), Optional(42)); } @@ -62,10 +71,7 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) StrictMock media_mock2; auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_, nullptr, &media_mock2}, {}); - EXPECT_THAT(cetl::get_if(&maybe_transport), IsNull()); - - auto transport = cetl::get>(std::move(maybe_transport)); - EXPECT_THAT(transport, NotNull()); + EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); } // All 3 maximum number of media interfaces @@ -73,10 +79,7 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) StrictMock media_mock2{}, media_mock3{}; auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_, &media_mock2, &media_mock3}, {}); - EXPECT_THAT(cetl::get_if(&maybe_transport), IsNull()); - - auto transport = cetl::get>(std::move(maybe_transport)); - EXPECT_THAT(transport, NotNull()); + EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); } } @@ -87,7 +90,7 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) const auto node_id = cetl::make_optional(static_cast(CANARD_NODE_ID_MAX)); const auto maybe_transport = makeTransport(mr_, mux_mock_, {}, node_id); - EXPECT_THAT(cetl::get_if(cetl::get_if(&maybe_transport)), NotNull()); + EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } // try just a bit bigger than max canard id (aka 128) @@ -95,7 +98,7 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) const auto node_id = cetl::make_optional(static_cast(CANARD_NODE_ID_MAX + 1)); const auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, node_id); - EXPECT_THAT(cetl::get_if(cetl::get_if(&maybe_transport)), NotNull()); + EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } // magic 255 number (aka CANARD_NODE_ID_UNSET) can't be used as well @@ -103,7 +106,7 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) const auto node_id = cetl::make_optional(static_cast(CANARD_NODE_ID_UNSET)); const auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, node_id); - EXPECT_THAT(cetl::get_if(cetl::get_if(&maybe_transport)), NotNull()); + EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } // just in case try 0x100 (aka overflow) @@ -112,7 +115,7 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) const auto node_id = cetl::make_optional(too_big); const auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, node_id); - EXPECT_THAT(cetl::get_if(cetl::get_if(&maybe_transport)), NotNull()); + EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } } @@ -149,10 +152,9 @@ TEST_F(TestCanTransport, makeMessageRxSession) auto transport = cetl::get>(makeTransport(mr_, mux_mock_, {&media_mock_}, {})); auto maybe_rx_session = transport->makeMessageRxSession({42, 123}); - EXPECT_THAT(cetl::get_if(&maybe_rx_session), IsNull()); + EXPECT_THAT(maybe_rx_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_rx_session)); - EXPECT_THAT(session, NotNull()); EXPECT_THAT(session->getParams().extent_bytes, Eq(42)); EXPECT_THAT(session->getParams().subject_id, Eq(123)); } @@ -162,7 +164,7 @@ TEST_F(TestCanTransport, makeMessageRxSession_invalid_subject_id) auto transport = cetl::get>(makeTransport(mr_, mux_mock_, {&media_mock_}, {})); auto maybe_rx_session = transport->makeMessageRxSession({0, CANARD_SUBJECT_ID_MAX + 1}); - EXPECT_THAT(cetl::get_if(cetl::get_if(&maybe_rx_session)), NotNull()); + EXPECT_THAT(maybe_rx_session, VariantWith(VariantWith(_))); } } // namespace diff --git a/test/unittest/transport/udp/test_udp_transport.cpp b/test/unittest/transport/udp/test_udp_transport.cpp index e2bbb6007..e019dbc41 100644 --- a/test/unittest/transport/udp/test_udp_transport.cpp +++ b/test/unittest/transport/udp/test_udp_transport.cpp @@ -7,6 +7,7 @@ #include "media_mock.hpp" #include "../multiplexer_mock.hpp" +#include "../../tracking_memory_resource.hpp" #include @@ -16,24 +17,34 @@ using namespace libcyphal; using namespace libcyphal::transport; using namespace libcyphal::transport::udp; -using testing::IsNull; -using testing::NotNull; +using testing::_; using testing::StrictMock; +using testing::VariantWith; -// MARK: Tests: - -TEST(test_udp_transport, makeTransport) +class TestUpdTransport : public testing::Test { - auto mr = cetl::pmr::new_delete_resource(); +protected: + void TearDown() override + { + // TODO: Uncomment this when PMR deleter is fixed. + // EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); + } - StrictMock media_mock{}; - StrictMock multiplex_mock{}; + // MARK: Data members: - { - auto maybe_transport = makeTransport(*mr, multiplex_mock, {&media_mock}, {}); + TrackingMemoryResource mr_; + StrictMock media_mock_{}; + StrictMock mux_mock_{}; +}; - EXPECT_THAT(cetl::get_if>(&maybe_transport), IsNull()); - EXPECT_THAT(cetl::get_if(cetl::get_if(&maybe_transport)), NotNull()); +// MARK: Tests: + +TEST_F(TestUpdTransport, makeTransport) +{ + // Anonymous node + { + auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, {}); + EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } } From 7b729e10727fbfe3acce4ef6cdd1591fade67e8d Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 18 Apr 2024 16:01:33 +0300 Subject: [PATCH 15/64] more unit tests to cover memory errors #verification --- .github/workflows/tests.yml | 2 +- .../transport/can/msg_rx_session.hpp | 4 ++ include/libcyphal/transport/can/transport.hpp | 18 ++++--- test/unittest/memory_resource_mock.hpp | 54 +++++++++++++++++++ .../transport/can/test_can_delegate.cpp | 20 +++---- .../transport/can/test_can_msg_rx_session.cpp | 28 +++++++--- .../transport/can/test_can_transport.cpp | 14 +++++ 7 files changed, 114 insertions(+), 26 deletions(-) create mode 100644 test/unittest/memory_resource_mock.hpp diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9bfd0d0eb..063e8b7cf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -77,7 +77,7 @@ jobs: run: > ./build-tools/bin/verify.py --verbose - --asserts + ${{ matrix.build_flavor != 'Coverage' && '--asserts' || '' }} --cpp-standard ${{ matrix.std }} --build-flavor ${{ matrix.build_flavor }} --toolchain ${{ matrix.toolchain }} diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp index 47f658df5..bd2174fed 100644 --- a/include/libcyphal/transport/can/msg_rx_session.hpp +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -42,6 +42,10 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate { return any_error.value(); } + if (session == nullptr) + { + return MemoryError{}; + } return session; } diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index 012478b33..be09a312a 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -71,12 +71,18 @@ class TransportImpl final : public ICanTransport, private TransportDelegate const auto canard_node_id = static_cast(local_node_id.value_or(CANARD_NODE_ID_UNSET)); - return libcyphal::detail::makeUniquePtr(memory, - Tag{}, - memory, - multiplexer, - std::move(media_array), - canard_node_id); + auto transport = libcyphal::detail::makeUniquePtr(memory, + Tag{}, + memory, + multiplexer, + std::move(media_array), + canard_node_id); + if (transport == nullptr) + { + return MemoryError{}; + } + + return transport; } TransportImpl(Tag, diff --git a/test/unittest/memory_resource_mock.hpp b/test/unittest/memory_resource_mock.hpp new file mode 100644 index 000000000..29fc4eefb --- /dev/null +++ b/test/unittest/memory_resource_mock.hpp @@ -0,0 +1,54 @@ +/// @file +/// libcyphal common header. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#ifndef LIBCYPHAL_MEMORY_RESOURCE_MOCK_HPP_INCLUDED +#define LIBCYPHAL_MEMORY_RESOURCE_MOCK_HPP_INCLUDED + +#include + +#include + +class MemoryResourceMock : public cetl::pmr::memory_resource +{ +public: + MOCK_METHOD(void*, do_allocate, (std::size_t, std::size_t)); + MOCK_METHOD(std::size_t, do_max_size, (), (const, noexcept)); + MOCK_METHOD(void, do_deallocate, (void*, std::size_t, std::size_t)); + MOCK_METHOD(bool, do_is_equal, (const memory_resource&), (const, noexcept)); + MOCK_METHOD(void*, do_reallocate, (void*, std::size_t, std::size_t, std::size_t)); + + void redirectExpectedCallsTo(cetl::pmr::memory_resource& mr) + { + using ::testing::_; + + EXPECT_CALL(*this, do_allocate(_, _)) + .WillRepeatedly([&mr](std::size_t size_bytes, std::size_t alignment) -> void* { + return mr.allocate(size_bytes, alignment); + }); + + EXPECT_CALL(*this, do_max_size()).WillRepeatedly([&mr]() { return mr.max_size(); }); + + EXPECT_CALL(*this, do_deallocate(_, _, _)) + .WillRepeatedly([&mr](void* p, std::size_t size_bytes, std::size_t alignment) { + mr.deallocate(p, size_bytes, alignment); + }); + + EXPECT_CALL(*this, do_is_equal(_)).WillRepeatedly([&mr](const memory_resource& rhs) { + return mr.is_equal(rhs); + }); + + EXPECT_CALL(*this, do_reallocate(_, _, _, _)) + .WillRepeatedly( + [&mr](void* ptr, std::size_t old_size_bytes, std::size_t new_size_bytes, std::size_t alignment) { + return mr.reallocate(ptr, old_size_bytes, new_size_bytes, alignment); + }); + } + +}; // MemoryResourceMock + +#endif // LIBCYPHAL_MEMORY_RESOURCE_MOCK_HPP_INCLUDED diff --git a/test/unittest/transport/can/test_can_delegate.cpp b/test/unittest/transport/can/test_can_delegate.cpp index 8585db85a..fda724897 100644 --- a/test/unittest/transport/can/test_can_delegate.cpp +++ b/test/unittest/transport/can/test_can_delegate.cpp @@ -5,6 +5,7 @@ #include +#include "../../memory_resource_mock.hpp" #include "../../tracking_memory_resource.hpp" #include @@ -146,21 +147,14 @@ TEST_F(TestCanDelegate, anyErrorFromCanard) TEST_F(TestCanDelegate, canardMemoryAllocate_no_memory) { - class NoMemoryResource final : public cetl::pmr::memory_resource - { - public: - MOCK_METHOD(void*, do_allocate, (std::size_t, std::size_t), (override)); - MOCK_METHOD(void, do_deallocate, (void*, std::size_t, std::size_t), (override)); - MOCK_METHOD(bool, do_is_equal, (const memory_resource&), (const, noexcept, override)); - MOCK_METHOD(std::size_t, do_max_size, (), (const, noexcept, override)); - MOCK_METHOD(void*, do_reallocate, (void*, std::size_t, std::size_t, std::size_t)); - }; - NoMemoryResource no_memory_resource; - - detail::TransportDelegate delegate{no_memory_resource}; + StrictMock mr_mock{}; + + detail::TransportDelegate delegate{mr_mock}; auto& canard_instance = delegate.canard_instance(); - EXPECT_CALL(no_memory_resource, do_allocate(_, _)).WillOnce(Return(nullptr)); + // Emulate that there is no memory at all. + EXPECT_CALL(mr_mock, do_allocate(_, _)).WillOnce(Return(nullptr)); + EXPECT_THAT(canard_instance.memory_allocate(&canard_instance, 1), IsNull()); } diff --git a/test/unittest/transport/can/test_can_msg_rx_session.cpp b/test/unittest/transport/can/test_can_msg_rx_session.cpp index df358a09d..b2c673aa7 100644 --- a/test/unittest/transport/can/test_can_msg_rx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_rx_session.cpp @@ -8,6 +8,7 @@ #include "media_mock.hpp" #include "../multiplexer_mock.hpp" #include "../../gtest_helpers.hpp" +#include "../../memory_resource_mock.hpp" #include "../../tracking_memory_resource.hpp" #include @@ -20,6 +21,7 @@ using namespace libcyphal::transport::can; using testing::_; using testing::Eq; +using testing::Return; using testing::IsNull; using testing::NotNull; using testing::Optional; @@ -37,10 +39,10 @@ class TestCanMsgRxSession : public testing::Test // EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); } - CETL_NODISCARD UniquePtr makeTransport() + CETL_NODISCARD UniquePtr makeTransport(cetl::pmr::memory_resource& mr) { - auto maybe_transport = can::makeTransport(mr_, mux_mock_, {&media_mock_}, {}); - EXPECT_THAT(maybe_transport, VariantWith>(_)); + auto maybe_transport = can::makeTransport(mr, mux_mock_, {&media_mock_}, {}); + EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); return cetl::get>(std::move(maybe_transport)); } @@ -55,7 +57,7 @@ class TestCanMsgRxSession : public testing::Test TEST_F(TestCanMsgRxSession, make_setTransferIdTimeout) { - auto transport = makeTransport(); + auto transport = makeTransport(mr_); auto maybe_session = transport->makeMessageRxSession({42, 123}); EXPECT_THAT(maybe_session, VariantWith>(_)); @@ -69,13 +71,27 @@ TEST_F(TestCanMsgRxSession, make_setTransferIdTimeout) session->setTransferIdTimeout(500ms); } +TEST_F(TestCanMsgRxSession, make_no_memory) +{ + StrictMock mr_mock{}; + mr_mock.redirectExpectedCallsTo(mr_); + + // Emulate that there is no memory available for the message session. + EXPECT_CALL(mr_mock, do_allocate(sizeof(can::detail::MessageRxSession), _)).WillOnce(Return(nullptr)); + + auto transport = makeTransport(mr_mock); + + auto maybe_session = transport->makeMessageRxSession({64, 0x23}); + EXPECT_THAT(maybe_session, VariantWith(VariantWith(_))); +} + TEST_F(TestCanMsgRxSession, run_receive) { - auto transport = makeTransport(); + auto transport = makeTransport(mr_); auto maybe_session = transport->makeMessageRxSession({4, 0x23}); EXPECT_THAT(maybe_session, VariantWith>(_)); - auto session = cetl::get>(std::move(maybe_session)); + auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); // 1-st iteration: one frame available @ 1s diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index ef73475f4..7964a5f01 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -7,6 +7,7 @@ #include "media_mock.hpp" #include "../multiplexer_mock.hpp" +#include "../../memory_resource_mock.hpp" #include "../../tracking_memory_resource.hpp" #include @@ -20,6 +21,7 @@ using namespace libcyphal::transport::can; using testing::_; using testing::Eq; +using testing::Return; using testing::IsNull; using testing::NotNull; using testing::Optional; @@ -44,6 +46,18 @@ class TestCanTransport : public testing::Test // MARK: Tests: +TEST_F(TestCanTransport, makeTransport_no_memory) +{ + StrictMock mr_mock{}; + mr_mock.redirectExpectedCallsTo(mr_); + + // Emulate that there is no memory available for the transport. + EXPECT_CALL(mr_mock, do_allocate(sizeof(can::detail::TransportImpl), _)).WillOnce(Return(nullptr)); + + auto maybe_transport = makeTransport(mr_mock, mux_mock_, {&media_mock_}, {}); + EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); +} + TEST_F(TestCanTransport, makeTransport_getLocalNodeId) { // Anonymous node From b92db328d2942a7e2c2158a994a16449bfc3c7f3 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 18 Apr 2024 16:17:40 +0300 Subject: [PATCH 16/64] fix unit tests on cpp17+ #verification --- test/unittest/memory_resource_mock.hpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/unittest/memory_resource_mock.hpp b/test/unittest/memory_resource_mock.hpp index 29fc4eefb..3e3781a4f 100644 --- a/test/unittest/memory_resource_mock.hpp +++ b/test/unittest/memory_resource_mock.hpp @@ -17,10 +17,13 @@ class MemoryResourceMock : public cetl::pmr::memory_resource { public: MOCK_METHOD(void*, do_allocate, (std::size_t, std::size_t)); - MOCK_METHOD(std::size_t, do_max_size, (), (const, noexcept)); MOCK_METHOD(void, do_deallocate, (void*, std::size_t, std::size_t)); MOCK_METHOD(bool, do_is_equal, (const memory_resource&), (const, noexcept)); + +#if (__cplusplus < CETL_CPP_STANDARD_17) + MOCK_METHOD(std::size_t, do_max_size, (), (const, noexcept)); MOCK_METHOD(void*, do_reallocate, (void*, std::size_t, std::size_t, std::size_t)); +#endif void redirectExpectedCallsTo(cetl::pmr::memory_resource& mr) { @@ -30,23 +33,22 @@ class MemoryResourceMock : public cetl::pmr::memory_resource .WillRepeatedly([&mr](std::size_t size_bytes, std::size_t alignment) -> void* { return mr.allocate(size_bytes, alignment); }); - - EXPECT_CALL(*this, do_max_size()).WillRepeatedly([&mr]() { return mr.max_size(); }); - EXPECT_CALL(*this, do_deallocate(_, _, _)) .WillRepeatedly([&mr](void* p, std::size_t size_bytes, std::size_t alignment) { mr.deallocate(p, size_bytes, alignment); }); - EXPECT_CALL(*this, do_is_equal(_)).WillRepeatedly([&mr](const memory_resource& rhs) { return mr.is_equal(rhs); }); +#if (__cplusplus < CETL_CPP_STANDARD_17) + EXPECT_CALL(*this, do_max_size()).WillRepeatedly([&mr]() { return mr.max_size(); }); EXPECT_CALL(*this, do_reallocate(_, _, _, _)) .WillRepeatedly( [&mr](void* ptr, std::size_t old_size_bytes, std::size_t new_size_bytes, std::size_t alignment) { return mr.reallocate(ptr, old_size_bytes, new_size_bytes, alignment); }); +#endif } }; // MemoryResourceMock From 4952f811e73d1f65a256a13830617eba23f39157 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Fri, 19 Apr 2024 13:22:15 +0300 Subject: [PATCH 17/64] try to fix seg fault on release #verification --- cmake/compiler_flag_sets/default.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/compiler_flag_sets/default.cmake b/cmake/compiler_flag_sets/default.cmake index 1598097cc..fb7307efa 100644 --- a/cmake/compiler_flag_sets/default.cmake +++ b/cmake/compiler_flag_sets/default.cmake @@ -53,6 +53,7 @@ if (CMAKE_BUILD_TYPE STREQUAL "Release") message(DEBUG "Release build. Setting optimization flags.") list(APPEND C_FLAG_SET "-O1" + "-fno-delete-null-pointer-checks" ) else() From 6e1c79b1b792d18db86598d8b5f92946b6e85645 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Fri, 19 Apr 2024 18:23:25 +0300 Subject: [PATCH 18/64] first draft of can msg tx session --- .../transport/can/msg_rx_session.hpp | 2 +- .../transport/can/msg_tx_session.hpp | 86 +++++++++++++++++ include/libcyphal/transport/can/transport.hpp | 92 +++++++++++++------ .../transport/can/test_can_msg_rx_session.cpp | 7 +- .../transport/can/test_can_msg_tx_session.cpp | 75 +++++++++++++++ .../transport/can/test_can_transport.cpp | 33 ++++--- 6 files changed, 253 insertions(+), 42 deletions(-) create mode 100644 include/libcyphal/transport/can/msg_tx_session.hpp create mode 100644 test/unittest/transport/can/test_can_msg_tx_session.cpp diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp index bd2174fed..18975f893 100644 --- a/include/libcyphal/transport/can/msg_rx_session.hpp +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -148,4 +148,4 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate } // namespace transport } // namespace libcyphal -#endif // LIBCYPHAL_TRANSPORT_CAN_MSG_RX_SESSION_HPP_INCLUDED \ No newline at end of file +#endif // LIBCYPHAL_TRANSPORT_CAN_MSG_RX_SESSION_HPP_INCLUDED diff --git a/include/libcyphal/transport/can/msg_tx_session.hpp b/include/libcyphal/transport/can/msg_tx_session.hpp new file mode 100644 index 000000000..a0626d903 --- /dev/null +++ b/include/libcyphal/transport/can/msg_tx_session.hpp @@ -0,0 +1,86 @@ +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#ifndef LIBCYPHAL_TRANSPORT_CAN_MSG_TX_SESSION_HPP_INCLUDED +#define LIBCYPHAL_TRANSPORT_CAN_MSG_TX_SESSION_HPP_INCLUDED + +#include "delegate.hpp" +#include "libcyphal/transport/msg_sessions.hpp" + +#include + +namespace libcyphal +{ +namespace transport +{ +namespace can +{ +namespace detail +{ +class MessageTxSession final : public IMessageTxSession +{ + // In use to disable public construction. + // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ + struct Tag + { + explicit Tag() = default; + using Interface = IMessageTxSession; + using Concrete = MessageTxSession; + }; + +public: + CETL_NODISCARD static Expected, AnyError> make(TransportDelegate& delegate, + const MessageTxParams& params) + { + auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Tag{}, delegate, params); + if (session == nullptr) + { + return MemoryError{}; + } + + return session; + } + + MessageTxSession(Tag, TransportDelegate& delegate, const MessageTxParams& params) + : delegate_{delegate} + , params_{params} + { + } + +private: + // MARK: IMessageTxSession + + CETL_NODISCARD MessageTxParams getParams() const noexcept override + { + return params_; + } + + CETL_NODISCARD cetl::optional send(const TransferMetadata& metadata, + const PayloadFragments payload_fragments) override + { + (void) delegate_; + (void) metadata; + (void) payload_fragments; + + return NotImplementedError{}; + } + + // MARK: IRunnable + + void run(const TimePoint) override {} + + // MARK: Data members: + + TransportDelegate& delegate_; + const MessageTxParams params_; + +}; // MessageTxSession + +} // namespace detail +} // namespace can +} // namespace transport +} // namespace libcyphal + +#endif // LIBCYPHAL_TRANSPORT_CAN_MSG_TX_SESSION_HPP_INCLUDED diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index be09a312a..f27820df0 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -9,6 +9,7 @@ #include "media.hpp" #include "delegate.hpp" #include "msg_rx_session.hpp" +#include "msg_tx_session.hpp" #include "libcyphal/transport/transport.hpp" #include "libcyphal/transport/multiplexer.hpp" @@ -45,15 +46,16 @@ class TransportImpl final : public ICanTransport, private TransportDelegate cetl::pmr::memory_resource& memory, IMultiplexer& multiplexer, const std::array& media, + const std::size_t tx_capacity, const cetl::optional local_node_id) { // Verify input arguments: - // - At least one media interface must be provided. + // - At least one media interface must be provided, but no more than the maximum allowed (255). // - If a local node ID is provided, it must be within the valid range. // const auto media_count = static_cast( std::count_if(media.cbegin(), media.cend(), [](auto media) { return media != nullptr; })); - if (media_count == 0) + if ((media_count == 0) || (media_count > std::numeric_limits::max())) { return ArgumentError{}; } @@ -62,20 +64,15 @@ class TransportImpl final : public ICanTransport, private TransportDelegate return ArgumentError{}; } - libcyphal::detail::VarArray media_array{MaxMediaInterfaces, &memory}; - media_array.reserve(media_count); - std::copy_if(media.cbegin(), media.cend(), std::back_inserter(media_array), [](auto media) { - return media != nullptr; - }); - CETL_DEBUG_ASSERT(!media_array.empty() && (media_array.size() == media_count), ""); - const auto canard_node_id = static_cast(local_node_id.value_or(CANARD_NODE_ID_UNSET)); auto transport = libcyphal::detail::makeUniquePtr(memory, Tag{}, memory, multiplexer, - std::move(media_array), + media_count, + media, + tx_capacity, canard_node_id); if (transport == nullptr) { @@ -86,12 +83,14 @@ class TransportImpl final : public ICanTransport, private TransportDelegate } TransportImpl(Tag, - cetl::pmr::memory_resource& memory, - IMultiplexer& multiplexer, - libcyphal::detail::VarArray&& media_array, - const CanardNodeID canard_node_id) + cetl::pmr::memory_resource& memory, + IMultiplexer& multiplexer, + const std::size_t media_count, + const std::array& media_interfaces, + const std::size_t tx_capacity, + const CanardNodeID canard_node_id) : TransportDelegate{memory} - , media_array_{std::move(media_array)} + , media_array_{make_media_array(memory, tx_capacity, media_count, media_interfaces)} { // TODO: Use it! (void) multiplexer; @@ -132,9 +131,9 @@ class TransportImpl final : public ICanTransport, private TransportDelegate } CETL_NODISCARD Expected, AnyError> makeMessageTxSession( - const MessageTxParams&) override + const MessageTxParams& params) override { - return NotImplementedError{}; + return MessageTxSession::make(asDelegate(), params); } CETL_NODISCARD Expected, AnyError> makeRequestRxSession( @@ -179,13 +178,10 @@ class TransportImpl final : public ICanTransport, private TransportDelegate { std::array payload{}; - for (std::size_t media_index = 0; media_index < media_array_.size(); ++media_index) + for (const auto& media : media_array_) { - CETL_DEBUG_ASSERT(media_array_[media_index] != nullptr, "Expected media interface."); - auto& media = *media_array_[media_index]; - // TODO: Handle errors. - const auto pop_result = media.pop(payload); + const auto pop_result = media.interface.pop(payload); if (const auto opt_rx_meta = cetl::get_if>(&pop_result)) { if (opt_rx_meta->has_value()) @@ -203,7 +199,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate const auto result = canardRxAccept(&canard_instance(), static_cast(timestamp_us.count()), &canard_frame, - static_cast(media_index), + media.index, &out_transfer, &out_subscription); if (result > 0) @@ -229,14 +225,32 @@ class TransportImpl final : public ICanTransport, private TransportDelegate // MARK: Privates: + struct Media final + { + Media(const std::size_t _index, IMedia& _interface, const std::size_t tx_capacity) + : index{static_cast(_index)} + , interface{_interface} + , canard_tx_queue{canardTxInit(tx_capacity, _interface.getMtu())} + { + } + Media(Media&&) = default; + Media(const Media&) = delete; + Media& operator=(Media&&) = delete; + Media& operator=(const Media&) = delete; + + const uint8_t index; + IMedia& interface; + CanardTxQueue canard_tx_queue; + }; + using MediaArray = libcyphal::detail::VarArray; + template CETL_NODISCARD T reduceMedia(const T init, Reducer reducer) const { T acc = init; - for (const auto media : media_array_) + for (const auto& media : media_array_) { - CETL_DEBUG_ASSERT(media != nullptr, "Expected media interface."); - acc = reducer(std::forward(acc), std::ref(*media)); + acc = reducer(std::forward(acc), media.interface); } return acc; } @@ -263,9 +277,30 @@ class TransportImpl final : public ICanTransport, private TransportDelegate return {}; } + static MediaArray make_media_array(cetl::pmr::memory_resource& memory, + const std::size_t tx_capacity, + const std::size_t media_count, + const std::array& media_interfaces) + { + MediaArray media_array{media_count, &memory}; + media_array.reserve(media_count); + + std::size_t index = 0; + for (const auto media_interface : media_interfaces) + { + if (media_interface != nullptr) + { + auto& media = *media_interface; + media_array.emplace_back(index++, media, tx_capacity); + } + } + + return media_array; + } + // MARK: Data members: - const libcyphal::detail::VarArray media_array_; + const MediaArray media_array_; }; // TransportImpl @@ -275,9 +310,10 @@ CETL_NODISCARD inline Expected, FactoryError> makeTrans cetl::pmr::memory_resource& memory, IMultiplexer& multiplexer, const std::array& media, // TODO: replace with `cetl::span` + const std::size_t tx_capacity, const cetl::optional local_node_id) { - return detail::TransportImpl::make(memory, multiplexer, media, local_node_id); + return detail::TransportImpl::make(memory, multiplexer, media, tx_capacity, local_node_id); } } // namespace can diff --git a/test/unittest/transport/can/test_can_msg_rx_session.cpp b/test/unittest/transport/can/test_can_msg_rx_session.cpp index b2c673aa7..3358e8bcf 100644 --- a/test/unittest/transport/can/test_can_msg_rx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_rx_session.cpp @@ -33,6 +33,11 @@ using namespace std::chrono_literals; class TestCanMsgRxSession : public testing::Test { protected: + void SetUp() override + { + EXPECT_CALL(media_mock_, getMtu()).WillRepeatedly(Return(CANARD_MTU_MAX)); + } + void TearDown() override { // TODO: Uncomment this when PMR deleter is fixed. @@ -41,7 +46,7 @@ class TestCanMsgRxSession : public testing::Test CETL_NODISCARD UniquePtr makeTransport(cetl::pmr::memory_resource& mr) { - auto maybe_transport = can::makeTransport(mr, mux_mock_, {&media_mock_}, {}); + auto maybe_transport = can::makeTransport(mr, mux_mock_, {&media_mock_}, 0, {}); EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); return cetl::get>(std::move(maybe_transport)); } diff --git a/test/unittest/transport/can/test_can_msg_tx_session.cpp b/test/unittest/transport/can/test_can_msg_tx_session.cpp new file mode 100644 index 000000000..14cc5f026 --- /dev/null +++ b/test/unittest/transport/can/test_can_msg_tx_session.cpp @@ -0,0 +1,75 @@ +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#include + +#include "media_mock.hpp" +#include "../multiplexer_mock.hpp" +#include "../../gtest_helpers.hpp" +#include "../../memory_resource_mock.hpp" +#include "../../tracking_memory_resource.hpp" + +#include + +namespace +{ +using namespace libcyphal; +using namespace libcyphal::transport; +using namespace libcyphal::transport::can; + +using testing::_; +using testing::Eq; +using testing::Return; +using testing::IsNull; +using testing::NotNull; +using testing::Optional; +using testing::StrictMock; +using testing::VariantWith; + +using namespace std::chrono_literals; + +class TestCanMsgTxSession : public testing::Test +{ +protected: + void SetUp() override + { + EXPECT_CALL(media_mock_, getMtu()).WillRepeatedly(Return(CANARD_MTU_MAX)); + } + + void TearDown() override + { + // TODO: Uncomment this when PMR deleter is fixed. + // EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); + } + + CETL_NODISCARD UniquePtr makeTransport(cetl::pmr::memory_resource& mr) + { + auto maybe_transport = can::makeTransport(mr, mux_mock_, {&media_mock_}, 0, {}); + EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); + return cetl::get>(std::move(maybe_transport)); + } + + // MARK: Data members: + + TrackingMemoryResource mr_; + StrictMock media_mock_{}; + StrictMock mux_mock_{}; +}; + +// MARK: Tests: + +TEST_F(TestCanMsgTxSession, make) +{ + auto transport = makeTransport(mr_); + + auto maybe_session = transport->makeMessageTxSession({123}); + EXPECT_THAT(maybe_session, VariantWith>(_)); + auto session = cetl::get>(std::move(maybe_session)); + EXPECT_THAT(session, NotNull()); + + EXPECT_THAT(session->getParams().subject_id, Eq(123)); +} + +} // namespace diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index 7964a5f01..896fbc09a 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -31,6 +31,11 @@ using testing::VariantWith; class TestCanTransport : public testing::Test { protected: + void SetUp() override + { + EXPECT_CALL(media_mock_, getMtu()).WillRepeatedly(Return(CANARD_MTU_MAX)); + } + void TearDown() override { // TODO: Uncomment this when PMR deleter is fixed. @@ -54,7 +59,7 @@ TEST_F(TestCanTransport, makeTransport_no_memory) // Emulate that there is no memory available for the transport. EXPECT_CALL(mr_mock, do_allocate(sizeof(can::detail::TransportImpl), _)).WillOnce(Return(nullptr)); - auto maybe_transport = makeTransport(mr_mock, mux_mock_, {&media_mock_}, {}); + auto maybe_transport = makeTransport(mr_mock, mux_mock_, {&media_mock_}, 0, {}); EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } @@ -62,7 +67,7 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) { // Anonymous node { - auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, {}); + auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, 0, {}); EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); auto transport = cetl::get>(std::move(maybe_transport)); @@ -73,7 +78,7 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) { const auto node_id = cetl::make_optional(static_cast(42)); - auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, node_id); + auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, 0, node_id); EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); auto transport = cetl::get>(std::move(maybe_transport)); @@ -83,16 +88,19 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) // Two media interfaces { StrictMock media_mock2; + EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(Return(CANARD_MTU_MAX)); - auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_, nullptr, &media_mock2}, {}); + auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_, nullptr, &media_mock2}, 0, {}); EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); } // All 3 maximum number of media interfaces { StrictMock media_mock2{}, media_mock3{}; + EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(Return(CANARD_MTU_MAX)); + EXPECT_CALL(media_mock3, getMtu()).WillRepeatedly(Return(CANARD_MTU_MAX)); - auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_, &media_mock2, &media_mock3}, {}); + auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_, &media_mock2, &media_mock3}, 0, {}); EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); } } @@ -103,7 +111,7 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) { const auto node_id = cetl::make_optional(static_cast(CANARD_NODE_ID_MAX)); - const auto maybe_transport = makeTransport(mr_, mux_mock_, {}, node_id); + const auto maybe_transport = makeTransport(mr_, mux_mock_, {}, 0, node_id); EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } @@ -111,7 +119,7 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) { const auto node_id = cetl::make_optional(static_cast(CANARD_NODE_ID_MAX + 1)); - const auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, node_id); + const auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, 0, node_id); EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } @@ -119,7 +127,7 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) { const auto node_id = cetl::make_optional(static_cast(CANARD_NODE_ID_UNSET)); - const auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, node_id); + const auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, 0, node_id); EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } @@ -128,7 +136,7 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) const NodeId too_big = static_cast(std::numeric_limits::max()) + 1; const auto node_id = cetl::make_optional(too_big); - const auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, node_id); + const auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, 0, node_id); EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } } @@ -136,9 +144,10 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) TEST_F(TestCanTransport, getProtocolParams) { StrictMock media_mock2{}; + EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(Return(CANARD_MTU_MAX)); auto transport = - cetl::get>(makeTransport(mr_, mux_mock_, {&media_mock_, &media_mock2}, {})); + cetl::get>(makeTransport(mr_, mux_mock_, {&media_mock_, &media_mock2}, 0, {})); EXPECT_CALL(media_mock_, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_FD)); EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_CLASSIC)); @@ -163,7 +172,7 @@ TEST_F(TestCanTransport, getProtocolParams) TEST_F(TestCanTransport, makeMessageRxSession) { - auto transport = cetl::get>(makeTransport(mr_, mux_mock_, {&media_mock_}, {})); + auto transport = cetl::get>(makeTransport(mr_, mux_mock_, {&media_mock_}, 0, {})); auto maybe_rx_session = transport->makeMessageRxSession({42, 123}); EXPECT_THAT(maybe_rx_session, VariantWith>(NotNull())); @@ -175,7 +184,7 @@ TEST_F(TestCanTransport, makeMessageRxSession) TEST_F(TestCanTransport, makeMessageRxSession_invalid_subject_id) { - auto transport = cetl::get>(makeTransport(mr_, mux_mock_, {&media_mock_}, {})); + auto transport = cetl::get>(makeTransport(mr_, mux_mock_, {&media_mock_}, 0, {})); auto maybe_rx_session = transport->makeMessageRxSession({0, CANARD_SUBJECT_ID_MAX + 1}); EXPECT_THAT(maybe_rx_session, VariantWith(VariantWith(_))); From a68fadb6db10cf93ebc6c8829884b53e3adbc9ab Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Fri, 19 Apr 2024 18:35:14 +0300 Subject: [PATCH 19/64] unit test for no memory for tx session #verification --- include/libcyphal/transport/can/transport.hpp | 1 + .../transport/can/test_can_msg_tx_session.cpp | 14 ++++++++++++++ test/unittest/transport/can/test_can_transport.cpp | 11 +++++++++++ 3 files changed, 26 insertions(+) diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index f27820df0..dde02a78f 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -294,6 +294,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate media_array.emplace_back(index++, media, tx_capacity); } } + CETL_DEBUG_ASSERT(!media_array.empty() && (media_array.size() == media_count) && (index == media_count), ""); return media_array; } diff --git a/test/unittest/transport/can/test_can_msg_tx_session.cpp b/test/unittest/transport/can/test_can_msg_tx_session.cpp index 14cc5f026..45d01c0d3 100644 --- a/test/unittest/transport/can/test_can_msg_tx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_tx_session.cpp @@ -72,4 +72,18 @@ TEST_F(TestCanMsgTxSession, make) EXPECT_THAT(session->getParams().subject_id, Eq(123)); } +TEST_F(TestCanMsgTxSession, make_no_memory) +{ + StrictMock mr_mock{}; + mr_mock.redirectExpectedCallsTo(mr_); + + // Emulate that there is no memory available for the message session. + EXPECT_CALL(mr_mock, do_allocate(sizeof(can::detail::MessageTxSession), _)).WillOnce(Return(nullptr)); + + auto transport = makeTransport(mr_mock); + + auto maybe_session = transport->makeMessageTxSession({0x23}); + EXPECT_THAT(maybe_session, VariantWith(VariantWith(_))); +} + } // namespace diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index 896fbc09a..c333af428 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -190,4 +190,15 @@ TEST_F(TestCanTransport, makeMessageRxSession_invalid_subject_id) EXPECT_THAT(maybe_rx_session, VariantWith(VariantWith(_))); } +TEST_F(TestCanTransport, makeMessageTxSession) +{ + auto transport = cetl::get>(makeTransport(mr_, mux_mock_, {&media_mock_}, 0, {})); + + auto maybe_tx_session = transport->makeMessageTxSession({123}); + EXPECT_THAT(maybe_tx_session, VariantWith>(NotNull())); + + auto session = cetl::get>(std::move(maybe_tx_session)); + EXPECT_THAT(session->getParams().subject_id, Eq(123)); +} + } // namespace From a705debe5eca3be2e0230a587d59d8c4e4a7deb0 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Fri, 19 Apr 2024 20:38:14 +0300 Subject: [PATCH 20/64] buffer --- .../transport/can/msg_tx_session.hpp | 59 ++++++++++++++++++- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/include/libcyphal/transport/can/msg_tx_session.hpp b/include/libcyphal/transport/can/msg_tx_session.hpp index a0626d903..04d6ef1aa 100644 --- a/include/libcyphal/transport/can/msg_tx_session.hpp +++ b/include/libcyphal/transport/can/msg_tx_session.hpp @@ -11,6 +11,8 @@ #include +#include + namespace libcyphal { namespace transport @@ -60,9 +62,60 @@ class MessageTxSession final : public IMessageTxSession CETL_NODISCARD cetl::optional send(const TransferMetadata& metadata, const PayloadFragments payload_fragments) override { - (void) delegate_; - (void) metadata; - (void) payload_fragments; + std::size_t payload_size = 0; + const cetl::byte* payload = nullptr; + cetl::byte* buffer = nullptr; + + const auto predicate = [&payload_size, &payload](const cetl::span frag) -> bool { + const auto frag_size = frag.size(); + if (frag_size == 0) + { + return false; + } + payload = frag.data(); + payload_size += frag_size; + return true; + }; + const auto total_fragments = std::count_if(payload_fragments.begin(), payload_fragments.end(), predicate); + + if (total_fragments > 1) + { + buffer = static_cast(delegate_.memory().allocate(payload_size)); + if (buffer == nullptr) + { + return MemoryError{}; + } + + size_t index = 0; + for (const auto& frag : payload_fragments) + { + std::memcpy(&buffer[index], frag.data(), frag.size()); + index += frag.size(); + } + payload = buffer; + } + + // TransferMetadata + // TransferId transfer_id; + // TimePoint timestamp; + // Priority priority; + + // int32_t canardTxPush(CanardTxQueue* const que, + // CanardInstance* const ins, + // const CanardMicrosecond tx_deadline_usec, + // const CanardTransferMetadata* const metadata, + // const size_t payload_size, + // const void* const payload) + // + // const auto result = canardTxPush(&delegate_.canard_instance(), + // CanardTransferKindMessage, + // static_cast(params_.subject_id), + // static_cast(metadata.extent_bytes), + // &transfer_id, + // ×tamp, + // &priority, + // payload_fragments.data(), + // payload_fragments.size()); return NotImplementedError{}; } From 96b65ae894b402b4f2cb1fe6fab2e4b58b23d9c1 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Sun, 21 Apr 2024 01:21:54 +0300 Subject: [PATCH 21/64] Added `ContiguousPayload` helper. #verification --- include/libcyphal/runnable.hpp | 8 +- include/libcyphal/transport/can/delegate.hpp | 8 +- include/libcyphal/transport/can/media.hpp | 8 +- .../transport/can/msg_tx_session.hpp | 39 ++--- include/libcyphal/transport/can/transport.hpp | 8 +- .../transport/contiguous_payload.hpp | 99 ++++++++++++ include/libcyphal/transport/defines.hpp | 1 + include/libcyphal/transport/multiplexer.hpp | 8 +- include/libcyphal/transport/udp/media.hpp | 8 +- .../transport/test_contiguous_payload.cpp | 141 ++++++++++++++++++ 10 files changed, 274 insertions(+), 54 deletions(-) create mode 100644 include/libcyphal/transport/contiguous_payload.hpp create mode 100644 test/unittest/transport/test_contiguous_payload.cpp diff --git a/include/libcyphal/runnable.hpp b/include/libcyphal/runnable.hpp index 6e68d6401..082a963fd 100644 --- a/include/libcyphal/runnable.hpp +++ b/include/libcyphal/runnable.hpp @@ -14,10 +14,10 @@ namespace libcyphal class IRunnable { public: - IRunnable(IRunnable&&) = delete; - IRunnable(const IRunnable&) = delete; - IRunnable& operator=(IRunnable&&) = delete; - IRunnable& operator=(const IRunnable&) = delete; + IRunnable(const IRunnable&) = delete; + IRunnable(IRunnable&&) noexcept = delete; + IRunnable& operator=(const IRunnable&) = delete; + IRunnable& operator=(IRunnable&&) noexcept = delete; virtual void run(const TimePoint now) = 0; diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp index 4324a9bbc..40118751f 100644 --- a/include/libcyphal/transport/can/delegate.hpp +++ b/include/libcyphal/transport/can/delegate.hpp @@ -194,10 +194,10 @@ struct TransportDelegate struct SessionDelegate { - SessionDelegate(SessionDelegate&&) = delete; - SessionDelegate(const SessionDelegate&) = delete; - SessionDelegate& operator=(SessionDelegate&&) = delete; - SessionDelegate& operator=(const SessionDelegate&) = delete; + SessionDelegate(const SessionDelegate&) = delete; + SessionDelegate(SessionDelegate&&) noexcept = delete; + SessionDelegate& operator=(const SessionDelegate&) = delete; + SessionDelegate& operator=(SessionDelegate&&) noexcept = delete; virtual void acceptRxTransfer(const CanardRxTransfer& transfer) = 0; diff --git a/include/libcyphal/transport/can/media.hpp b/include/libcyphal/transport/can/media.hpp index f1bf8ce22..6dc12cabe 100644 --- a/include/libcyphal/transport/can/media.hpp +++ b/include/libcyphal/transport/can/media.hpp @@ -36,10 +36,10 @@ struct RxMetadata final class IMedia { public: - IMedia(IMedia&&) = delete; - IMedia(const IMedia&) = delete; - IMedia& operator=(IMedia&&) = delete; - IMedia& operator=(const IMedia&) = delete; + IMedia(const IMedia&) = delete; + IMedia(IMedia&&) noexcept = delete; + IMedia& operator=(const IMedia&) = delete; + IMedia& operator=(IMedia&&) noexcept = delete; /// @brief Get the maximum transmission unit (MTU) of the CAN bus. /// diff --git a/include/libcyphal/transport/can/msg_tx_session.hpp b/include/libcyphal/transport/can/msg_tx_session.hpp index 04d6ef1aa..e45dd2aec 100644 --- a/include/libcyphal/transport/can/msg_tx_session.hpp +++ b/include/libcyphal/transport/can/msg_tx_session.hpp @@ -8,6 +8,7 @@ #include "delegate.hpp" #include "libcyphal/transport/msg_sessions.hpp" +#include "libcyphal/transport/contiguous_payload.hpp" #include @@ -62,37 +63,13 @@ class MessageTxSession final : public IMessageTxSession CETL_NODISCARD cetl::optional send(const TransferMetadata& metadata, const PayloadFragments payload_fragments) override { - std::size_t payload_size = 0; - const cetl::byte* payload = nullptr; - cetl::byte* buffer = nullptr; - - const auto predicate = [&payload_size, &payload](const cetl::span frag) -> bool { - const auto frag_size = frag.size(); - if (frag_size == 0) - { - return false; - } - payload = frag.data(); - payload_size += frag_size; - return true; - }; - const auto total_fragments = std::count_if(payload_fragments.begin(), payload_fragments.end(), predicate); - - if (total_fragments > 1) + // libcanard currently does not support fragmented payloads (at `canardTxPush`). + // so we need to concatenate them when there are more than one non-empty fragment. + // + const transport::detail::ContiguousPayload contiguous_payload{delegate_.memory(), payload_fragments}; + if ((contiguous_payload.data() == nullptr) && (contiguous_payload.size() > 0)) { - buffer = static_cast(delegate_.memory().allocate(payload_size)); - if (buffer == nullptr) - { - return MemoryError{}; - } - - size_t index = 0; - for (const auto& frag : payload_fragments) - { - std::memcpy(&buffer[index], frag.data(), frag.size()); - index += frag.size(); - } - payload = buffer; + return MemoryError{}; } // TransferMetadata @@ -117,6 +94,8 @@ class MessageTxSession final : public IMessageTxSession // payload_fragments.data(), // payload_fragments.size()); + (void) metadata; + return NotImplementedError{}; } diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index dde02a78f..015cea64c 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -233,10 +233,10 @@ class TransportImpl final : public ICanTransport, private TransportDelegate , canard_tx_queue{canardTxInit(tx_capacity, _interface.getMtu())} { } - Media(Media&&) = default; - Media(const Media&) = delete; - Media& operator=(Media&&) = delete; - Media& operator=(const Media&) = delete; + Media(const Media&) = delete; + Media(Media&&) noexcept = default; + Media& operator=(const Media&) = delete; + Media& operator=(Media&&) noexcept = delete; const uint8_t index; IMedia& interface; diff --git a/include/libcyphal/transport/contiguous_payload.hpp b/include/libcyphal/transport/contiguous_payload.hpp new file mode 100644 index 000000000..2724fd8bc --- /dev/null +++ b/include/libcyphal/transport/contiguous_payload.hpp @@ -0,0 +1,99 @@ +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#ifndef LIBCYPHAL_TRANSPORT_CONTIGUOUS_PAYLOAD_HPP_INCLUDED +#define LIBCYPHAL_TRANSPORT_CONTIGUOUS_PAYLOAD_HPP_INCLUDED + +#include "defines.hpp" +#include "libcyphal/types.hpp" + +namespace libcyphal +{ +namespace transport +{ +namespace detail +{ + +/// @breaf Makes a contiguous payload from a list of payload fragments. +/// +/// Has optimization for the case when there is only one non-empty fragment - +/// in this case there will be no memory allocation and payload copying. +/// Automatically deallocates memory (if any) when the object is destroyed. +/// +/// Probably could be deleted when libcanard will start support fragmented payloads (at `canardTxPush`). +/// +class ContiguousPayload final +{ +public: + ContiguousPayload(cetl::pmr::memory_resource& mr, const PayloadFragments payload_fragments) + : mr_{mr} + { + // Count fragments skipping empty ones. Also keep tracking of the total payload size + // and pointer to the last non-empty fragment (which will be in use for the optimization). + // + const auto total_non_empty_fragments = + std::count_if(payload_fragments.begin(), payload_fragments.end(), [this](auto frag) { + if (frag.empty()) + { + return false; + } + payload_ = frag.data(); + payload_size_ += frag.size(); + return true; + }); + + if (total_non_empty_fragments > 1) + { + allocated_buffer_ = static_cast(mr_.allocate(payload_size_)); + payload_ = allocated_buffer_; + if (const auto buffer = allocated_buffer_) + { + std::size_t offset = 0; + for (const auto frag : payload_fragments) + { + std::memcpy(&buffer[offset], frag.data(), frag.size()); + offset += frag.size(); + } + } + } + } + ContiguousPayload(const ContiguousPayload&) = delete; + ContiguousPayload(ContiguousPayload&&) noexcept = delete; + ContiguousPayload& operator=(const ContiguousPayload&) = delete; + ContiguousPayload& operator=(ContiguousPayload&&) noexcept = delete; + + ~ContiguousPayload() + { + if (allocated_buffer_ != nullptr) + { + mr_.deallocate(allocated_buffer_, payload_size_); + } + } + + CETL_NODISCARD std::size_t size() const noexcept + { + return payload_size_; + } + + CETL_NODISCARD const cetl::byte* data() const noexcept + { + return payload_; + } + +private: + // MARK: Data members: + + cetl::pmr::memory_resource& mr_; + const cetl::byte* payload_{nullptr}; + std::size_t payload_size_{0}; + cetl::byte* allocated_buffer_{nullptr}; + +}; // ContiguousBytes + +} // namespace detail +} // namespace transport +} // namespace libcyphal + +#endif // LIBCYPHAL_TRANSPORT_CONTIGUOUS_PAYLOAD_HPP_INCLUDED diff --git a/include/libcyphal/transport/defines.hpp b/include/libcyphal/transport/defines.hpp index 6cce553f5..a6784f11c 100644 --- a/include/libcyphal/transport/defines.hpp +++ b/include/libcyphal/transport/defines.hpp @@ -7,6 +7,7 @@ #define LIBCYPHAL_TRANSPORT_DEFINES_HPP_INCLUDED #include "dynamic_buffer.hpp" +#include "libcyphal/types.hpp" namespace libcyphal { diff --git a/include/libcyphal/transport/multiplexer.hpp b/include/libcyphal/transport/multiplexer.hpp index 799796730..66b51a6b8 100644 --- a/include/libcyphal/transport/multiplexer.hpp +++ b/include/libcyphal/transport/multiplexer.hpp @@ -14,10 +14,10 @@ namespace transport class IMultiplexer { public: - IMultiplexer(IMultiplexer&&) = delete; - IMultiplexer(const IMultiplexer&) = delete; - IMultiplexer& operator=(IMultiplexer&&) = delete; - IMultiplexer& operator=(const IMultiplexer&) = delete; + IMultiplexer(const IMultiplexer&) = delete; + IMultiplexer(IMultiplexer&&) noexcept = delete; + IMultiplexer& operator=(const IMultiplexer&) = delete; + IMultiplexer& operator=(IMultiplexer&&) noexcept = delete; // TODO: Add methods here diff --git a/include/libcyphal/transport/udp/media.hpp b/include/libcyphal/transport/udp/media.hpp index e532ec50e..dd83e05b9 100644 --- a/include/libcyphal/transport/udp/media.hpp +++ b/include/libcyphal/transport/udp/media.hpp @@ -16,10 +16,10 @@ namespace udp class IMedia { public: - IMedia(IMedia&&) = delete; - IMedia(const IMedia&) = delete; - IMedia& operator=(IMedia&&) = delete; - IMedia& operator=(const IMedia&) = delete; + IMedia(const IMedia&) = delete; + IMedia(IMedia&&) noexcept = delete; + IMedia& operator=(const IMedia&) = delete; + IMedia& operator=(IMedia&&) noexcept = delete; // TODO: Add methods here diff --git a/test/unittest/transport/test_contiguous_payload.cpp b/test/unittest/transport/test_contiguous_payload.cpp new file mode 100644 index 000000000..3f56d0138 --- /dev/null +++ b/test/unittest/transport/test_contiguous_payload.cpp @@ -0,0 +1,141 @@ +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#include + +#include "../memory_resource_mock.hpp" +#include "../tracking_memory_resource.hpp" + +#include +#include + +namespace +{ +using byte = cetl::byte; +using ContiguousPayload = libcyphal::transport::detail::ContiguousPayload; + +using testing::_; +using testing::Eq; +using testing::IsNull; +using testing::NotNull; +using testing::Return; +using testing::StrictMock; +using testing::ElementsAre; + +class TestContiguousPayload : public testing::Test +{ +protected: + void TearDown() override + { + EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); + } + + static constexpr byte b(std::uint8_t b) + { + return static_cast(b); + } + + // MARK: Data members: + + TrackingMemoryResource mr_; +}; + +// MARK: Tests: + +TEST_F(TestContiguousPayload, ctor_data_size) +{ + // Single fragment + { + const std::array data123 = {b(1), b(2), b(3)}; + const std::array, 1> fragments = {data123}; + + const ContiguousPayload payload{mr_, fragments}; + + EXPECT_THAT(payload.size(), Eq(3)); + EXPECT_THAT(payload.data(), NotNull()); + const std::vector v(payload.data(), payload.data() + payload.size()); + EXPECT_THAT(v, ElementsAre(b(1), b(2), b(3))); + } + EXPECT_THAT(mr_.total_allocated_bytes, Eq(0)); + EXPECT_THAT(mr_.total_deallocated_bytes, Eq(0)); + + // Double fragments + { + const std::array data123 = {b(1), b(2), b(3)}; + const std::array data45 = {b(4), b(5)}; + const std::array, 2> fragments = {data123, data45}; + + const ContiguousPayload payload{mr_, fragments}; + + EXPECT_THAT(payload.size(), Eq(5)); + EXPECT_THAT(payload.data(), NotNull()); + const std::vector v(payload.data(), payload.data() + payload.size()); + EXPECT_THAT(v, ElementsAre(b(1), b(2), b(3), b(4), b(5))); + } + EXPECT_THAT(mr_.total_allocated_bytes, Eq(5)); + EXPECT_THAT(mr_.total_deallocated_bytes, Eq(5)); +} + +TEST_F(TestContiguousPayload, ctor_empty_cases) +{ + // No fragments + { + const std::array, 0> fragments = {}; + + const ContiguousPayload payload{mr_, fragments}; + + EXPECT_THAT(payload.size(), Eq(0)); + EXPECT_THAT(payload.data(), IsNull()); + } + + // There are fragments, but they are empty + { + const std::array data_empty0 = {}; + const std::array data_empty1 = {}; + const std::array, 2> fragments = {data_empty0, data_empty1}; + + const ContiguousPayload payload{mr_, fragments}; + + EXPECT_THAT(payload.size(), Eq(0)); + EXPECT_THAT(payload.data(), IsNull()); + } +} + +TEST_F(TestContiguousPayload, ctor_no_alloc_for_single_non_empty_fragment) +{ + StrictMock mr_mock{}; + + // 4 fragments, but only 1 is non-empty + const std::array data_empty0 = {}; + const std::array data123 = {b(1), b(2), b(3)}; + const std::array data_empty3 = {}; + const std::array, 4> fragments = {data_empty0, data123, {nullptr, 0}, data_empty3}; + + const ContiguousPayload payload{mr_mock, fragments}; + + EXPECT_THAT(payload.size(), Eq(3)); + EXPECT_THAT(payload.data(), Eq(data123.data())); + const std::vector v(payload.data(), payload.data() + payload.size()); + EXPECT_THAT(v, ElementsAre(b(1), b(2), b(3))); +} + +TEST_F(TestContiguousPayload, ctor_no_memory_error) +{ + StrictMock mr_mock{}; + + // Emulate that there is no memory available for the transport. + EXPECT_CALL(mr_mock, do_allocate(_, _)).WillOnce(Return(nullptr)); + + const std::array data123 = {b(1), b(2), b(3)}; + const std::array data45 = {b(4), b(5)}; + const std::array, 2> fragments = {data123, data45}; + + const ContiguousPayload payload{mr_mock, fragments}; + + EXPECT_THAT(payload.size(), Eq(5)); + EXPECT_THAT(payload.data(), IsNull()); +} + +} // namespace From c4e478b95791bf589e3958d650f57522a1a69393 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Sun, 21 Apr 2024 01:45:18 +0300 Subject: [PATCH 22/64] try fix build #verification --- test/unittest/transport/test_contiguous_payload.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/unittest/transport/test_contiguous_payload.cpp b/test/unittest/transport/test_contiguous_payload.cpp index 3f56d0138..76afd484b 100644 --- a/test/unittest/transport/test_contiguous_payload.cpp +++ b/test/unittest/transport/test_contiguous_payload.cpp @@ -111,7 +111,10 @@ TEST_F(TestContiguousPayload, ctor_no_alloc_for_single_non_empty_fragment) const std::array data_empty0 = {}; const std::array data123 = {b(1), b(2), b(3)}; const std::array data_empty3 = {}; - const std::array, 4> fragments = {data_empty0, data123, {nullptr, 0}, data_empty3}; + const std::array, 4> fragments = {data_empty0, + data123, + {static_cast(nullptr), 0}, + data_empty3}; const ContiguousPayload payload{mr_mock, fragments}; From 27a04f5957cec682cb27021b47f605be92dd0f4a Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Sun, 21 Apr 2024 02:26:15 +0300 Subject: [PATCH 23/64] disable/ignore exceptions during coverage #verification --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 063e8b7cf..32a340947 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -77,7 +77,7 @@ jobs: run: > ./build-tools/bin/verify.py --verbose - ${{ matrix.build_flavor != 'Coverage' && '--asserts' || '' }} + ${{ matrix.build_flavor != 'Coverage' && '--asserts' || '--no-exceptions' }} --cpp-standard ${{ matrix.std }} --build-flavor ${{ matrix.build_flavor }} --toolchain ${{ matrix.toolchain }} From 11bdbeabb18570663648bca0aad4406bf0439445 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Mon, 22 Apr 2024 16:49:57 +0300 Subject: [PATCH 24/64] PR fixes --- include/libcyphal/runnable.hpp | 4 ++-- include/libcyphal/transport/can/delegate.hpp | 17 ++++++++++------- .../libcyphal/transport/can/msg_rx_session.hpp | 15 ++++++++------- include/libcyphal/transport/can/transport.hpp | 13 ++++++------- include/libcyphal/transport/defines.hpp | 2 ++ include/libcyphal/transport/errors.hpp | 16 ++++++++-------- 6 files changed, 36 insertions(+), 31 deletions(-) diff --git a/include/libcyphal/runnable.hpp b/include/libcyphal/runnable.hpp index 6e68d6401..83e3681ea 100644 --- a/include/libcyphal/runnable.hpp +++ b/include/libcyphal/runnable.hpp @@ -18,12 +18,12 @@ class IRunnable IRunnable(const IRunnable&) = delete; IRunnable& operator=(IRunnable&&) = delete; IRunnable& operator=(const IRunnable&) = delete; + virtual ~IRunnable() = default; virtual void run(const TimePoint now) = 0; protected: - IRunnable() = default; - virtual ~IRunnable() = default; + IRunnable() = default; }; } // namespace libcyphal diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp index 4324a9bbc..d2e08fa20 100644 --- a/include/libcyphal/transport/can/delegate.hpp +++ b/include/libcyphal/transport/can/delegate.hpp @@ -22,14 +22,16 @@ namespace can namespace detail { -struct TransportDelegate +class TransportDelegate { // 1141F5C0-2E61-44BF-9F0E-FA1C518CD517 using CanardMemoryTypeIdType = cetl:: type_id_type<0x11, 0x41, 0xF5, 0xC0, 0x2E, 0x61, 0x44, 0xBF, 0x9F, 0x0E, 0xFA, 0x1C, 0x51, 0x8C, 0xD5, 0x17>; - struct CanardMemory final : public cetl::rtti_helper +public: + class CanardMemory final : public cetl::rtti_helper { + public: CanardMemory(TransportDelegate& delegate, void* const buffer, const std::size_t payload_size) : delegate_{delegate} , buffer_{buffer} @@ -165,8 +167,8 @@ struct TransportDelegate { auto& self = getSelfFrom(ins); - const auto memory_size = sizeof(CanardMemoryHeader) + amount; - auto memory_header = static_cast(self.memory_.allocate(memory_size)); + const std::size_t memory_size = sizeof(CanardMemoryHeader) + amount; + auto memory_header = static_cast(self.memory_.allocate(memory_size)); if (memory_header == nullptr) { return nullptr; @@ -192,8 +194,9 @@ struct TransportDelegate }; // TransportDelegate -struct SessionDelegate +class SessionDelegate { +public: SessionDelegate(SessionDelegate&&) = delete; SessionDelegate(const SessionDelegate&) = delete; SessionDelegate& operator=(SessionDelegate&&) = delete; @@ -202,8 +205,8 @@ struct SessionDelegate virtual void acceptRxTransfer(const CanardRxTransfer& transfer) = 0; protected: - SessionDelegate() = default; - virtual ~SessionDelegate() = default; + SessionDelegate() = default; + ~SessionDelegate() = default; }; // SessionDelegate diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp index bd2174fed..67c3803ce 100644 --- a/include/libcyphal/transport/can/msg_rx_session.hpp +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -118,13 +118,14 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate void acceptRxTransfer(const CanardRxTransfer& transfer) override { - const auto priority{static_cast(transfer.metadata.priority)}; - const auto transfer_id{static_cast(transfer.metadata.transfer_id)}; - const auto timestamp = TimePoint{std::chrono::microseconds{transfer.timestamp_usec}}; - - const auto publisher_node_id = transfer.metadata.remote_node_id > CANARD_NODE_ID_MAX - ? cetl::nullopt - : cetl::make_optional(transfer.metadata.remote_node_id); + const auto priority = static_cast(transfer.metadata.priority); + const auto transfer_id = static_cast(transfer.metadata.transfer_id); + const auto timestamp = TimePoint{std::chrono::microseconds{transfer.timestamp_usec}}; + + const cetl::optional publisher_node_id = + transfer.metadata.remote_node_id > CANARD_NODE_ID_MAX + ? cetl::nullopt + : cetl::make_optional(transfer.metadata.remote_node_id); const MessageTransferMetadata meta{{transfer_id, timestamp, priority}, publisher_node_id}; TransportDelegate::CanardMemory canard_memory{delegate_, transfer.payload, transfer.payload_size}; diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index 7ff7a4f20..5bd14b2bd 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -48,11 +48,12 @@ class TransportImpl final : public ICanTransport, private TransportDelegate const cetl::optional local_node_id) { // Verify input arguments: - // - At least one media interface must be provided. + // - At least one valid media interface must be provided. // - If a local node ID is provided, it must be within the valid range. // - const auto media_count = static_cast( - std::count_if(media.cbegin(), media.cend(), [](auto media) { return media != nullptr; })); + auto valid_media_predicate = [](IMedia* const media) { return media != nullptr; }; + const auto media_count = + static_cast(std::count_if(media.cbegin(), media.cend(), valid_media_predicate)); if (media_count == 0) { return ArgumentError{}; @@ -64,9 +65,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate libcyphal::detail::VarArray media_array{MaxMediaInterfaces, &memory}; media_array.reserve(media_count); - std::copy_if(media.cbegin(), media.cend(), std::back_inserter(media_array), [](auto media) { - return media != nullptr; - }); + std::copy_if(media.cbegin(), media.cend(), std::back_inserter(media_array), valid_media_predicate); CETL_DEBUG_ASSERT(!media_array.empty() && (media_array.size() == media_count), ""); const auto canard_node_id = static_cast(local_node_id.value_or(CANARD_NODE_ID_UNSET)); @@ -183,7 +182,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate for (std::size_t media_index = 0; media_index < media_array_.size(); ++media_index) { CETL_DEBUG_ASSERT(media_array_[media_index] != nullptr, "Expected media interface."); - auto& media = *media_array_[media_index]; + IMedia& media = *media_array_[media_index]; // TODO: Handle errors. const auto pop_result = media.pop(payload); diff --git a/include/libcyphal/transport/defines.hpp b/include/libcyphal/transport/defines.hpp index 6cce553f5..ca3cbd226 100644 --- a/include/libcyphal/transport/defines.hpp +++ b/include/libcyphal/transport/defines.hpp @@ -54,6 +54,7 @@ struct TransferMetadata Priority priority; }; +// AUTOSAR A11-0-2 exception: we just enhance the base metadata with additional information. struct MessageTransferMetadata final : TransferMetadata { MessageTransferMetadata(const TransferMetadata& transfer_metadata, cetl::optional _publisher_node_id) @@ -65,6 +66,7 @@ struct MessageTransferMetadata final : TransferMetadata cetl::optional publisher_node_id; }; +// AUTOSAR A11-0-2 exception: we just enhance the base metadata with additional information. struct ServiceTransferMetadata final : TransferMetadata { NodeId remote_node_id; diff --git a/include/libcyphal/transport/errors.hpp b/include/libcyphal/transport/errors.hpp index e73c0fa1d..c899bb890 100644 --- a/include/libcyphal/transport/errors.hpp +++ b/include/libcyphal/transport/errors.hpp @@ -13,31 +13,31 @@ namespace libcyphal namespace transport { -struct StateError +struct StateError final {}; -struct AnonymousError +struct AnonymousError final {}; -struct ArgumentError +struct ArgumentError final {}; -struct MemoryError +struct MemoryError final {}; -struct CapacityError +struct CapacityError final {}; -struct PlatformError +struct PlatformError final { std::uint32_t code; }; -struct SessionAlreadyExistsError +struct SessionAlreadyExistsError final {}; // TODO: Delete it when everything is implemented. -struct NotImplementedError +struct NotImplementedError final {}; /// @brief Defines any possible error at Cyphal transport layer. From 784665a0a3a2fa1721276113b823224949d77a19 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Mon, 22 Apr 2024 17:17:56 +0300 Subject: [PATCH 25/64] minor fixes #verification --- include/libcyphal/transport/can/transport.hpp | 11 ++++++----- include/libcyphal/transport/contiguous_payload.hpp | 8 +++++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index e131ff283..0390ef28c 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -54,7 +54,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate // - If a local node ID is provided, it must be within the valid range. // const auto media_count = static_cast( - std::count_if(media.cbegin(), media.cend(), [](auto media) { return media != nullptr; })); + std::count_if(media.cbegin(), media.cend(), [](IMedia* const media) { return media != nullptr; })); if ((media_count == 0) || (media_count > std::numeric_limits::max())) { return ArgumentError{}; @@ -179,7 +179,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate { std::array payload{}; - for (const auto& media : media_array_) + for (const Media& media : media_array_) { // TODO: Handle errors. const auto pop_result = media.interface.pop(payload); @@ -226,8 +226,9 @@ class TransportImpl final : public ICanTransport, private TransportDelegate // MARK: Privates: - struct Media final + class Media final { + public: Media(const std::size_t _index, IMedia& _interface, const std::size_t tx_capacity) : index{static_cast(_index)} , interface{_interface} @@ -287,11 +288,11 @@ class TransportImpl final : public ICanTransport, private TransportDelegate media_array.reserve(media_count); std::size_t index = 0; - for (const auto media_interface : media_interfaces) + for (IMedia* const media_interface : media_interfaces) { if (media_interface != nullptr) { - auto& media = *media_interface; + IMedia& media = *media_interface; media_array.emplace_back(index++, media, tx_capacity); } } diff --git a/include/libcyphal/transport/contiguous_payload.hpp b/include/libcyphal/transport/contiguous_payload.hpp index 2724fd8bc..0f5030c79 100644 --- a/include/libcyphal/transport/contiguous_payload.hpp +++ b/include/libcyphal/transport/contiguous_payload.hpp @@ -30,11 +30,13 @@ class ContiguousPayload final ContiguousPayload(cetl::pmr::memory_resource& mr, const PayloadFragments payload_fragments) : mr_{mr} { + using Fragment = cetl::span; + // Count fragments skipping empty ones. Also keep tracking of the total payload size // and pointer to the last non-empty fragment (which will be in use for the optimization). // const auto total_non_empty_fragments = - std::count_if(payload_fragments.begin(), payload_fragments.end(), [this](auto frag) { + std::count_if(payload_fragments.begin(), payload_fragments.end(), [this](const Fragment frag) { if (frag.empty()) { return false; @@ -48,10 +50,10 @@ class ContiguousPayload final { allocated_buffer_ = static_cast(mr_.allocate(payload_size_)); payload_ = allocated_buffer_; - if (const auto buffer = allocated_buffer_) + if (cetl::byte* const buffer = allocated_buffer_) { std::size_t offset = 0; - for (const auto frag : payload_fragments) + for (const Fragment frag : payload_fragments) { std::memcpy(&buffer[offset], frag.data(), frag.size()); offset += frag.size(); From 7c0a53812739a49d3a4de5538f4a5416d5f90347 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Mon, 22 Apr 2024 18:02:56 +0300 Subject: [PATCH 26/64] Fix for issue #349 #verification Addressed `// TODO: replace with cetl::span` --- cmake/modules/Findcetl.cmake | 2 +- include/libcyphal/transport/can/transport.hpp | 42 +++++++++---------- include/libcyphal/transport/defines.hpp | 4 -- include/libcyphal/transport/udp/transport.hpp | 8 ++-- .../transport/can/test_can_msg_rx_session.cpp | 4 +- .../transport/can/test_can_msg_tx_session.cpp | 4 +- .../transport/can/test_can_transport.cpp | 37 ++++++++++------ .../transport/udp/test_udp_transport.cpp | 3 +- 8 files changed, 58 insertions(+), 46 deletions(-) diff --git a/cmake/modules/Findcetl.cmake b/cmake/modules/Findcetl.cmake index edad54c7d..94659983e 100644 --- a/cmake/modules/Findcetl.cmake +++ b/cmake/modules/Findcetl.cmake @@ -6,7 +6,7 @@ include(FetchContent) set(cetl_GIT_REPOSITORY "https://github.com/OpenCyphal/cetl.git") -set(cetl_GIT_TAG "884f3b38e7857daa790ed6ba7031f7037fb81a2e") +set(cetl_GIT_TAG "886a0d227a043511eed6b252ea0f788590c50e75") FetchContent_Declare( cetl diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index 0390ef28c..8ff29605d 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -43,18 +43,18 @@ class TransportImpl final : public ICanTransport, private TransportDelegate public: CETL_NODISCARD static Expected, FactoryError> make( - cetl::pmr::memory_resource& memory, - IMultiplexer& multiplexer, - const std::array& media, - const std::size_t tx_capacity, - const cetl::optional local_node_id) + cetl::pmr::memory_resource& memory, + IMultiplexer& multiplexer, + const cetl::span media, + const std::size_t tx_capacity, + const cetl::optional local_node_id) { // Verify input arguments: // - At least one media interface must be provided, but no more than the maximum allowed (255). // - If a local node ID is provided, it must be within the valid range. // const auto media_count = static_cast( - std::count_if(media.cbegin(), media.cend(), [](IMedia* const media) { return media != nullptr; })); + std::count_if(media.begin(), media.end(), [](IMedia* const media) { return media != nullptr; })); if ((media_count == 0) || (media_count > std::numeric_limits::max())) { return ArgumentError{}; @@ -83,12 +83,12 @@ class TransportImpl final : public ICanTransport, private TransportDelegate } TransportImpl(Tag, - cetl::pmr::memory_resource& memory, - IMultiplexer& multiplexer, - const std::size_t media_count, - const std::array& media_interfaces, - const std::size_t tx_capacity, - const CanardNodeID canard_node_id) + cetl::pmr::memory_resource& memory, + IMultiplexer& multiplexer, + const std::size_t media_count, + const cetl::span media_interfaces, + const std::size_t tx_capacity, + const CanardNodeID canard_node_id) : TransportDelegate{memory} , media_array_{make_media_array(memory, tx_capacity, media_count, media_interfaces)} { @@ -279,10 +279,10 @@ class TransportImpl final : public ICanTransport, private TransportDelegate return {}; } - static MediaArray make_media_array(cetl::pmr::memory_resource& memory, - const std::size_t tx_capacity, - const std::size_t media_count, - const std::array& media_interfaces) + static MediaArray make_media_array(cetl::pmr::memory_resource& memory, + const std::size_t tx_capacity, + const std::size_t media_count, + const cetl::span media_interfaces) { MediaArray media_array{media_count, &memory}; media_array.reserve(media_count); @@ -310,11 +310,11 @@ class TransportImpl final : public ICanTransport, private TransportDelegate } // namespace detail CETL_NODISCARD inline Expected, FactoryError> makeTransport( - cetl::pmr::memory_resource& memory, - IMultiplexer& multiplexer, - const std::array& media, // TODO: replace with `cetl::span` - const std::size_t tx_capacity, - const cetl::optional local_node_id) + cetl::pmr::memory_resource& memory, + IMultiplexer& multiplexer, + const cetl::span media, + const std::size_t tx_capacity, + const cetl::optional local_node_id) { return detail::TransportImpl::make(memory, multiplexer, media, tx_capacity, local_node_id); } diff --git a/include/libcyphal/transport/defines.hpp b/include/libcyphal/transport/defines.hpp index a40273aab..035901577 100644 --- a/include/libcyphal/transport/defines.hpp +++ b/include/libcyphal/transport/defines.hpp @@ -88,10 +88,6 @@ struct ServiceRxTransfer final DynamicBuffer payload; }; -/// @brief Defines maximum number of media interfaces that can be used in a Cyphal transport. -/// TODO: This is a temporary constant and will be replaced by `cetl::span::size()` (see `makeTransport`). -constexpr std::size_t MaxMediaInterfaces = 3; - } // namespace transport } // namespace libcyphal diff --git a/include/libcyphal/transport/udp/transport.hpp b/include/libcyphal/transport/udp/transport.hpp index ab86f51d5..8784b7c8f 100644 --- a/include/libcyphal/transport/udp/transport.hpp +++ b/include/libcyphal/transport/udp/transport.hpp @@ -102,10 +102,10 @@ class TransportImpl final : public IUdpTransport } // namespace detail CETL_NODISCARD inline Expected, FactoryError> makeTransport( - cetl::pmr::memory_resource& memory, - IMultiplexer& multiplexer, - const std::array media, // TODO: replace with `cetl::span` - const cetl::optional local_node_id) + cetl::pmr::memory_resource& memory, + IMultiplexer& multiplexer, + const cetl::span media, + const cetl::optional local_node_id) { // TODO: Use these! (void) multiplexer; diff --git a/test/unittest/transport/can/test_can_msg_rx_session.cpp b/test/unittest/transport/can/test_can_msg_rx_session.cpp index 3358e8bcf..1066b5e47 100644 --- a/test/unittest/transport/can/test_can_msg_rx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_rx_session.cpp @@ -46,7 +46,9 @@ class TestCanMsgRxSession : public testing::Test CETL_NODISCARD UniquePtr makeTransport(cetl::pmr::memory_resource& mr) { - auto maybe_transport = can::makeTransport(mr, mux_mock_, {&media_mock_}, 0, {}); + std::array media_array{&media_mock_}; + + auto maybe_transport = can::makeTransport(mr, mux_mock_, media_array, 0, {}); EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); return cetl::get>(std::move(maybe_transport)); } diff --git a/test/unittest/transport/can/test_can_msg_tx_session.cpp b/test/unittest/transport/can/test_can_msg_tx_session.cpp index 45d01c0d3..75fdfbaff 100644 --- a/test/unittest/transport/can/test_can_msg_tx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_tx_session.cpp @@ -46,7 +46,9 @@ class TestCanMsgTxSession : public testing::Test CETL_NODISCARD UniquePtr makeTransport(cetl::pmr::memory_resource& mr) { - auto maybe_transport = can::makeTransport(mr, mux_mock_, {&media_mock_}, 0, {}); + std::array media_array{&media_mock_}; + + auto maybe_transport = can::makeTransport(mr, mux_mock_, media_array, 0, {}); EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); return cetl::get>(std::move(maybe_transport)); } diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index c333af428..d11a72efb 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -59,7 +59,8 @@ TEST_F(TestCanTransport, makeTransport_no_memory) // Emulate that there is no memory available for the transport. EXPECT_CALL(mr_mock, do_allocate(sizeof(can::detail::TransportImpl), _)).WillOnce(Return(nullptr)); - auto maybe_transport = makeTransport(mr_mock, mux_mock_, {&media_mock_}, 0, {}); + std::array media_array{&media_mock_}; + auto maybe_transport = makeTransport(mr_mock, mux_mock_, media_array, 0, {}); EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } @@ -67,7 +68,8 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) { // Anonymous node { - auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, 0, {}); + std::array media_array{&media_mock_}; + auto maybe_transport = makeTransport(mr_, mux_mock_, media_array, 0, {}); EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); auto transport = cetl::get>(std::move(maybe_transport)); @@ -78,7 +80,8 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) { const auto node_id = cetl::make_optional(static_cast(42)); - auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, 0, node_id); + std::array media_array{&media_mock_}; + auto maybe_transport = makeTransport(mr_, mux_mock_, media_array, 0, node_id); EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); auto transport = cetl::get>(std::move(maybe_transport)); @@ -90,7 +93,8 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) StrictMock media_mock2; EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(Return(CANARD_MTU_MAX)); - auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_, nullptr, &media_mock2}, 0, {}); + std::array media_array{&media_mock_, nullptr, &media_mock2}; + auto maybe_transport = makeTransport(mr_, mux_mock_, media_array, 0, {}); EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); } @@ -100,7 +104,8 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(Return(CANARD_MTU_MAX)); EXPECT_CALL(media_mock3, getMtu()).WillRepeatedly(Return(CANARD_MTU_MAX)); - auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_, &media_mock2, &media_mock3}, 0, {}); + std::array media_array{&media_mock_, &media_mock2, &media_mock3}; + auto maybe_transport = makeTransport(mr_, mux_mock_, media_array, 0, {}); EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); } } @@ -119,7 +124,8 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) { const auto node_id = cetl::make_optional(static_cast(CANARD_NODE_ID_MAX + 1)); - const auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, 0, node_id); + std::array media_array{&media_mock_}; + const auto maybe_transport = makeTransport(mr_, mux_mock_, media_array, 0, node_id); EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } @@ -127,7 +133,8 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) { const auto node_id = cetl::make_optional(static_cast(CANARD_NODE_ID_UNSET)); - const auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, 0, node_id); + std::array media_array{&media_mock_}; + const auto maybe_transport = makeTransport(mr_, mux_mock_, media_array, 0, node_id); EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } @@ -136,7 +143,8 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) const NodeId too_big = static_cast(std::numeric_limits::max()) + 1; const auto node_id = cetl::make_optional(too_big); - const auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, 0, node_id); + std::array media_array{&media_mock_}; + const auto maybe_transport = makeTransport(mr_, mux_mock_, media_array, 0, node_id); EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } } @@ -146,8 +154,8 @@ TEST_F(TestCanTransport, getProtocolParams) StrictMock media_mock2{}; EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(Return(CANARD_MTU_MAX)); - auto transport = - cetl::get>(makeTransport(mr_, mux_mock_, {&media_mock_, &media_mock2}, 0, {})); + std::array media_array{&media_mock_, &media_mock2}; + auto transport = cetl::get>(makeTransport(mr_, mux_mock_, media_array, 0, {})); EXPECT_CALL(media_mock_, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_FD)); EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_CLASSIC)); @@ -172,7 +180,8 @@ TEST_F(TestCanTransport, getProtocolParams) TEST_F(TestCanTransport, makeMessageRxSession) { - auto transport = cetl::get>(makeTransport(mr_, mux_mock_, {&media_mock_}, 0, {})); + std::array media_array{&media_mock_}; + auto transport = cetl::get>(makeTransport(mr_, mux_mock_, media_array, 0, {})); auto maybe_rx_session = transport->makeMessageRxSession({42, 123}); EXPECT_THAT(maybe_rx_session, VariantWith>(NotNull())); @@ -184,7 +193,8 @@ TEST_F(TestCanTransport, makeMessageRxSession) TEST_F(TestCanTransport, makeMessageRxSession_invalid_subject_id) { - auto transport = cetl::get>(makeTransport(mr_, mux_mock_, {&media_mock_}, 0, {})); + std::array media_array{&media_mock_}; + auto transport = cetl::get>(makeTransport(mr_, mux_mock_, media_array, 0, {})); auto maybe_rx_session = transport->makeMessageRxSession({0, CANARD_SUBJECT_ID_MAX + 1}); EXPECT_THAT(maybe_rx_session, VariantWith(VariantWith(_))); @@ -192,7 +202,8 @@ TEST_F(TestCanTransport, makeMessageRxSession_invalid_subject_id) TEST_F(TestCanTransport, makeMessageTxSession) { - auto transport = cetl::get>(makeTransport(mr_, mux_mock_, {&media_mock_}, 0, {})); + std::array media_array{&media_mock_}; + auto transport = cetl::get>(makeTransport(mr_, mux_mock_, media_array, 0, {})); auto maybe_tx_session = transport->makeMessageTxSession({123}); EXPECT_THAT(maybe_tx_session, VariantWith>(NotNull())); diff --git a/test/unittest/transport/udp/test_udp_transport.cpp b/test/unittest/transport/udp/test_udp_transport.cpp index e019dbc41..889ecd2b1 100644 --- a/test/unittest/transport/udp/test_udp_transport.cpp +++ b/test/unittest/transport/udp/test_udp_transport.cpp @@ -43,7 +43,8 @@ TEST_F(TestUpdTransport, makeTransport) { // Anonymous node { - auto maybe_transport = makeTransport(mr_, mux_mock_, {&media_mock_}, {}); + std::array media_array{&media_mock_}; + auto maybe_transport = makeTransport(mr_, mux_mock_, media_array, {}); EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } } From cdeedd4672de1c1f4ac748b76f3218f14d632e61 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Tue, 23 Apr 2024 07:16:21 +0300 Subject: [PATCH 27/64] mention issue # 223 --- include/libcyphal/transport/can/msg_tx_session.hpp | 1 + include/libcyphal/transport/contiguous_payload.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/include/libcyphal/transport/can/msg_tx_session.hpp b/include/libcyphal/transport/can/msg_tx_session.hpp index e45dd2aec..db4d680a4 100644 --- a/include/libcyphal/transport/can/msg_tx_session.hpp +++ b/include/libcyphal/transport/can/msg_tx_session.hpp @@ -65,6 +65,7 @@ class MessageTxSession final : public IMessageTxSession { // libcanard currently does not support fragmented payloads (at `canardTxPush`). // so we need to concatenate them when there are more than one non-empty fragment. + // See https://github.com/OpenCyphal/libcanard/issues/223 // const transport::detail::ContiguousPayload contiguous_payload{delegate_.memory(), payload_fragments}; if ((contiguous_payload.data() == nullptr) && (contiguous_payload.size() > 0)) diff --git a/include/libcyphal/transport/contiguous_payload.hpp b/include/libcyphal/transport/contiguous_payload.hpp index 0f5030c79..82704826d 100644 --- a/include/libcyphal/transport/contiguous_payload.hpp +++ b/include/libcyphal/transport/contiguous_payload.hpp @@ -23,6 +23,7 @@ namespace detail /// Automatically deallocates memory (if any) when the object is destroyed. /// /// Probably could be deleted when libcanard will start support fragmented payloads (at `canardTxPush`). +/// See https://github.com/OpenCyphal/libcanard/issues/223 /// class ContiguousPayload final { From 8d9ff46c6a4df7b2d33cab841c1febe53c527363 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Tue, 23 Apr 2024 07:57:51 +0300 Subject: [PATCH 28/64] =?UTF-8?q?rename=20`defines.hpp`=20=E2=86=92=20`typ?= =?UTF-8?q?es.hpp`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/libcyphal/transport/can/media.hpp | 2 +- include/libcyphal/transport/session.hpp | 2 +- include/libcyphal/transport/{defines.hpp => types.hpp} | 6 +++--- test/unittest/gtest_helpers.hpp | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) rename include/libcyphal/transport/{defines.hpp => types.hpp} (94%) diff --git a/include/libcyphal/transport/can/media.hpp b/include/libcyphal/transport/can/media.hpp index 7fbb96c88..1035f940c 100644 --- a/include/libcyphal/transport/can/media.hpp +++ b/include/libcyphal/transport/can/media.hpp @@ -8,7 +8,7 @@ #include "libcyphal/types.hpp" #include "libcyphal/transport/errors.hpp" -#include "libcyphal/transport/defines.hpp" +#include "libcyphal/transport/types.hpp" namespace libcyphal { diff --git a/include/libcyphal/transport/session.hpp b/include/libcyphal/transport/session.hpp index a85672acd..7dab5901d 100644 --- a/include/libcyphal/transport/session.hpp +++ b/include/libcyphal/transport/session.hpp @@ -7,7 +7,7 @@ #define LIBCYPHAL_TRANSPORT_SESSION_HPP_INCLUDED #include "libcyphal/runnable.hpp" -#include "defines.hpp" +#include "types.hpp" namespace libcyphal { diff --git a/include/libcyphal/transport/defines.hpp b/include/libcyphal/transport/types.hpp similarity index 94% rename from include/libcyphal/transport/defines.hpp rename to include/libcyphal/transport/types.hpp index fd6b1e823..9bed90846 100644 --- a/include/libcyphal/transport/defines.hpp +++ b/include/libcyphal/transport/types.hpp @@ -3,8 +3,8 @@ /// Copyright Amazon.com Inc. or its affiliates. /// SPDX-License-Identifier: MIT -#ifndef LIBCYPHAL_TRANSPORT_DEFINES_HPP_INCLUDED -#define LIBCYPHAL_TRANSPORT_DEFINES_HPP_INCLUDED +#ifndef LIBCYPHAL_TRANSPORT_TYPES_HPP_INCLUDED +#define LIBCYPHAL_TRANSPORT_TYPES_HPP_INCLUDED #include "scattered_buffer.hpp" @@ -94,4 +94,4 @@ constexpr std::size_t MaxMediaInterfaces = 3; } // namespace transport } // namespace libcyphal -#endif // LIBCYPHAL_TRANSPORT_DEFINES_HPP_INCLUDED +#endif // LIBCYPHAL_TRANSPORT_TYPES_HPP_INCLUDED diff --git a/test/unittest/gtest_helpers.hpp b/test/unittest/gtest_helpers.hpp index fb9120351..e90d05462 100644 --- a/test/unittest/gtest_helpers.hpp +++ b/test/unittest/gtest_helpers.hpp @@ -10,7 +10,7 @@ #define LIBCYPHAL_GTEST_HELPERS_HPP #include -#include +#include #include From 56d77e546fb5c86ebbd83196a8cf245ba90e6ef6 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Tue, 23 Apr 2024 08:00:33 +0300 Subject: [PATCH 29/64] minor fix after merge --- include/libcyphal/transport/contiguous_payload.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libcyphal/transport/contiguous_payload.hpp b/include/libcyphal/transport/contiguous_payload.hpp index 82704826d..86dd093a1 100644 --- a/include/libcyphal/transport/contiguous_payload.hpp +++ b/include/libcyphal/transport/contiguous_payload.hpp @@ -6,7 +6,7 @@ #ifndef LIBCYPHAL_TRANSPORT_CONTIGUOUS_PAYLOAD_HPP_INCLUDED #define LIBCYPHAL_TRANSPORT_CONTIGUOUS_PAYLOAD_HPP_INCLUDED -#include "defines.hpp" +#include "types.hpp" #include "libcyphal/types.hpp" namespace libcyphal From e2d38fae4c702ec7dc95db7ebce64b6ce1174236 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Tue, 23 Apr 2024 16:29:16 +0300 Subject: [PATCH 30/64] implemented msg tx push --- include/libcyphal/transport/can/delegate.hpp | 14 ++++- .../transport/can/msg_rx_session.hpp | 18 +++--- .../transport/can/msg_tx_session.hpp | 39 +++++-------- include/libcyphal/transport/can/transport.hpp | 57 ++++++++++++++++--- test/unittest/tracking_memory_resource.hpp | 42 +++++++++++--- test/unittest/transport/can/media_mock.hpp | 2 +- .../transport/can/test_can_delegate.cpp | 33 ++++++++--- .../transport/can/test_can_msg_rx_session.cpp | 4 +- .../transport/can/test_can_msg_tx_session.cpp | 25 +++++++- .../transport/can/test_can_transport.cpp | 2 + test/unittest/transport/multiplexer_mock.hpp | 2 +- .../transport/test_contiguous_payload.cpp | 2 + test/unittest/transport/udp/media_mock.hpp | 2 +- .../transport/udp/test_udp_transport.cpp | 2 + 14 files changed, 176 insertions(+), 68 deletions(-) diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp index fa07df332..9a51be5cf 100644 --- a/include/libcyphal/transport/can/delegate.hpp +++ b/include/libcyphal/transport/can/delegate.hpp @@ -94,7 +94,7 @@ class TransportDelegate explicit TransportDelegate(cetl::pmr::memory_resource& memory) : memory_{memory} - , canard_instance_(canardInit(canardMemoryAllocate, canardMemoryFree)) + , canard_instance_(::canardInit(canardMemoryAllocate, canardMemoryFree)) { canard_instance().user_reference = this; } @@ -114,10 +114,10 @@ class TransportDelegate return memory_; } - static cetl::optional anyErrorFromCanard(const int8_t result) + static cetl::optional anyErrorFromCanard(const int32_t result) { // Canard error results are negative, so we need to negate them to get the error code. - const auto canard_error = static_cast(-result); + const auto canard_error = static_cast(-result); if (canard_error == CANARD_ERROR_INVALID_ARGUMENT) { @@ -144,6 +144,14 @@ class TransportDelegate memory_.deallocate(memory_header, memory_header->size); } + CETL_NODISCARD virtual cetl::optional sendTransfer(const CanardMicrosecond timestamp, + const CanardTransferMetadata& metadata, + const void* const payload, + const std::size_t payload_size) = 0; + +protected: + ~TransportDelegate() = default; + private: // Until "canardMemFree must provide size" issue #216 is resolved, // we need to store the size of the memory allocated. diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp index b6b614b36..cf24d1dda 100644 --- a/include/libcyphal/transport/can/msg_rx_session.hpp +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -57,12 +57,12 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate : delegate_{delegate} , params_{params} { - const auto result = canardRxSubscribe(&delegate.canard_instance(), - CanardTransferKindMessage, - static_cast(params_.subject_id), - static_cast(params_.extent_bytes), - CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC, - &subscription_); + const auto result = ::canardRxSubscribe(&delegate.canard_instance(), + CanardTransferKindMessage, + static_cast(params_.subject_id), + static_cast(params_.extent_bytes), + CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC, + &subscription_); if (result < 0) { out_error = TransportDelegate::anyErrorFromCanard(result); @@ -78,9 +78,9 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate { if (is_subscribed_) { - canardRxUnsubscribe(&delegate_.canard_instance(), - CanardTransferKindMessage, - static_cast(params_.subject_id)); + ::canardRxUnsubscribe(&delegate_.canard_instance(), + CanardTransferKindMessage, + static_cast(params_.subject_id)); } } diff --git a/include/libcyphal/transport/can/msg_tx_session.hpp b/include/libcyphal/transport/can/msg_tx_session.hpp index db4d680a4..755ffc627 100644 --- a/include/libcyphal/transport/can/msg_tx_session.hpp +++ b/include/libcyphal/transport/can/msg_tx_session.hpp @@ -73,31 +73,20 @@ class MessageTxSession final : public IMessageTxSession return MemoryError{}; } - // TransferMetadata - // TransferId transfer_id; - // TimePoint timestamp; - // Priority priority; - - // int32_t canardTxPush(CanardTxQueue* const que, - // CanardInstance* const ins, - // const CanardMicrosecond tx_deadline_usec, - // const CanardTransferMetadata* const metadata, - // const size_t payload_size, - // const void* const payload) - // - // const auto result = canardTxPush(&delegate_.canard_instance(), - // CanardTransferKindMessage, - // static_cast(params_.subject_id), - // static_cast(metadata.extent_bytes), - // &transfer_id, - // ×tamp, - // &priority, - // payload_fragments.data(), - // payload_fragments.size()); - - (void) metadata; - - return NotImplementedError{}; + const auto timestamp_us = + std::chrono::duration_cast(metadata.timestamp.time_since_epoch()); + + const auto canard_timestamp = static_cast(timestamp_us.count()); + const auto canard_metadata = CanardTransferMetadata{static_cast(metadata.priority), + CanardTransferKindMessage, + static_cast(params_.subject_id), + CANARD_NODE_ID_UNSET, + static_cast(metadata.transfer_id)}; + + return delegate_.sendTransfer(canard_timestamp, + canard_metadata, + contiguous_payload.data(), + contiguous_payload.size()); } // MARK: IRunnable diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index 8ff29605d..9372cf19c 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -98,6 +98,14 @@ class TransportImpl final : public ICanTransport, private TransportDelegate canard_instance().node_id = canard_node_id; } + ~TransportImpl() override + { + for (Media& media : media_array_) + { + flushCanardTxQueue(media.canard_tx_queue); + } + } + private: // MARK: ITransport @@ -197,12 +205,12 @@ class TransportImpl final : public ICanTransport, private TransportDelegate CanardRxSubscription* out_subscription{}; // TODO: Handle errors. - const auto result = canardRxAccept(&canard_instance(), - static_cast(timestamp_us.count()), - &canard_frame, - media.index, - &out_transfer, - &out_subscription); + const auto result = ::canardRxAccept(&canard_instance(), + static_cast(timestamp_us.count()), + &canard_frame, + media.index, + &out_transfer, + &out_subscription); if (result > 0) { CETL_DEBUG_ASSERT(out_subscription != nullptr, "Expected subscription."); @@ -224,6 +232,28 @@ class TransportImpl final : public ICanTransport, private TransportDelegate return *this; } + CETL_NODISCARD cetl::optional sendTransfer(const CanardMicrosecond timestamp, + const CanardTransferMetadata& metadata, + const void* const payload, + const std::size_t payload_size) override + { + cetl::optional maybe_error{}; + + for (Media& media : media_array_) + { + media.canard_tx_queue.mtu_bytes = media.interface.getMtu(); + + const auto result = + ::canardTxPush(&media.canard_tx_queue, &canard_instance(), timestamp, &metadata, payload_size, payload); + if (result < 0) + { + maybe_error = TransportDelegate::anyErrorFromCanard(result); + } + } + + return maybe_error; + } + // MARK: Privates: class Media final @@ -232,7 +262,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate Media(const std::size_t _index, IMedia& _interface, const std::size_t tx_capacity) : index{static_cast(_index)} , interface{_interface} - , canard_tx_queue{canardTxInit(tx_capacity, _interface.getMtu())} + , canard_tx_queue{::canardTxInit(tx_capacity, _interface.getMtu())} { } Media(const Media&) = delete; @@ -266,7 +296,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate return ArgumentError{}; } - const auto hasSubscription = canardRxHasSubscription(&canard_instance(), transfer_kind, port_id); + const auto hasSubscription = ::canardRxHasSubscription(&canard_instance(), transfer_kind, port_id); if (hasSubscription < 0) { return anyErrorFromCanard(hasSubscription); @@ -301,9 +331,18 @@ class TransportImpl final : public ICanTransport, private TransportDelegate return media_array; } + void flushCanardTxQueue(CanardTxQueue& canard_tx_queue) + { + while (const auto maybe_item = ::canardTxPeek(&canard_tx_queue)) + { + auto item = ::canardTxPop(&canard_tx_queue, maybe_item); + canardMemoryFree(item); + } + } + // MARK: Data members: - const MediaArray media_array_; + MediaArray media_array_; }; // TransportImpl diff --git a/test/unittest/tracking_memory_resource.hpp b/test/unittest/tracking_memory_resource.hpp index afb0c075c..9c6707c48 100644 --- a/test/unittest/tracking_memory_resource.hpp +++ b/test/unittest/tracking_memory_resource.hpp @@ -11,11 +11,26 @@ #include +#include +#include + class TrackingMemoryResource final : public cetl::pmr::memory_resource { public: - std::size_t total_allocated_bytes = 0; - std::size_t total_deallocated_bytes = 0; + struct Allocation final + { + std::size_t size; + void* pointer; + + friend void PrintTo(const Allocation& alloc, std::ostream* os) + { + *os << "\n{ptr=0x" << std::hex << alloc.pointer << ", size=" << std::dec << alloc.size << "}"; + } + }; + + std::vector allocations{}; + std::size_t total_allocated_bytes = 0; + std::size_t total_deallocated_bytes = 0; private: // MARK: cetl::pmr::memory_resource @@ -30,22 +45,31 @@ class TrackingMemoryResource final : public cetl::pmr::memory_resource return nullptr; } + auto ptr = std::malloc(size_bytes); + total_allocated_bytes += size_bytes; - return std::malloc(size_bytes); + allocations.push_back({size_bytes, ptr}); + + return ptr; } - void do_deallocate(void* p, std::size_t size_bytes, std::size_t) override + void do_deallocate(void* ptr, std::size_t size_bytes, std::size_t) override { + std::free(ptr); + + auto prev_alloc = std::find_if(allocations.cbegin(), allocations.cend(), [&](const auto& alloc) { + return alloc.pointer == ptr; + }); + if (prev_alloc != allocations.cend()) + { + allocations.erase(prev_alloc); + } total_deallocated_bytes += size_bytes; - std::free(p); } #if (__cplusplus < CETL_CPP_STANDARD_17) - void* do_reallocate(void* ptr, - std::size_t old_size_bytes, - std::size_t new_size_bytes, - std::size_t) override + void* do_reallocate(void* ptr, std::size_t old_size_bytes, std::size_t new_size_bytes, std::size_t) override { total_allocated_bytes -= old_size_bytes; total_allocated_bytes += new_size_bytes; diff --git a/test/unittest/transport/can/media_mock.hpp b/test/unittest/transport/can/media_mock.hpp index d1f74ed9e..b317a7a39 100644 --- a/test/unittest/transport/can/media_mock.hpp +++ b/test/unittest/transport/can/media_mock.hpp @@ -20,7 +20,7 @@ namespace can class MediaMock : public IMedia { public: - MediaMock() = default; + MediaMock() = default; virtual ~MediaMock() = default; MOCK_METHOD(std::size_t, getMtu, (), (const, noexcept, override)); diff --git a/test/unittest/transport/can/test_can_delegate.cpp b/test/unittest/transport/can/test_can_delegate.cpp index fda724897..cdf32ad2f 100644 --- a/test/unittest/transport/can/test_can_delegate.cpp +++ b/test/unittest/transport/can/test_can_delegate.cpp @@ -19,8 +19,9 @@ using namespace libcyphal::transport::can; using testing::_; using testing::Eq; using testing::Each; -using testing::IsNull; using testing::Return; +using testing::IsNull; +using testing::IsEmpty; using testing::Optional; using testing::StrictMock; using testing::ElementsAre; @@ -29,8 +30,26 @@ using testing::VariantWith; class TestCanDelegate : public testing::Test { protected: + class TransportDelegateImpl : public detail::TransportDelegate + { + public: + explicit TransportDelegateImpl(cetl::pmr::memory_resource& memory) + : detail::TransportDelegate{memory} + { + } + virtual ~TransportDelegateImpl() = default; + + MOCK_METHOD((cetl::optional), + sendTransfer, + (const CanardMicrosecond timestamp, + const CanardTransferMetadata& metadata, + const void* const payload, + const std::size_t payload_size)); + }; + void TearDown() override { + EXPECT_THAT(mr_.allocations, IsEmpty()); EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); } @@ -45,8 +64,8 @@ TEST_F(TestCanDelegate, CanardMemory_copy) { using CanardMemory = detail::TransportDelegate::CanardMemory; - detail::TransportDelegate delegate{mr_}; - auto& canard_instance = delegate.canard_instance(); + TransportDelegateImpl delegate{mr_}; + auto& canard_instance = delegate.canard_instance(); const auto payload = static_cast(canard_instance.memory_allocate(&canard_instance, 8)); std::iota(payload, payload + 8, '0'); @@ -103,8 +122,8 @@ TEST_F(TestCanDelegate, CanardMemory_copy_on_moved) { using CanardMemory = detail::TransportDelegate::CanardMemory; - detail::TransportDelegate delegate{mr_}; - auto& canard_instance = delegate.canard_instance(); + TransportDelegateImpl delegate{mr_}; + auto& canard_instance = delegate.canard_instance(); const std::size_t payload_size = 4; const auto payload = static_cast(canard_instance.memory_allocate(&canard_instance, payload_size)); @@ -149,8 +168,8 @@ TEST_F(TestCanDelegate, canardMemoryAllocate_no_memory) { StrictMock mr_mock{}; - detail::TransportDelegate delegate{mr_mock}; - auto& canard_instance = delegate.canard_instance(); + TransportDelegateImpl delegate{mr_mock}; + auto& canard_instance = delegate.canard_instance(); // Emulate that there is no memory at all. EXPECT_CALL(mr_mock, do_allocate(_, _)).WillOnce(Return(nullptr)); diff --git a/test/unittest/transport/can/test_can_msg_rx_session.cpp b/test/unittest/transport/can/test_can_msg_rx_session.cpp index 1066b5e47..3c5775635 100644 --- a/test/unittest/transport/can/test_can_msg_rx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_rx_session.cpp @@ -23,6 +23,7 @@ using testing::_; using testing::Eq; using testing::Return; using testing::IsNull; +using testing::IsEmpty; using testing::NotNull; using testing::Optional; using testing::StrictMock; @@ -40,6 +41,7 @@ class TestCanMsgRxSession : public testing::Test void TearDown() override { + EXPECT_THAT(mr_.allocations, IsEmpty()); // TODO: Uncomment this when PMR deleter is fixed. // EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); } @@ -92,7 +94,7 @@ TEST_F(TestCanMsgRxSession, make_no_memory) EXPECT_THAT(maybe_session, VariantWith(VariantWith(_))); } -TEST_F(TestCanMsgRxSession, run_receive) +TEST_F(TestCanMsgRxSession, run_and_receive) { auto transport = makeTransport(mr_); diff --git a/test/unittest/transport/can/test_can_msg_tx_session.cpp b/test/unittest/transport/can/test_can_msg_tx_session.cpp index 75fdfbaff..f0c44f54c 100644 --- a/test/unittest/transport/can/test_can_msg_tx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_tx_session.cpp @@ -23,6 +23,7 @@ using testing::_; using testing::Eq; using testing::Return; using testing::IsNull; +using testing::IsEmpty; using testing::NotNull; using testing::Optional; using testing::StrictMock; @@ -40,15 +41,17 @@ class TestCanMsgTxSession : public testing::Test void TearDown() override { + EXPECT_THAT(mr_.allocations, IsEmpty()); // TODO: Uncomment this when PMR deleter is fixed. // EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); } - CETL_NODISCARD UniquePtr makeTransport(cetl::pmr::memory_resource& mr) + CETL_NODISCARD UniquePtr makeTransport(cetl::pmr::memory_resource& mr, + const std::size_t tx_capacity = 16) { std::array media_array{&media_mock_}; - auto maybe_transport = can::makeTransport(mr, mux_mock_, media_array, 0, {}); + auto maybe_transport = can::makeTransport(mr, mux_mock_, media_array, tx_capacity, {}); EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); return cetl::get>(std::move(maybe_transport)); } @@ -88,4 +91,22 @@ TEST_F(TestCanMsgTxSession, make_no_memory) EXPECT_THAT(maybe_session, VariantWith(VariantWith(_))); } +TEST_F(TestCanMsgTxSession, send_empty_payload_and_no_transport_run) +{ + StrictMock mr_mock{}; + mr_mock.redirectExpectedCallsTo(mr_); + + auto transport = makeTransport(mr_mock); + + auto maybe_session = transport->makeMessageTxSession({123}); + EXPECT_THAT(maybe_session, VariantWith>(_)); + auto session = cetl::get>(std::move(maybe_session)); + EXPECT_THAT(session, NotNull()); + + const PayloadFragments empty_payload{}; + + auto maybe_error = session->send({0x1AF52, TimePoint{1s}, Priority::Low}, empty_payload); + EXPECT_THAT(maybe_error, Eq(cetl::nullopt)); +} + } // namespace diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index d11a72efb..6839ffe2e 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -23,6 +23,7 @@ using testing::_; using testing::Eq; using testing::Return; using testing::IsNull; +using testing::IsEmpty; using testing::NotNull; using testing::Optional; using testing::StrictMock; @@ -38,6 +39,7 @@ class TestCanTransport : public testing::Test void TearDown() override { + EXPECT_THAT(mr_.allocations, IsEmpty()); // TODO: Uncomment this when PMR deleter is fixed. // EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); } diff --git a/test/unittest/transport/multiplexer_mock.hpp b/test/unittest/transport/multiplexer_mock.hpp index 88ba9c5a6..70b0c369d 100644 --- a/test/unittest/transport/multiplexer_mock.hpp +++ b/test/unittest/transport/multiplexer_mock.hpp @@ -18,7 +18,7 @@ namespace transport class MultiplexerMock : public IMultiplexer { public: - MultiplexerMock() = default; + MultiplexerMock() = default; virtual ~MultiplexerMock() = default; }; diff --git a/test/unittest/transport/test_contiguous_payload.cpp b/test/unittest/transport/test_contiguous_payload.cpp index 76afd484b..29b0ab9f7 100644 --- a/test/unittest/transport/test_contiguous_payload.cpp +++ b/test/unittest/transport/test_contiguous_payload.cpp @@ -19,6 +19,7 @@ using ContiguousPayload = libcyphal::transport::detail::ContiguousPayload; using testing::_; using testing::Eq; using testing::IsNull; +using testing::IsEmpty; using testing::NotNull; using testing::Return; using testing::StrictMock; @@ -29,6 +30,7 @@ class TestContiguousPayload : public testing::Test protected: void TearDown() override { + EXPECT_THAT(mr_.allocations, IsEmpty()); EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); } diff --git a/test/unittest/transport/udp/media_mock.hpp b/test/unittest/transport/udp/media_mock.hpp index 3dd5f99ca..2a981463e 100644 --- a/test/unittest/transport/udp/media_mock.hpp +++ b/test/unittest/transport/udp/media_mock.hpp @@ -20,7 +20,7 @@ namespace udp class MediaMock : public IMedia { public: - MediaMock() = default; + MediaMock() = default; virtual ~MediaMock() = default; }; // MediaMock diff --git a/test/unittest/transport/udp/test_udp_transport.cpp b/test/unittest/transport/udp/test_udp_transport.cpp index 889ecd2b1..95d1feeb5 100644 --- a/test/unittest/transport/udp/test_udp_transport.cpp +++ b/test/unittest/transport/udp/test_udp_transport.cpp @@ -18,6 +18,7 @@ using namespace libcyphal::transport; using namespace libcyphal::transport::udp; using testing::_; +using testing::IsEmpty; using testing::StrictMock; using testing::VariantWith; @@ -26,6 +27,7 @@ class TestUpdTransport : public testing::Test protected: void TearDown() override { + EXPECT_THAT(mr_.allocations, IsEmpty()); // TODO: Uncomment this when PMR deleter is fixed. // EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); } From 01cfe9d850c4ae30048fa093f4bfbe8aef5058f1 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Tue, 23 Apr 2024 16:40:44 +0300 Subject: [PATCH 31/64] minor fix #verification --- include/libcyphal/transport/can/transport.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index 9372cf19c..201a689a4 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -121,8 +121,9 @@ class TransportImpl final : public ICanTransport, private TransportDelegate CETL_NODISCARD ProtocolParams getProtocolParams() const noexcept override { const auto min_mtu = - reduceMedia(std::numeric_limits::max(), - [](const std::size_t mtu, IMedia& media) { return std::min(mtu, media.getMtu()); }); + reduceMedia(std::numeric_limits::max(), [](const std::size_t mtu, const Media& media) { + return std::min(mtu, media.interface.getMtu()); + }); return ProtocolParams{1 << CANARD_TRANSFER_ID_BIT_LENGTH, min_mtu, CANARD_NODE_ID_MAX + 1}; } @@ -282,7 +283,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate T acc = init; for (const Media& media : media_array_) { - acc = reducer(std::forward(acc), media.interface); + acc = reducer(std::forward(acc), media); } return acc; } From b8696a6a564f9be3c0ff591c7e142ca9583e8009 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Tue, 23 Apr 2024 17:18:54 +0300 Subject: [PATCH 32/64] added `setSendTimeout(const Duration timeout)` --- include/libcyphal/transport/can/delegate.hpp | 2 +- .../libcyphal/transport/can/msg_tx_session.hpp | 17 ++++++++++++----- include/libcyphal/transport/can/transport.hpp | 4 ++-- include/libcyphal/transport/msg_sessions.hpp | 2 +- include/libcyphal/transport/session.hpp | 13 +++++++++++++ include/libcyphal/transport/svc_sessions.hpp | 4 ++-- .../transport/can/test_can_delegate.cpp | 2 +- 7 files changed, 32 insertions(+), 12 deletions(-) diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp index 9a51be5cf..3c553bedb 100644 --- a/include/libcyphal/transport/can/delegate.hpp +++ b/include/libcyphal/transport/can/delegate.hpp @@ -144,7 +144,7 @@ class TransportDelegate memory_.deallocate(memory_header, memory_header->size); } - CETL_NODISCARD virtual cetl::optional sendTransfer(const CanardMicrosecond timestamp, + CETL_NODISCARD virtual cetl::optional sendTransfer(const CanardMicrosecond deadline, const CanardTransferMetadata& metadata, const void* const payload, const std::size_t payload_size) = 0; diff --git a/include/libcyphal/transport/can/msg_tx_session.hpp b/include/libcyphal/transport/can/msg_tx_session.hpp index 755ffc627..a330dd4f9 100644 --- a/include/libcyphal/transport/can/msg_tx_session.hpp +++ b/include/libcyphal/transport/can/msg_tx_session.hpp @@ -53,6 +53,13 @@ class MessageTxSession final : public IMessageTxSession } private: + // MARK: ITxSession + + void setSendTimeout(const Duration timeout) override + { + send_timeout_ = timeout; + } + // MARK: IMessageTxSession CETL_NODISCARD MessageTxParams getParams() const noexcept override @@ -73,17 +80,16 @@ class MessageTxSession final : public IMessageTxSession return MemoryError{}; } - const auto timestamp_us = - std::chrono::duration_cast(metadata.timestamp.time_since_epoch()); + const TimePoint deadline = metadata.timestamp + send_timeout_; + const auto deadline_us = std::chrono::duration_cast(deadline.time_since_epoch()); - const auto canard_timestamp = static_cast(timestamp_us.count()); - const auto canard_metadata = CanardTransferMetadata{static_cast(metadata.priority), + const auto canard_metadata = CanardTransferMetadata{static_cast(metadata.priority), CanardTransferKindMessage, static_cast(params_.subject_id), CANARD_NODE_ID_UNSET, static_cast(metadata.transfer_id)}; - return delegate_.sendTransfer(canard_timestamp, + return delegate_.sendTransfer(static_cast(deadline_us.count()), canard_metadata, contiguous_payload.data(), contiguous_payload.size()); @@ -97,6 +103,7 @@ class MessageTxSession final : public IMessageTxSession TransportDelegate& delegate_; const MessageTxParams params_; + Duration send_timeout_ = std::chrono::seconds{1}; }; // MessageTxSession diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index 201a689a4..ad003ed7e 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -233,7 +233,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate return *this; } - CETL_NODISCARD cetl::optional sendTransfer(const CanardMicrosecond timestamp, + CETL_NODISCARD cetl::optional sendTransfer(const CanardMicrosecond deadline, const CanardTransferMetadata& metadata, const void* const payload, const std::size_t payload_size) override @@ -245,7 +245,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate media.canard_tx_queue.mtu_bytes = media.interface.getMtu(); const auto result = - ::canardTxPush(&media.canard_tx_queue, &canard_instance(), timestamp, &metadata, payload_size, payload); + ::canardTxPush(&media.canard_tx_queue, &canard_instance(), deadline, &metadata, payload_size, payload); if (result < 0) { maybe_error = TransportDelegate::anyErrorFromCanard(result); diff --git a/include/libcyphal/transport/msg_sessions.hpp b/include/libcyphal/transport/msg_sessions.hpp index b6311cd58..7732ed739 100644 --- a/include/libcyphal/transport/msg_sessions.hpp +++ b/include/libcyphal/transport/msg_sessions.hpp @@ -39,7 +39,7 @@ class IMessageRxSession : public IRxSession }; // IMessageRxSession -class IMessageTxSession : public IRunnable +class IMessageTxSession : public ITxSession { public: CETL_NODISCARD virtual MessageTxParams getParams() const noexcept = 0; diff --git a/include/libcyphal/transport/session.hpp b/include/libcyphal/transport/session.hpp index 7dab5901d..7a94d4bfb 100644 --- a/include/libcyphal/transport/session.hpp +++ b/include/libcyphal/transport/session.hpp @@ -25,6 +25,19 @@ class IRxSession : public ISession virtual void setTransferIdTimeout(const Duration timeout) = 0; }; +class ITxSession : public ISession +{ +public: + /// @brief Sets the timeout for a transmission. + /// + /// The value is added to the original transfer timestamp to determine its deadline. + /// Any transfer that exceeded this deadline would be dropped by the transport. + /// + /// @param timeout - Positive duration for transmission timeout. Default value is 1 second. + /// + virtual void setSendTimeout(const Duration timeout) = 0; +}; + } // namespace transport } // namespace libcyphal diff --git a/include/libcyphal/transport/svc_sessions.hpp b/include/libcyphal/transport/svc_sessions.hpp index 462f2f07e..d2cd795ed 100644 --- a/include/libcyphal/transport/svc_sessions.hpp +++ b/include/libcyphal/transport/svc_sessions.hpp @@ -55,7 +55,7 @@ class IRequestRxSession : public ISvcRxSession CETL_NODISCARD virtual RequestRxParams getParams() const noexcept = 0; }; -class IRequestTxSession : public ISession +class IRequestTxSession : public ITxSession { public: CETL_NODISCARD virtual RequestTxParams getParams() const noexcept = 0; @@ -76,7 +76,7 @@ class IResponseRxSession : public ISvcRxSession CETL_NODISCARD virtual ResponseRxParams getParams() const noexcept = 0; }; -class IResponseTxSession : public ISession +class IResponseTxSession : public ITxSession { public: CETL_NODISCARD virtual ResponseTxParams getParams() const noexcept = 0; diff --git a/test/unittest/transport/can/test_can_delegate.cpp b/test/unittest/transport/can/test_can_delegate.cpp index cdf32ad2f..fcee15809 100644 --- a/test/unittest/transport/can/test_can_delegate.cpp +++ b/test/unittest/transport/can/test_can_delegate.cpp @@ -41,7 +41,7 @@ class TestCanDelegate : public testing::Test MOCK_METHOD((cetl::optional), sendTransfer, - (const CanardMicrosecond timestamp, + (const CanardMicrosecond deadline, const CanardTransferMetadata& metadata, const void* const payload, const std::size_t payload_size)); From 362dafb4e0865895a750d81126fcf1b6ed4558db Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Wed, 24 Apr 2024 22:03:44 +0300 Subject: [PATCH 33/64] cover tx send & run #verification --- include/libcyphal/transport/can/delegate.hpp | 12 +- include/libcyphal/transport/can/transport.hpp | 131 +++++++--- test/unittest/gtest_helpers.hpp | 233 ++++++++++++++++++ .../transport/can/test_can_msg_rx_session.cpp | 19 +- .../transport/can/test_can_msg_tx_session.cpp | 184 +++++++++++++- 5 files changed, 522 insertions(+), 57 deletions(-) diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp index 3c553bedb..ab4a5062c 100644 --- a/include/libcyphal/transport/can/delegate.hpp +++ b/include/libcyphal/transport/can/delegate.hpp @@ -52,7 +52,7 @@ class TransportDelegate { if (buffer_ != nullptr) { - delegate_.canardMemoryFree(buffer_); + delegate_.freeCanardMemory(buffer_); } } @@ -94,7 +94,7 @@ class TransportDelegate explicit TransportDelegate(cetl::pmr::memory_resource& memory) : memory_{memory} - , canard_instance_(::canardInit(canardMemoryAllocate, canardMemoryFree)) + , canard_instance_(::canardInit(allocateMemoryForCanard, freeCanardMemory)) { canard_instance().user_reference = this; } @@ -131,7 +131,7 @@ class TransportDelegate return {}; } - void canardMemoryFree(void* const pointer) + void freeCanardMemory(void* const pointer) { if (pointer == nullptr) { @@ -171,7 +171,7 @@ class TransportDelegate return *static_cast(ins->user_reference); } - CETL_NODISCARD static void* canardMemoryAllocate(CanardInstance* ins, size_t amount) + CETL_NODISCARD static void* allocateMemoryForCanard(CanardInstance* ins, size_t amount) { auto& self = getSelfFrom(ins); @@ -189,10 +189,10 @@ class TransportDelegate return ++memory_header; } - static void canardMemoryFree(CanardInstance* ins, void* pointer) + static void freeCanardMemory(CanardInstance* ins, void* pointer) { auto& self = getSelfFrom(ins); - self.canardMemoryFree(pointer); + self.freeCanardMemory(pointer); } // MARK: Data members: diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index ad003ed7e..b997bed7e 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -184,46 +184,10 @@ class TransportImpl final : public ICanTransport, private TransportDelegate // MARK: IRunnable - void run(const TimePoint) override + void run(const TimePoint now) override { - std::array payload{}; - - for (const Media& media : media_array_) - { - // TODO: Handle errors. - const auto pop_result = media.interface.pop(payload); - if (const auto opt_rx_meta = cetl::get_if>(&pop_result)) - { - if (opt_rx_meta->has_value()) - { - const auto& rx_meta = opt_rx_meta->value(); - - const auto timestamp_us = - std::chrono::duration_cast(rx_meta.timestamp.time_since_epoch()); - const CanardFrame canard_frame{rx_meta.can_id, rx_meta.payload_size, payload.cbegin()}; - - CanardRxTransfer out_transfer{}; - CanardRxSubscription* out_subscription{}; - - // TODO: Handle errors. - const auto result = ::canardRxAccept(&canard_instance(), - static_cast(timestamp_us.count()), - &canard_frame, - media.index, - &out_transfer, - &out_subscription); - if (result > 0) - { - CETL_DEBUG_ASSERT(out_subscription != nullptr, "Expected subscription."); - CETL_DEBUG_ASSERT(out_subscription->user_reference != nullptr, "Expected session delegate."); - - const auto delegate = static_cast(out_subscription->user_reference); - delegate->acceptRxTransfer(out_transfer); - } - } - } - - } // for each media + runMediaTransmit(now); + runMediaReceive(); } // MARK: TransportDelegate @@ -238,6 +202,9 @@ class TransportImpl final : public ICanTransport, private TransportDelegate const void* const payload, const std::size_t payload_size) override { + // TODO: Rework error handling strategy. + // Currently, we return the last error encountered, but we should consider all errors somehow. + // cetl::optional maybe_error{}; for (Media& media : media_array_) @@ -337,10 +304,94 @@ class TransportImpl final : public ICanTransport, private TransportDelegate while (const auto maybe_item = ::canardTxPeek(&canard_tx_queue)) { auto item = ::canardTxPop(&canard_tx_queue, maybe_item); - canardMemoryFree(item); + freeCanardMemory(item); } } + void runMediaReceive() + { + std::array payload{}; + + for (const Media& media : media_array_) + { + // TODO: Handle errors. + const auto pop_result = media.interface.pop(payload); + if (const auto opt_rx_meta = cetl::get_if>(&pop_result)) + { + if (opt_rx_meta->has_value()) + { + const auto& rx_meta = opt_rx_meta->value(); + + const auto timestamp_us = + std::chrono::duration_cast(rx_meta.timestamp.time_since_epoch()); + const CanardFrame canard_frame{rx_meta.can_id, rx_meta.payload_size, payload.cbegin()}; + + CanardRxTransfer out_transfer{}; + CanardRxSubscription* out_subscription{}; + + // TODO: Handle errors. + const auto result = ::canardRxAccept(&canard_instance(), + static_cast(timestamp_us.count()), + &canard_frame, + media.index, + &out_transfer, + &out_subscription); + if (result > 0) + { + CETL_DEBUG_ASSERT(out_subscription != nullptr, "Expected subscription."); + CETL_DEBUG_ASSERT(out_subscription->user_reference != nullptr, "Expected session delegate."); + + const auto delegate = static_cast(out_subscription->user_reference); + delegate->acceptRxTransfer(out_transfer); + } + } + } + + } // for each media + } + + void runMediaTransmit(const TimePoint now) + { + for (Media& media : media_array_) + { + while (const auto tx_item = ::canardTxPeek(&media.canard_tx_queue)) + { + // We are dropping any TX item that has expired. + // Otherwise, we would send it to the media interface. + // We use strictly `<` (instead of `<=`) to give this frame a chance (one extra 1us) at media level. + // + const auto deadline = TimePoint{std::chrono::microseconds{tx_item->tx_deadline_usec}}; + if (now < deadline) + { + const cetl::span payload{static_cast(tx_item->frame.payload), + tx_item->frame.payload_size}; + const auto maybe_pushed = + media.interface.push(deadline, static_cast(tx_item->frame.extended_can_id), payload); + if (const auto is_pushed = cetl::get_if(&maybe_pushed)) + { + if (!*is_pushed) + { + // Media interface is busy, so we will try again with it later (on next `run`). + break; + } + } + else + { + // In case of media error we are going to drop this frame + // (b/c it looks like media can't handle this frame), + // but we will continue to process other frames. + + // TODO: Add error reporting somehow. + } + } + + freeCanardMemory(::canardTxPop(&media.canard_tx_queue, tx_item)); + + } // for each frame + + } // for each media + } + // MARK: Data members: MediaArray media_array_; diff --git a/test/unittest/gtest_helpers.hpp b/test/unittest/gtest_helpers.hpp index e90d05462..7d39a1788 100644 --- a/test/unittest/gtest_helpers.hpp +++ b/test/unittest/gtest_helpers.hpp @@ -13,6 +13,57 @@ #include #include +#include + +// MARK: - GTest Printers: + +namespace std +{ +#if (__cplusplus >= CETL_CPP_STANDARD_17) +inline std::ostream& operator<<(std::ostream& os, const std::byte& b) +{ + return os << "0x" << std::uppercase << std::hex << std::setw(2) << std::setfill('0') + << static_cast(b); +} +#endif +#if (__cplusplus >= CETL_CPP_STANDARD_20) +template +inline std::ostream& operator<<(std::ostream& os, const std::span& items) +{ + os << "{size=" << items.size() << ", data=["; + for (const auto& item : items) + { + os << testing::PrintToString(item) << ", "; + } + return os << ")}"; +} +#endif +} // namespace std + +namespace cetl +{ +namespace pf17 +{ +inline std::ostream& operator<<(std::ostream& os, const cetl::byte& b) +{ + return os << "0x" << std::uppercase << std::hex << std::setw(2) << std::setfill('0') + << static_cast(b); +} +} // namespace pf17 +namespace pf20 +{ +template +inline std::ostream& operator<<(std::ostream& os, const cetl::span& items) +{ + os << "{size=" << items.size() << ", data=["; + for (const auto& item : items) + { + os << testing::PrintToString(item) << ", "; + } + return os << ")}"; +} +} // namespace pf20 +} // namespace cetl namespace libcyphal { @@ -47,6 +98,188 @@ inline void PrintTo(const Priority priority, std::ostream* os) *os << names[static_cast(priority)]; } +} // namespace transport + +// MARK: - GTest Matchers: + +namespace transport +{ +namespace can +{ + +class PriorityOfCanIdMatcher +{ +public: + using is_gtest_matcher = void; + + explicit PriorityOfCanIdMatcher(Priority priority) + : priority_{priority} + { + } + + bool MatchAndExplain(const CanId& can_id, std::ostream* os) const + { + const auto priority = static_cast((can_id >> 26) & 0b111); + if (os) + { + *os << "priority='" << testing::PrintToString(priority) << "'"; + } + return priority == priority_; + } + void DescribeTo(std::ostream* os) const + { + *os << "priority=='" << testing::PrintToString(priority_) << "'"; + } + void DescribeNegationTo(std::ostream* os) const + { + *os << "priority!='" << testing::PrintToString(priority_) << "'"; + } + +private: + const Priority priority_; +}; +inline testing::Matcher PriorityOfCanIdEq(Priority priority) +{ + return PriorityOfCanIdMatcher(priority); +} + +class IsServiceCanIdMatcher +{ +public: + using is_gtest_matcher = void; + + explicit IsServiceCanIdMatcher(bool is_service) + : is_service_{is_service} + { + } + + bool MatchAndExplain(const CanId& can_id, std::ostream* os) const + { + const auto is_service = (can_id & 1 << 25) != 0; + if (os) + { + *os << "is_service=" << std::boolalpha << is_service; + } + return is_service == is_service_; + } + void DescribeTo(std::ostream* os) const + { + *os << "is_service==" << std::boolalpha << is_service_; + } + void DescribeNegationTo(std::ostream* os) const + { + *os << "is_service!=" << std::boolalpha << is_service_; + } + +private: + const bool is_service_; +}; +inline testing::Matcher IsServiceCanId(bool is_service = true) +{ + return IsServiceCanIdMatcher(is_service); +} +inline testing::Matcher IsMessageCanId(bool is_message = true) +{ + return IsServiceCanIdMatcher(!is_message); +} + +class SubjectOfCanIdMatcher +{ +public: + using is_gtest_matcher = void; + + explicit SubjectOfCanIdMatcher(PortId subject_id) + : subject_id_{subject_id} + { + } + + bool MatchAndExplain(const CanId& can_id, std::ostream* os) const + { + const auto subject_id = (can_id >> 8) & CANARD_SUBJECT_ID_MAX; + if (os) + { + *os << "subject_id=" << subject_id; + } + return subject_id == subject_id_; + } + void DescribeTo(std::ostream* os) const + { + *os << "subject_id==" << subject_id_; + } + void DescribeNegationTo(std::ostream* os) const + { + *os << "subject_id!=" << subject_id_; + } + +private: + const PortId subject_id_; +}; +inline testing::Matcher SubjectOfCanIdEq(PortId subject_id) +{ + return SubjectOfCanIdMatcher(subject_id); +} + +class FrameLastByteMatcher +{ +public: + using is_gtest_matcher = void; + + explicit FrameLastByteMatcher(TransferId transfer_id, bool is_start, bool is_end, bool is_toggle) + : transfer_id_{static_cast(transfer_id & CANARD_TRANSFER_ID_MAX)} + , is_start_{is_start} + , is_end_{is_end} + , is_toggle_{is_toggle} + { + } + + bool MatchAndExplain(const cetl::byte& last_byte, std::ostream* os) const + { + const auto byte_value = static_cast(last_byte); + const auto transfer_id = byte_value & CANARD_TRANSFER_ID_MAX; + const auto is_start = (byte_value & 1 << 7) != 0; + const auto is_end = (byte_value & 1 << 6) != 0; + const auto is_toggle = (byte_value & 1 << 5) != 0; + if (os) + { + *os << "{"; + *os << "transfer_id=" << static_cast(transfer_id_) << ", "; + *os << "is_start=" << std::boolalpha << is_start << ", "; + *os << "is_end=" << std::boolalpha << is_end << ", "; + *os << "is_toggle=" << std::boolalpha << is_toggle; + *os << "}"; + } + return transfer_id == transfer_id_ && is_start == is_start_ && is_end == is_end_ && is_toggle == is_toggle_; + } + void DescribeTo(std::ostream* os) const + { + *os << "{"; + *os << "transfer_id=" << static_cast(transfer_id_) << ", "; + *os << "is_start=" << std::boolalpha << is_start_ << ", "; + *os << "is_end=" << std::boolalpha << is_end_ << ", "; + *os << "is_toggle=" << std::boolalpha << is_toggle_; + *os << "}"; + } + void DescribeNegationTo(std::ostream* os) const + { + *os << "is NOT equal to "; + DescribeTo(os); + } + +private: + const std::uint8_t transfer_id_; + const bool is_start_; + const bool is_end_; + const bool is_toggle_; +}; +inline testing::Matcher FrameLastByteEq(TransferId transfer_id, + bool is_start = true, + bool is_end = true, + bool is_toggle = true) +{ + return FrameLastByteMatcher(transfer_id, is_start, is_end, is_toggle); +} + +} // namespace can } // namespace transport } // namespace libcyphal diff --git a/test/unittest/transport/can/test_can_msg_rx_session.cpp b/test/unittest/transport/can/test_can_msg_rx_session.cpp index 3c5775635..6af443096 100644 --- a/test/unittest/transport/can/test_can_msg_rx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_rx_session.cpp @@ -15,6 +15,8 @@ namespace { +using byte = cetl::byte; + using namespace libcyphal; using namespace libcyphal::transport; using namespace libcyphal::transport::can; @@ -55,6 +57,11 @@ class TestCanMsgRxSession : public testing::Test return cetl::get>(std::move(maybe_transport)); } + static constexpr byte b(std::uint8_t b) + { + return static_cast(b); + } + // MARK: Data members: TrackingMemoryResource mr_; @@ -105,13 +112,13 @@ TEST_F(TestCanMsgRxSession, run_and_receive) // 1-st iteration: one frame available @ 1s { - EXPECT_CALL(media_mock_, pop(_)).WillOnce([](const cetl::span payload) { + EXPECT_CALL(media_mock_, pop(_)).WillOnce([](const cetl::span payload) { EXPECT_THAT(payload.size(), Eq(CANARD_MTU_MAX)); - payload[0] = static_cast(42); - payload[1] = static_cast(147); - payload[2] = static_cast(0xED); - return RxMetadata{TimePoint{1s}, 0x0C002345, 3}; + payload[0] = b(42); + payload[1] = b(147); + payload[2] = b(0xED); + return RxMetadata{TimePoint{1s}, 0x0C'00'23'45, 3}; }); transport->run(TimePoint{1s + 10ms}); @@ -136,7 +143,7 @@ TEST_F(TestCanMsgRxSession, run_and_receive) // 2-nd iteration: no frames available { - EXPECT_CALL(media_mock_, pop(_)).WillOnce([](const cetl::span payload) { + EXPECT_CALL(media_mock_, pop(_)).WillOnce([](const cetl::span payload) { EXPECT_THAT(payload.size(), Eq(CANARD_MTU_MAX)); return cetl::nullopt; }); diff --git a/test/unittest/transport/can/test_can_msg_tx_session.cpp b/test/unittest/transport/can/test_can_msg_tx_session.cpp index f0c44f54c..d157018a7 100644 --- a/test/unittest/transport/can/test_can_msg_tx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_tx_session.cpp @@ -15,6 +15,8 @@ namespace { +using byte = cetl::byte; + using namespace libcyphal; using namespace libcyphal::transport; using namespace libcyphal::transport::can; @@ -27,6 +29,7 @@ using testing::IsEmpty; using testing::NotNull; using testing::Optional; using testing::StrictMock; +using testing::ElementsAre; using testing::VariantWith; using namespace std::chrono_literals; @@ -36,7 +39,7 @@ class TestCanMsgTxSession : public testing::Test protected: void SetUp() override { - EXPECT_CALL(media_mock_, getMtu()).WillRepeatedly(Return(CANARD_MTU_MAX)); + EXPECT_CALL(media_mock_, getMtu()).WillRepeatedly(Return(CANARD_MTU_CAN_CLASSIC)); } void TearDown() override @@ -56,6 +59,25 @@ class TestCanMsgTxSession : public testing::Test return cetl::get>(std::move(maybe_transport)); } + static constexpr byte b(std::uint8_t b) + { + return static_cast(b); + } + + template + std::array makeIotaArray(const std::uint8_t init = 0) + { + std::array arr{}; + std::iota(reinterpret_cast(arr.begin()), reinterpret_cast(arr.end()), init); + return arr; + } + + template + std::array, 1> makeSpansFrom(const std::array& payload) + { + return {payload}; + } + // MARK: Data members: TrackingMemoryResource mr_; @@ -93,20 +115,172 @@ TEST_F(TestCanMsgTxSession, make_no_memory) TEST_F(TestCanMsgTxSession, send_empty_payload_and_no_transport_run) { - StrictMock mr_mock{}; - mr_mock.redirectExpectedCallsTo(mr_); + auto transport = makeTransport(mr_); - auto transport = makeTransport(mr_mock); + auto maybe_session = transport->makeMessageTxSession({123}); + EXPECT_THAT(maybe_session, VariantWith>(_)); + auto session = cetl::get>(std::move(maybe_session)); + EXPECT_THAT(session, NotNull()); + + const PayloadFragments empty_payload{}; + const TransferMetadata metadata{0x1AF52, TimePoint{1s}, Priority::Low}; + + auto maybe_error = session->send(metadata, empty_payload); + EXPECT_THAT(maybe_error, Eq(cetl::nullopt)); + + // Payload still inside canard TX queue (b/c there was no `transport->run` call deliberately), + // but there will be no memory leak b/c we expect that it should be deallocated when the transport is destroyed. + // See `EXPECT_THAT(mr_.allocations, IsEmpty());` at the `TearDown` method. +} + +TEST_F(TestCanMsgTxSession, send_empty_payload) +{ + EXPECT_CALL(media_mock_, pop(_)).WillRepeatedly(Return(cetl::nullopt)); + + auto transport = makeTransport(mr_); auto maybe_session = transport->makeMessageTxSession({123}); EXPECT_THAT(maybe_session, VariantWith>(_)); auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); + const auto send_time = TimePoint{10s}; + const auto expected_default_timeout = 1s; + const PayloadFragments empty_payload{}; + const TransferMetadata metadata{0x3AF52, send_time, Priority::Low}; - auto maybe_error = session->send({0x1AF52, TimePoint{1s}, Priority::Low}, empty_payload); + auto maybe_error = session->send(metadata, empty_payload); EXPECT_THAT(maybe_error, Eq(cetl::nullopt)); + + EXPECT_CALL(media_mock_, push(_, _, _)) + .WillOnce([&](const TimePoint deadline, const CanId can_id, const cetl::span payload) { + EXPECT_THAT(deadline, Eq(send_time + expected_default_timeout)); + EXPECT_THAT(can_id, SubjectOfCanIdEq(123)); + EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())); + + auto flb = FrameLastByteEq(metadata.transfer_id); + EXPECT_THAT(payload, ElementsAre(flb)); + return true; + }); + + transport->run(TimePoint{1s + 10ms}); + transport->run(TimePoint{1s + 20ms}); } +TEST_F(TestCanMsgTxSession, send_empty_expired_payload) +{ + EXPECT_CALL(media_mock_, pop(_)).WillRepeatedly(Return(cetl::nullopt)); + + auto transport = makeTransport(mr_); + + auto maybe_session = transport->makeMessageTxSession({123}); + EXPECT_THAT(maybe_session, VariantWith>(_)); + auto session = cetl::get>(std::move(maybe_session)); + EXPECT_THAT(session, NotNull()); + + const auto send_time = TimePoint{10s}; + const auto expected_default_timeout = 1s; + + const PayloadFragments empty_payload{}; + const TransferMetadata metadata{0x11, send_time, Priority::Low}; + + auto maybe_error = session->send(metadata, empty_payload); + EXPECT_THAT(maybe_error, Eq(cetl::nullopt)); + + // Emulate run calls just on the very edge of the default 1s timeout (exactly at the deadline). + // Payload should NOT be sent but dropped instead. + // + transport->run(TimePoint{send_time + expected_default_timeout}); + transport->run(TimePoint{send_time + expected_default_timeout + 1us}); +} + +TEST_F(TestCanMsgTxSession, send_7bytes_payload_with_500ms_timeout) +{ + EXPECT_CALL(media_mock_, pop(_)).WillRepeatedly(Return(cetl::nullopt)); + + auto transport = makeTransport(mr_); + + auto maybe_session = transport->makeMessageTxSession({17}); + EXPECT_THAT(maybe_session, VariantWith>(_)); + auto session = cetl::get>(std::move(maybe_session)); + EXPECT_THAT(session, NotNull()); + + const auto timeout = 500ms; + session->setSendTimeout(timeout); + + const auto send_time = TimePoint{10s}; + + const auto payload = makeIotaArray<7>('0'); + const TransferMetadata metadata{0x03, send_time, Priority::High}; + + auto maybe_error = session->send(metadata, makeSpansFrom(payload)); + EXPECT_THAT(maybe_error, Eq(cetl::nullopt)); + + // Emulate run calls just on the very edge of the 500ms deadline (just 1us before it). + // Payload should be sent successfully. + // + EXPECT_CALL(media_mock_, push(TimePoint{send_time + timeout}, _, _)) + .WillOnce([&](const TimePoint deadline, const CanId can_id, const cetl::span payload) { + EXPECT_THAT(deadline, Eq(send_time + timeout)); + EXPECT_THAT(can_id, SubjectOfCanIdEq(17)); + EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())); + + auto flb = FrameLastByteEq(metadata.transfer_id); + EXPECT_THAT(payload, ElementsAre(b('0'), b('1'), b('2'), b('3'), b('4'), b('5'), b('6'), flb)); + return true; + }); + // + transport->run(TimePoint{send_time + timeout - 1us}); + transport->run(TimePoint{send_time + timeout - 1us}); +} +/* + * This test is disabled temporary because can't set yet transport local node id. + * +TEST_F(TestCanMsgTxSession, send_8bytes_payload) +{ + EXPECT_CALL(media_mock_, pop(_)).WillRepeatedly(Return(cetl::nullopt)); + + auto transport = makeTransport(mr_); + + auto maybe_session = transport->makeMessageTxSession({7}); + EXPECT_THAT(maybe_session, VariantWith>(_)); + auto session = cetl::get>(std::move(maybe_session)); + EXPECT_THAT(session, NotNull()); + + const auto send_time = TimePoint{10s}; + const auto expected_default_timeout = 1s; + + const auto payload = makeIotaArray<8>('0'); + const TransferMetadata metadata{0x13, send_time, Priority::Nominal}; + + auto maybe_error = session->send(metadata, makeSpansFrom(payload)); + EXPECT_THAT(maybe_error, Eq(cetl::nullopt)); + + EXPECT_CALL(media_mock_, push(TimePoint{send_time + +10us}, _, _)) + .WillOnce([&](const TimePoint deadline, const CanId can_id, const cetl::span payload) { + EXPECT_THAT(deadline, Eq(send_time + expected_default_timeout)); + EXPECT_THAT(can_id, SubjectOfCanIdEq(7)); + EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())); + + auto flb = FrameLastByteEq(metadata.transfer_id, true, false); + EXPECT_THAT(payload, ElementsAre(b('0'), b('1'), b('2'), b('3'), b('4'), b('5'), b('6'), flb)); + return true; + }); + EXPECT_CALL(media_mock_, push(TimePoint{send_time + +10us}, _, _)) + .WillOnce([&](const TimePoint deadline, const CanId can_id, const cetl::span payload) { + EXPECT_THAT(deadline, Eq(send_time + expected_default_timeout)); + EXPECT_THAT(can_id, SubjectOfCanIdEq(7)); + EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())); + + auto flb = FrameLastByteEq(metadata.transfer_id, false, true, false); + EXPECT_THAT(payload, ElementsAre(b('7'), flb)); + return true; + }); + + transport->run(TimePoint{send_time + 10us}); + transport->run(TimePoint{send_time + 20us}); + transport->run(TimePoint{send_time + 30us}); +} +*/ } // namespace From 769f23f729a98452117ecdb4b2d654dc264c6ece Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 25 Apr 2024 12:01:28 +0300 Subject: [PATCH 34/64] Added `ITransport::setLocalNodeId`; unit tests for multiframe msg tx; #verification --- include/libcyphal/transport/can/transport.hpp | 16 ++ include/libcyphal/transport/transport.hpp | 25 +++- test/unittest/gtest_helpers.hpp | 3 - test/unittest/memory_resource_mock.hpp | 3 - test/unittest/test_libcyphal.cpp | 21 --- test/unittest/test_scheduler.hpp | 49 +++++++ test/unittest/tracking_memory_resource.hpp | 3 - .../transport/can/test_can_delegate.cpp | 28 ++-- .../transport/can/test_can_msg_rx_session.cpp | 48 +++--- .../transport/can/test_can_msg_tx_session.cpp | 137 ++++++++++++------ .../transport/can/test_can_transport.cpp | 18 +-- .../transport/test_contiguous_payload.cpp | 23 ++- .../transport/test_scattered_buffer.cpp | 15 +- 13 files changed, 248 insertions(+), 141 deletions(-) delete mode 100644 test/unittest/test_libcyphal.cpp create mode 100644 test/unittest/test_scheduler.hpp diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index b997bed7e..d7a261467 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -118,6 +118,22 @@ class TransportImpl final : public ICanTransport, private TransportDelegate return cetl::make_optional(static_cast(canard_instance().node_id)); } + + CETL_NODISCARD cetl::optional setLocalNodeId(const NodeId node_id) noexcept override + { + if (node_id > CANARD_NODE_ID_MAX) + { + return ArgumentError{}; + } + if (canard_instance().node_id != CANARD_NODE_ID_UNSET) + { + return ArgumentError{}; + } + + canard_instance().node_id = static_cast(node_id); + return cetl::nullopt; + } + CETL_NODISCARD ProtocolParams getProtocolParams() const noexcept override { const auto min_mtu = diff --git a/include/libcyphal/transport/transport.hpp b/include/libcyphal/transport/transport.hpp index 6df2f6cbd..29b394fb2 100644 --- a/include/libcyphal/transport/transport.hpp +++ b/include/libcyphal/transport/transport.hpp @@ -18,8 +18,29 @@ namespace transport class ITransport : public IRunnable { public: - CETL_NODISCARD virtual cetl::optional getLocalNodeId() const noexcept = 0; - CETL_NODISCARD virtual ProtocolParams getProtocolParams() const noexcept = 0; + CETL_NODISCARD virtual ProtocolParams getProtocolParams() const noexcept = 0; + + /// @brief Gets the local node ID (if any). + /// + /// It's optional to have a local node ID set (see anonymous nodes in the Cyphal spec). + /// Initially (by default) it is not set. + /// + /// @return The node ID previously assigned to this transport interface (via `setLocalNodeId`). + /// Otherwise it's `nullopt` for an anonymous node. + /// + CETL_NODISCARD virtual cetl::optional getLocalNodeId() const noexcept = 0; + + /// @brief Sets the local node ID. + /// + /// It's only possible to set the local node ID once. Subsequent calls will return an argument error. + /// A concrete transport implementation may have a specific/limited range of valid node IDs. For example, + /// - an UDP transport may have a range of 0...65534 node ids (see `UDPARD_NODE_ID_MAX` in `udpard.h`) + /// - a CAN bus transport may have a range of 0...127 node ids (see `CANARD_NODE_ID_MAX` in `canard.h`) + /// + /// @param node_id Specific node ID assigned to this transport interface. + /// @return `nullopt` on success; otherwise an `ArgumentError` in case of the subsequent calls or ID out of range. + /// + CETL_NODISCARD virtual cetl::optional setLocalNodeId(const NodeId node_id) noexcept = 0; CETL_NODISCARD virtual Expected, AnyError> makeMessageRxSession( const MessageRxParams& params) = 0; diff --git a/test/unittest/gtest_helpers.hpp b/test/unittest/gtest_helpers.hpp index 7d39a1788..057ed9cdf 100644 --- a/test/unittest/gtest_helpers.hpp +++ b/test/unittest/gtest_helpers.hpp @@ -1,6 +1,3 @@ -/// @file -/// libcyphal common header. -/// /// @copyright /// Copyright (C) OpenCyphal Development Team /// Copyright Amazon.com Inc. or its affiliates. diff --git a/test/unittest/memory_resource_mock.hpp b/test/unittest/memory_resource_mock.hpp index 3e3781a4f..9c238837d 100644 --- a/test/unittest/memory_resource_mock.hpp +++ b/test/unittest/memory_resource_mock.hpp @@ -1,6 +1,3 @@ -/// @file -/// libcyphal common header. -/// /// @copyright /// Copyright (C) OpenCyphal Development Team /// Copyright Amazon.com Inc. or its affiliates. diff --git a/test/unittest/test_libcyphal.cpp b/test/unittest/test_libcyphal.cpp deleted file mode 100644 index dacf56ffa..000000000 --- a/test/unittest/test_libcyphal.cpp +++ /dev/null @@ -1,21 +0,0 @@ -/// @file -/// libcyphal common header. -/// -/// @copyright -/// Copyright (C) OpenCyphal Development Team -/// Copyright Amazon.com Inc. or its affiliates. -/// SPDX-License-Identifier: MIT - -#include - -#include - -namespace -{ - -// MARK: Tests: - -// TODO: Add tests here -TEST(test_libcyphal, rename_me) {} - -} // namespace diff --git a/test/unittest/test_scheduler.hpp b/test/unittest/test_scheduler.hpp new file mode 100644 index 000000000..d4e839c33 --- /dev/null +++ b/test/unittest/test_scheduler.hpp @@ -0,0 +1,49 @@ +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#ifndef LIBCYPHAL_TEST_SCHEDULER_HPP +#define LIBCYPHAL_TEST_SCHEDULER_HPP + +#include + +namespace libcyphal +{ + +struct TestScheduler +{ + explicit TestScheduler(const TimePoint initial_now = {}) + : now_{initial_now} + { + } + + TimePoint now() const + { + return now_; + } + + void setNow(const TimePoint now) + { + now_ = now; + } + + void runNow(const Duration duration) + { + now_ += duration; + } + + template + void runNow(const Duration duration, Action action) + { + runNow(duration); + action(); + } + +private: + TimePoint now_{}; +}; + +} // namespace libcyphal + +#endif // LIBCYPHAL_TEST_SCHEDULER_HPP diff --git a/test/unittest/tracking_memory_resource.hpp b/test/unittest/tracking_memory_resource.hpp index 9c6707c48..5f019f4cf 100644 --- a/test/unittest/tracking_memory_resource.hpp +++ b/test/unittest/tracking_memory_resource.hpp @@ -1,6 +1,3 @@ -/// @file -/// libcyphal common header. -/// /// @copyright /// Copyright (C) OpenCyphal Development Team /// Copyright Amazon.com Inc. or its affiliates. diff --git a/test/unittest/transport/can/test_can_delegate.cpp b/test/unittest/transport/can/test_can_delegate.cpp index fcee15809..9d5612cef 100644 --- a/test/unittest/transport/can/test_can_delegate.cpp +++ b/test/unittest/transport/can/test_can_delegate.cpp @@ -72,14 +72,14 @@ TEST_F(TestCanDelegate, CanardMemory_copy) const std::size_t payload_size = 4; const CanardMemory canard_memory{delegate, payload, payload_size}; - EXPECT_THAT(canard_memory.size(), Eq(payload_size)); + EXPECT_THAT(canard_memory.size(), payload_size); // Ask exactly as payload { const std::size_t ask_size = payload_size; std::array buffer{}; - EXPECT_THAT(canard_memory.copy(0, buffer.data(), ask_size), Eq(ask_size)); + EXPECT_THAT(canard_memory.copy(0, buffer.data(), ask_size), ask_size); EXPECT_THAT(buffer, ElementsAre('0', '1', '2', '3')); } @@ -88,7 +88,7 @@ TEST_F(TestCanDelegate, CanardMemory_copy) const std::size_t ask_size = payload_size + 2; std::array buffer{}; - EXPECT_THAT(canard_memory.copy(0, buffer.data(), ask_size), Eq(payload_size)); + EXPECT_THAT(canard_memory.copy(0, buffer.data(), ask_size), payload_size); EXPECT_THAT(buffer, ElementsAre('0', '1', '2', '3', '\0', '\0')); } @@ -97,24 +97,24 @@ TEST_F(TestCanDelegate, CanardMemory_copy) const std::size_t ask_size = payload_size - 2; std::array buffer{}; - EXPECT_THAT(canard_memory.copy(0, buffer.data(), ask_size), Eq(ask_size)); + EXPECT_THAT(canard_memory.copy(0, buffer.data(), ask_size), ask_size); EXPECT_THAT(buffer, ElementsAre('0', '1')); - EXPECT_THAT(canard_memory.copy(3, buffer.data(), buffer.size()), Eq(1)); + EXPECT_THAT(canard_memory.copy(3, buffer.data(), buffer.size()), 1); EXPECT_THAT(buffer, ElementsAre('3', '1')); - EXPECT_THAT(canard_memory.copy(2, buffer.data(), ask_size), Eq(ask_size)); + EXPECT_THAT(canard_memory.copy(2, buffer.data(), ask_size), ask_size); EXPECT_THAT(buffer, ElementsAre('2', '3')); - EXPECT_THAT(canard_memory.copy(payload_size, buffer.data(), ask_size), Eq(0)); + EXPECT_THAT(canard_memory.copy(payload_size, buffer.data(), ask_size), 0); EXPECT_THAT(buffer, ElementsAre('2', '3')); // Ask nothing - EXPECT_THAT(canard_memory.copy(0, buffer.data(), 0), Eq(0)); + EXPECT_THAT(canard_memory.copy(0, buffer.data(), 0), 0); EXPECT_THAT(buffer, ElementsAre('2', '3')); // No output buffer - EXPECT_THAT(canard_memory.copy(0, nullptr, 0), Eq(0)); + EXPECT_THAT(canard_memory.copy(0, nullptr, 0), 0); } } @@ -130,23 +130,23 @@ TEST_F(TestCanDelegate, CanardMemory_copy_on_moved) std::iota(payload, payload + payload_size, '0'); CanardMemory old_canard_memory{delegate, payload, payload_size}; - EXPECT_THAT(old_canard_memory.size(), Eq(payload_size)); + EXPECT_THAT(old_canard_memory.size(), payload_size); CanardMemory new_canard_memory{std::move(old_canard_memory)}; - EXPECT_THAT(old_canard_memory.size(), Eq(0)); - EXPECT_THAT(new_canard_memory.size(), Eq(payload_size)); + EXPECT_THAT(old_canard_memory.size(), 0); + EXPECT_THAT(new_canard_memory.size(), payload_size); // Try old one { std::array buffer{}; - EXPECT_THAT(old_canard_memory.copy(0, buffer.data(), buffer.size()), Eq(0)); + EXPECT_THAT(old_canard_memory.copy(0, buffer.data(), buffer.size()), 0); EXPECT_THAT(buffer, Each('\0')); } // Try new one { std::array buffer{}; - EXPECT_THAT(new_canard_memory.copy(0, buffer.data(), buffer.size()), Eq(payload_size)); + EXPECT_THAT(new_canard_memory.copy(0, buffer.data(), buffer.size()), payload_size); EXPECT_THAT(buffer, ElementsAre('0', '1', '2', '3')); } } diff --git a/test/unittest/transport/can/test_can_msg_rx_session.cpp b/test/unittest/transport/can/test_can_msg_rx_session.cpp index 6af443096..7cf3f1634 100644 --- a/test/unittest/transport/can/test_can_msg_rx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_rx_session.cpp @@ -8,6 +8,7 @@ #include "media_mock.hpp" #include "../multiplexer_mock.hpp" #include "../../gtest_helpers.hpp" +#include "../../test_scheduler.hpp" #include "../../memory_resource_mock.hpp" #include "../../tracking_memory_resource.hpp" @@ -48,6 +49,11 @@ class TestCanMsgRxSession : public testing::Test // EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); } + TimePoint now() const + { + return scheduler_.now(); + } + CETL_NODISCARD UniquePtr makeTransport(cetl::pmr::memory_resource& mr) { std::array media_array{&media_mock_}; @@ -64,6 +70,7 @@ class TestCanMsgRxSession : public testing::Test // MARK: Data members: + TestScheduler scheduler_{}; TrackingMemoryResource mr_; StrictMock media_mock_{}; StrictMock mux_mock_{}; @@ -80,8 +87,8 @@ TEST_F(TestCanMsgRxSession, make_setTransferIdTimeout) auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); - EXPECT_THAT(session->getParams().extent_bytes, Eq(42)); - EXPECT_THAT(session->getParams().subject_id, Eq(123)); + EXPECT_THAT(session->getParams().extent_bytes, 42); + EXPECT_THAT(session->getParams().subject_id, 123); session->setTransferIdTimeout(0s); session->setTransferIdTimeout(500ms); @@ -112,45 +119,48 @@ TEST_F(TestCanMsgRxSession, run_and_receive) // 1-st iteration: one frame available @ 1s { - EXPECT_CALL(media_mock_, pop(_)).WillOnce([](const cetl::span payload) { - EXPECT_THAT(payload.size(), Eq(CANARD_MTU_MAX)); + scheduler_.setNow(TimePoint{1s}); + const auto rx_timestamp = now(); + + EXPECT_CALL(media_mock_, pop(_)).WillOnce([&](const cetl::span payload) { + EXPECT_THAT(payload.size(), CANARD_MTU_MAX); payload[0] = b(42); payload[1] = b(147); payload[2] = b(0xED); - return RxMetadata{TimePoint{1s}, 0x0C'00'23'45, 3}; + return RxMetadata{rx_timestamp, 0x0C'00'23'45, 3}; }); - transport->run(TimePoint{1s + 10ms}); - - session->run(TimePoint{1s + 20ms}); + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + scheduler_.runNow(+10ms, [&] { session->run(now()); }); const auto maybe_rx_transfer = session->receive(); EXPECT_THAT(maybe_rx_transfer, Optional(_)); const auto& rx_transfer = maybe_rx_transfer.value(); - EXPECT_THAT(rx_transfer.metadata.timestamp, Eq(TimePoint{1s})); - EXPECT_THAT(rx_transfer.metadata.transfer_id, Eq(0x0D)); - EXPECT_THAT(rx_transfer.metadata.priority, Eq(Priority::High)); + EXPECT_THAT(rx_transfer.metadata.timestamp, rx_timestamp); + EXPECT_THAT(rx_transfer.metadata.transfer_id, 0x0D); + EXPECT_THAT(rx_transfer.metadata.priority, Priority::High); EXPECT_THAT(rx_transfer.metadata.publisher_node_id, Optional(0x45)); std::array buffer{}; - EXPECT_THAT(rx_transfer.payload.size(), Eq(2)); - EXPECT_THAT(rx_transfer.payload.copy(0, buffer.data(), buffer.size()), Eq(2)); - EXPECT_THAT(buffer[0], Eq(42)); - EXPECT_THAT(buffer[1], Eq(147)); + EXPECT_THAT(rx_transfer.payload.size(), 2); + EXPECT_THAT(rx_transfer.payload.copy(0, buffer.data(), buffer.size()), 2); + EXPECT_THAT(buffer[0], 42); + EXPECT_THAT(buffer[1], 147); } // 2-nd iteration: no frames available { + scheduler_.setNow(TimePoint{2s}); + EXPECT_CALL(media_mock_, pop(_)).WillOnce([](const cetl::span payload) { - EXPECT_THAT(payload.size(), Eq(CANARD_MTU_MAX)); + EXPECT_THAT(payload.size(), CANARD_MTU_MAX); return cetl::nullopt; }); - transport->run(TimePoint{2s + 10ms}); - - session->run(TimePoint{2s + 20ms}); + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + scheduler_.runNow(+10ms, [&] { session->run(now()); }); const auto maybe_rx_transfer = session->receive(); EXPECT_THAT(maybe_rx_transfer, Eq(cetl::nullopt)); diff --git a/test/unittest/transport/can/test_can_msg_tx_session.cpp b/test/unittest/transport/can/test_can_msg_tx_session.cpp index d157018a7..73252bbaa 100644 --- a/test/unittest/transport/can/test_can_msg_tx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_tx_session.cpp @@ -8,6 +8,7 @@ #include "media_mock.hpp" #include "../multiplexer_mock.hpp" #include "../../gtest_helpers.hpp" +#include "../../test_scheduler.hpp" #include "../../memory_resource_mock.hpp" #include "../../tracking_memory_resource.hpp" @@ -31,6 +32,7 @@ using testing::Optional; using testing::StrictMock; using testing::ElementsAre; using testing::VariantWith; +using testing::InSequence; using namespace std::chrono_literals; @@ -49,6 +51,11 @@ class TestCanMsgTxSession : public testing::Test // EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); } + TimePoint now() const + { + return scheduler_.now(); + } + CETL_NODISCARD UniquePtr makeTransport(cetl::pmr::memory_resource& mr, const std::size_t tx_capacity = 16) { @@ -80,6 +87,7 @@ class TestCanMsgTxSession : public testing::Test // MARK: Data members: + TestScheduler scheduler_{}; TrackingMemoryResource mr_; StrictMock media_mock_{}; StrictMock mux_mock_{}; @@ -96,7 +104,7 @@ TEST_F(TestCanMsgTxSession, make) auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); - EXPECT_THAT(session->getParams().subject_id, Eq(123)); + EXPECT_THAT(session->getParams().subject_id, 123); } TEST_F(TestCanMsgTxSession, make_no_memory) @@ -123,11 +131,13 @@ TEST_F(TestCanMsgTxSession, send_empty_payload_and_no_transport_run) EXPECT_THAT(session, NotNull()); const PayloadFragments empty_payload{}; - const TransferMetadata metadata{0x1AF52, TimePoint{1s}, Priority::Low}; + const TransferMetadata metadata{0x1AF52, {}, Priority::Low}; auto maybe_error = session->send(metadata, empty_payload); EXPECT_THAT(maybe_error, Eq(cetl::nullopt)); + scheduler_.runNow(+10ms, [&] { session->run(scheduler_.now()); }); + // Payload still inside canard TX queue (b/c there was no `transport->run` call deliberately), // but there will be no memory leak b/c we expect that it should be deallocated when the transport is destroyed. // See `EXPECT_THAT(mr_.allocations, IsEmpty());` at the `TearDown` method. @@ -144,8 +154,9 @@ TEST_F(TestCanMsgTxSession, send_empty_payload) auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); - const auto send_time = TimePoint{10s}; - const auto expected_default_timeout = 1s; + scheduler_.runNow(+10s); + const auto send_time = now(); + const auto timeout = 1s; const PayloadFragments empty_payload{}; const TransferMetadata metadata{0x3AF52, send_time, Priority::Low}; @@ -155,7 +166,8 @@ TEST_F(TestCanMsgTxSession, send_empty_payload) EXPECT_CALL(media_mock_, push(_, _, _)) .WillOnce([&](const TimePoint deadline, const CanId can_id, const cetl::span payload) { - EXPECT_THAT(deadline, Eq(send_time + expected_default_timeout)); + EXPECT_THAT(now(), send_time + 10ms); + EXPECT_THAT(deadline, send_time + timeout); EXPECT_THAT(can_id, SubjectOfCanIdEq(123)); EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())); @@ -164,8 +176,8 @@ TEST_F(TestCanMsgTxSession, send_empty_payload) return true; }); - transport->run(TimePoint{1s + 10ms}); - transport->run(TimePoint{1s + 20ms}); + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); } TEST_F(TestCanMsgTxSession, send_empty_expired_payload) @@ -179,8 +191,9 @@ TEST_F(TestCanMsgTxSession, send_empty_expired_payload) auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); - const auto send_time = TimePoint{10s}; - const auto expected_default_timeout = 1s; + scheduler_.runNow(+10s); + const auto send_time = now(); + const auto timeout = 1s; const PayloadFragments empty_payload{}; const TransferMetadata metadata{0x11, send_time, Priority::Low}; @@ -191,8 +204,8 @@ TEST_F(TestCanMsgTxSession, send_empty_expired_payload) // Emulate run calls just on the very edge of the default 1s timeout (exactly at the deadline). // Payload should NOT be sent but dropped instead. // - transport->run(TimePoint{send_time + expected_default_timeout}); - transport->run(TimePoint{send_time + expected_default_timeout + 1us}); + scheduler_.runNow(+timeout, [&] { transport->run(now()); }); + scheduler_.runNow(+1us, [&] { transport->run(now()); }); } TEST_F(TestCanMsgTxSession, send_7bytes_payload_with_500ms_timeout) @@ -209,9 +222,10 @@ TEST_F(TestCanMsgTxSession, send_7bytes_payload_with_500ms_timeout) const auto timeout = 500ms; session->setSendTimeout(timeout); - const auto send_time = TimePoint{10s}; + scheduler_.runNow(+10s); + const auto send_time = now(); - const auto payload = makeIotaArray<7>('0'); + const auto payload = makeIotaArray('0'); const TransferMetadata metadata{0x03, send_time, Priority::High}; auto maybe_error = session->send(metadata, makeSpansFrom(payload)); @@ -221,8 +235,8 @@ TEST_F(TestCanMsgTxSession, send_7bytes_payload_with_500ms_timeout) // Payload should be sent successfully. // EXPECT_CALL(media_mock_, push(TimePoint{send_time + timeout}, _, _)) - .WillOnce([&](const TimePoint deadline, const CanId can_id, const cetl::span payload) { - EXPECT_THAT(deadline, Eq(send_time + timeout)); + .WillOnce([&](const TimePoint, const CanId can_id, const cetl::span payload) { + EXPECT_THAT(now(), send_time + timeout - 1us); EXPECT_THAT(can_id, SubjectOfCanIdEq(17)); EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())); @@ -231,13 +245,11 @@ TEST_F(TestCanMsgTxSession, send_7bytes_payload_with_500ms_timeout) return true; }); // - transport->run(TimePoint{send_time + timeout - 1us}); - transport->run(TimePoint{send_time + timeout - 1us}); + scheduler_.runNow(timeout - 1us, [&] { transport->run(now()); }); + scheduler_.runNow(+0us, [&] { transport->run(now()); }); } -/* - * This test is disabled temporary because can't set yet transport local node id. - * -TEST_F(TestCanMsgTxSession, send_8bytes_payload) + +TEST_F(TestCanMsgTxSession, sending_multiframe_payload_should_fail_for_anonymous) { EXPECT_CALL(media_mock_, pop(_)).WillRepeatedly(Return(cetl::nullopt)); @@ -248,39 +260,70 @@ TEST_F(TestCanMsgTxSession, send_8bytes_payload) auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); - const auto send_time = TimePoint{10s}; - const auto expected_default_timeout = 1s; + scheduler_.runNow(+10s); + const auto send_time = now(); - const auto payload = makeIotaArray<8>('0'); + const auto payload = makeIotaArray('0'); const TransferMetadata metadata{0x13, send_time, Priority::Nominal}; auto maybe_error = session->send(metadata, makeSpansFrom(payload)); - EXPECT_THAT(maybe_error, Eq(cetl::nullopt)); + EXPECT_THAT(maybe_error, Optional(VariantWith(_))); - EXPECT_CALL(media_mock_, push(TimePoint{send_time + +10us}, _, _)) - .WillOnce([&](const TimePoint deadline, const CanId can_id, const cetl::span payload) { - EXPECT_THAT(deadline, Eq(send_time + expected_default_timeout)); - EXPECT_THAT(can_id, SubjectOfCanIdEq(7)); - EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())); + scheduler_.runNow(+10us, [&] { transport->run(now()); }); + scheduler_.runNow(10us, [&] { session->run(now()); }); +} - auto flb = FrameLastByteEq(metadata.transfer_id, true, false); - EXPECT_THAT(payload, ElementsAre(b('0'), b('1'), b('2'), b('3'), b('4'), b('5'), b('6'), flb)); - return true; - }); - EXPECT_CALL(media_mock_, push(TimePoint{send_time + +10us}, _, _)) - .WillOnce([&](const TimePoint deadline, const CanId can_id, const cetl::span payload) { - EXPECT_THAT(deadline, Eq(send_time + expected_default_timeout)); - EXPECT_THAT(can_id, SubjectOfCanIdEq(7)); - EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())); +TEST_F(TestCanMsgTxSession, sending_multiframe_payload_for_non_anonymous) +{ + EXPECT_CALL(media_mock_, pop(_)).WillRepeatedly(Return(cetl::nullopt)); - auto flb = FrameLastByteEq(metadata.transfer_id, false, true, false); - EXPECT_THAT(payload, ElementsAre(b('7'), flb)); - return true; - }); + auto transport = makeTransport(mr_); + EXPECT_THAT(transport->setLocalNodeId(0x45), Eq(cetl::nullopt)); - transport->run(TimePoint{send_time + 10us}); - transport->run(TimePoint{send_time + 20us}); - transport->run(TimePoint{send_time + 30us}); + auto maybe_session = transport->makeMessageTxSession({7}); + EXPECT_THAT(maybe_session, VariantWith>(_)); + auto session = cetl::get>(std::move(maybe_session)); + EXPECT_THAT(session, NotNull()); + + scheduler_.runNow(+10s); + const auto timeout = 1s; + const auto send_time = now(); + + const auto payload = makeIotaArray('0'); + const TransferMetadata metadata{0x13, send_time, Priority::Nominal}; + + auto maybe_error = session->send(metadata, makeSpansFrom(payload)); + EXPECT_THAT(maybe_error, Eq(cetl::nullopt)); + + { + InSequence s; + + EXPECT_CALL(media_mock_, push(_, _, _)) + .WillOnce([&](const TimePoint deadline, const CanId can_id, const cetl::span payload) { + EXPECT_THAT(now(), send_time + 10us); + EXPECT_THAT(deadline, send_time + timeout); + EXPECT_THAT(can_id, SubjectOfCanIdEq(7)); + EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())); + + auto flb = FrameLastByteEq(metadata.transfer_id, true, false); + EXPECT_THAT(payload, ElementsAre(b('0'), b('1'), b('2'), b('3'), b('4'), b('5'), b('6'), flb)); + return true; + }); + EXPECT_CALL(media_mock_, push(_, _, _)) + .WillOnce([&](const TimePoint deadline, const CanId can_id, const cetl::span payload) { + EXPECT_THAT(now(), send_time + 10us); + EXPECT_THAT(deadline, send_time + timeout); + EXPECT_THAT(can_id, SubjectOfCanIdEq(7)); + EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())); + + auto flb = FrameLastByteEq(metadata.transfer_id, false, true, false); + EXPECT_THAT(payload, ElementsAre(b('7'), _, _ /* CRC bytes */, flb)); + return true; + }); + } + + scheduler_.runNow(+10us, [&] { transport->run(now()); }); + scheduler_.runNow(+10us, [&] { transport->run(now()); }); } -*/ + } // namespace diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index 6839ffe2e..e00519f1e 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -163,20 +163,20 @@ TEST_F(TestCanTransport, getProtocolParams) EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_CLASSIC)); auto params = transport->getProtocolParams(); - EXPECT_THAT(params.transfer_id_modulo, Eq(1 << CANARD_TRANSFER_ID_BIT_LENGTH)); - EXPECT_THAT(params.max_nodes, Eq(CANARD_NODE_ID_MAX + 1)); - EXPECT_THAT(params.mtu_bytes, Eq(CANARD_MTU_CAN_CLASSIC)); + EXPECT_THAT(params.transfer_id_modulo, 1 << CANARD_TRANSFER_ID_BIT_LENGTH); + EXPECT_THAT(params.max_nodes, CANARD_NODE_ID_MAX + 1); + EXPECT_THAT(params.mtu_bytes, CANARD_MTU_CAN_CLASSIC); // Manipulate MTU values on fly { EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_FD)); - EXPECT_THAT(transport->getProtocolParams().mtu_bytes, Eq(CANARD_MTU_CAN_FD)); + EXPECT_THAT(transport->getProtocolParams().mtu_bytes, CANARD_MTU_CAN_FD); EXPECT_CALL(media_mock_, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_CLASSIC)); - EXPECT_THAT(transport->getProtocolParams().mtu_bytes, Eq(CANARD_MTU_CAN_CLASSIC)); + EXPECT_THAT(transport->getProtocolParams().mtu_bytes, CANARD_MTU_CAN_CLASSIC); EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_CLASSIC)); - EXPECT_THAT(transport->getProtocolParams().mtu_bytes, Eq(CANARD_MTU_CAN_CLASSIC)); + EXPECT_THAT(transport->getProtocolParams().mtu_bytes, CANARD_MTU_CAN_CLASSIC); } } @@ -189,8 +189,8 @@ TEST_F(TestCanTransport, makeMessageRxSession) EXPECT_THAT(maybe_rx_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_rx_session)); - EXPECT_THAT(session->getParams().extent_bytes, Eq(42)); - EXPECT_THAT(session->getParams().subject_id, Eq(123)); + EXPECT_THAT(session->getParams().extent_bytes, 42); + EXPECT_THAT(session->getParams().subject_id, 123); } TEST_F(TestCanTransport, makeMessageRxSession_invalid_subject_id) @@ -211,7 +211,7 @@ TEST_F(TestCanTransport, makeMessageTxSession) EXPECT_THAT(maybe_tx_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_tx_session)); - EXPECT_THAT(session->getParams().subject_id, Eq(123)); + EXPECT_THAT(session->getParams().subject_id, 123); } } // namespace diff --git a/test/unittest/transport/test_contiguous_payload.cpp b/test/unittest/transport/test_contiguous_payload.cpp index 29b0ab9f7..d4d9c9863 100644 --- a/test/unittest/transport/test_contiguous_payload.cpp +++ b/test/unittest/transport/test_contiguous_payload.cpp @@ -17,7 +17,6 @@ using byte = cetl::byte; using ContiguousPayload = libcyphal::transport::detail::ContiguousPayload; using testing::_; -using testing::Eq; using testing::IsNull; using testing::IsEmpty; using testing::NotNull; @@ -55,13 +54,13 @@ TEST_F(TestContiguousPayload, ctor_data_size) const ContiguousPayload payload{mr_, fragments}; - EXPECT_THAT(payload.size(), Eq(3)); + EXPECT_THAT(payload.size(), 3); EXPECT_THAT(payload.data(), NotNull()); const std::vector v(payload.data(), payload.data() + payload.size()); EXPECT_THAT(v, ElementsAre(b(1), b(2), b(3))); } - EXPECT_THAT(mr_.total_allocated_bytes, Eq(0)); - EXPECT_THAT(mr_.total_deallocated_bytes, Eq(0)); + EXPECT_THAT(mr_.total_allocated_bytes, 0); + EXPECT_THAT(mr_.total_deallocated_bytes, 0); // Double fragments { @@ -71,13 +70,13 @@ TEST_F(TestContiguousPayload, ctor_data_size) const ContiguousPayload payload{mr_, fragments}; - EXPECT_THAT(payload.size(), Eq(5)); + EXPECT_THAT(payload.size(), 5); EXPECT_THAT(payload.data(), NotNull()); const std::vector v(payload.data(), payload.data() + payload.size()); EXPECT_THAT(v, ElementsAre(b(1), b(2), b(3), b(4), b(5))); } - EXPECT_THAT(mr_.total_allocated_bytes, Eq(5)); - EXPECT_THAT(mr_.total_deallocated_bytes, Eq(5)); + EXPECT_THAT(mr_.total_allocated_bytes, 5); + EXPECT_THAT(mr_.total_deallocated_bytes, 5); } TEST_F(TestContiguousPayload, ctor_empty_cases) @@ -88,7 +87,7 @@ TEST_F(TestContiguousPayload, ctor_empty_cases) const ContiguousPayload payload{mr_, fragments}; - EXPECT_THAT(payload.size(), Eq(0)); + EXPECT_THAT(payload.size(), 0); EXPECT_THAT(payload.data(), IsNull()); } @@ -100,7 +99,7 @@ TEST_F(TestContiguousPayload, ctor_empty_cases) const ContiguousPayload payload{mr_, fragments}; - EXPECT_THAT(payload.size(), Eq(0)); + EXPECT_THAT(payload.size(), 0); EXPECT_THAT(payload.data(), IsNull()); } } @@ -120,8 +119,8 @@ TEST_F(TestContiguousPayload, ctor_no_alloc_for_single_non_empty_fragment) const ContiguousPayload payload{mr_mock, fragments}; - EXPECT_THAT(payload.size(), Eq(3)); - EXPECT_THAT(payload.data(), Eq(data123.data())); + EXPECT_THAT(payload.size(), 3); + EXPECT_THAT(payload.data(), data123.data()); const std::vector v(payload.data(), payload.data() + payload.size()); EXPECT_THAT(v, ElementsAre(b(1), b(2), b(3))); } @@ -139,7 +138,7 @@ TEST_F(TestContiguousPayload, ctor_no_memory_error) const ContiguousPayload payload{mr_mock, fragments}; - EXPECT_THAT(payload.size(), Eq(5)); + EXPECT_THAT(payload.size(), 5); EXPECT_THAT(payload.data(), IsNull()); } diff --git a/test/unittest/transport/test_scattered_buffer.cpp b/test/unittest/transport/test_scattered_buffer.cpp index f8197b5b2..10a05cd96 100644 --- a/test/unittest/transport/test_scattered_buffer.cpp +++ b/test/unittest/transport/test_scattered_buffer.cpp @@ -14,7 +14,6 @@ using cetl::rtti_helper; using ScatteredBuffer = libcyphal::transport::ScatteredBuffer; -using testing::Eq; using testing::Return; using testing::StrictMock; @@ -95,15 +94,15 @@ TEST(TestScatteredBuffer, move_ctor_assign_size) EXPECT_CALL(interface_mock, size()).Times(3).WillRepeatedly(Return(42)); { ScatteredBuffer src{InterfaceWrapper{&interface_mock}}; //< +1 move - EXPECT_THAT(src.size(), Eq(42)); + EXPECT_THAT(src.size(), 42); ScatteredBuffer dst{std::move(src)}; //< +2 moves b/c of `cetl::any` specifics (via swap with tmp) - EXPECT_THAT(src.size(), Eq(0)); - EXPECT_THAT(dst.size(), Eq(42)); + EXPECT_THAT(src.size(), 0); + EXPECT_THAT(dst.size(), 42); src = std::move(dst); //< +2 moves - EXPECT_THAT(src.size(), Eq(42)); - EXPECT_THAT(dst.size(), Eq(0)); + EXPECT_THAT(src.size(), 42); + EXPECT_THAT(dst.size(), 0); } } @@ -119,11 +118,11 @@ TEST(TestScatteredBuffer, copy_reset) ScatteredBuffer buffer{InterfaceWrapper{&interface_mock}}; auto copied_bytes = buffer.copy(13, test_dst.data(), test_dst.size()); - EXPECT_THAT(copied_bytes, Eq(7)); + EXPECT_THAT(copied_bytes, 7); buffer.reset(); copied_bytes = buffer.copy(13, test_dst.data(), test_dst.size()); - EXPECT_THAT(copied_bytes, Eq(0)); + EXPECT_THAT(copied_bytes, 0); } } From 15a950dedbdee83b7c76f96759354c2c72c88486 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 25 Apr 2024 12:27:06 +0300 Subject: [PATCH 35/64] Cover `can::TransportImpl::setLocalNodeId` #verification --- include/libcyphal/transport/udp/transport.hpp | 13 ++++++++++++ .../transport/can/test_can_transport.cpp | 20 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/include/libcyphal/transport/udp/transport.hpp b/include/libcyphal/transport/udp/transport.hpp index 8784b7c8f..4a64cfa3a 100644 --- a/include/libcyphal/transport/udp/transport.hpp +++ b/include/libcyphal/transport/udp/transport.hpp @@ -57,6 +57,19 @@ class TransportImpl final : public IUdpTransport { return cetl::nullopt; } + + CETL_NODISCARD cetl::optional setLocalNodeId(const NodeId node_id) noexcept override + { + if (node_id > UDPARD_NODE_ID_MAX) + { + return ArgumentError{}; + } + + // TODO: Implement! + + return ArgumentError{}; + } + CETL_NODISCARD ProtocolParams getProtocolParams() const noexcept override { return ProtocolParams{}; diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index e00519f1e..e2ec3e94c 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -112,6 +112,26 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) } } +TEST_F(TestCanTransport, setLocalNodeId) +{ + std::array media_array{&media_mock_}; + auto maybe_transport = makeTransport(mr_, mux_mock_, media_array, 0, {}); + EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); + auto transport = cetl::get>(std::move(maybe_transport)); + + EXPECT_THAT(transport->setLocalNodeId(CANARD_NODE_ID_MAX + 1), Optional(testing::A())); + EXPECT_THAT(transport->getLocalNodeId(), Eq(cetl::nullopt)); + + EXPECT_THAT(transport->setLocalNodeId(CANARD_NODE_ID_MAX), Eq(cetl::nullopt)); + EXPECT_THAT(transport->getLocalNodeId(), Optional(CANARD_NODE_ID_MAX)); + + EXPECT_THAT(transport->setLocalNodeId(CANARD_NODE_ID_MAX), Optional(testing::A())); + EXPECT_THAT(transport->getLocalNodeId(), Optional(CANARD_NODE_ID_MAX)); + + EXPECT_THAT(transport->setLocalNodeId(0), Optional(testing::A())); + EXPECT_THAT(transport->getLocalNodeId(), Optional(CANARD_NODE_ID_MAX)); +} + TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) { // No media From 1d62193a916e089b206ca4e92d58fbe26bc99a05 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 25 Apr 2024 12:53:21 +0300 Subject: [PATCH 36/64] Allow setting the same node ID multiple times #verification --- include/libcyphal/transport/can/transport.hpp | 12 ++++++++++-- include/libcyphal/transport/scattered_buffer.hpp | 1 + include/libcyphal/transport/transport.hpp | 7 +++++-- test/unittest/transport/can/test_can_transport.cpp | 2 +- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index d7a261467..ca6d8c9a6 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -125,12 +125,20 @@ class TransportImpl final : public ICanTransport, private TransportDelegate { return ArgumentError{}; } - if (canard_instance().node_id != CANARD_NODE_ID_UNSET) + + // Allow setting the same node ID multiple times, but only once otherwise. + // + auto& ins = canard_instance(); + if (ins.node_id == node_id) + { + return cetl::nullopt; + } + if (ins.node_id != CANARD_NODE_ID_UNSET) { return ArgumentError{}; } - canard_instance().node_id = static_cast(node_id); + ins.node_id = static_cast(node_id); return cetl::nullopt; } diff --git a/include/libcyphal/transport/scattered_buffer.hpp b/include/libcyphal/transport/scattered_buffer.hpp index 354db0f03..8d89a3daa 100644 --- a/include/libcyphal/transport/scattered_buffer.hpp +++ b/include/libcyphal/transport/scattered_buffer.hpp @@ -40,6 +40,7 @@ class ScatteredBuffer final protected: Interface() = default; + ~Interface() override = default; Interface(Interface&&) noexcept = default; Interface& operator=(Interface&&) noexcept = default; diff --git a/include/libcyphal/transport/transport.hpp b/include/libcyphal/transport/transport.hpp index 29b394fb2..779eaa805 100644 --- a/include/libcyphal/transport/transport.hpp +++ b/include/libcyphal/transport/transport.hpp @@ -33,12 +33,15 @@ class ITransport : public IRunnable /// @brief Sets the local node ID. /// /// It's only possible to set the local node ID once. Subsequent calls will return an argument error. + /// The only exception is when the current node ID is the same as the one being set - no operation is performed. + /// /// A concrete transport implementation may have a specific/limited range of valid node IDs. For example, /// - an UDP transport may have a range of 0...65534 node ids (see `UDPARD_NODE_ID_MAX` in `udpard.h`) /// - a CAN bus transport may have a range of 0...127 node ids (see `CANARD_NODE_ID_MAX` in `canard.h`) /// - /// @param node_id Specific node ID assigned to this transport interface. - /// @return `nullopt` on success; otherwise an `ArgumentError` in case of the subsequent calls or ID out of range. + /// @param node_id Specific node ID to be assigned to this transport interface. + /// @return `nullopt` on successful set (or when node ID is the same). + /// Otherwise an `ArgumentError` in case of the subsequent calls or ID out of range. /// CETL_NODISCARD virtual cetl::optional setLocalNodeId(const NodeId node_id) noexcept = 0; diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index e2ec3e94c..9569912f7 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -125,7 +125,7 @@ TEST_F(TestCanTransport, setLocalNodeId) EXPECT_THAT(transport->setLocalNodeId(CANARD_NODE_ID_MAX), Eq(cetl::nullopt)); EXPECT_THAT(transport->getLocalNodeId(), Optional(CANARD_NODE_ID_MAX)); - EXPECT_THAT(transport->setLocalNodeId(CANARD_NODE_ID_MAX), Optional(testing::A())); + EXPECT_THAT(transport->setLocalNodeId(CANARD_NODE_ID_MAX), Eq(cetl::nullopt)); EXPECT_THAT(transport->getLocalNodeId(), Optional(CANARD_NODE_ID_MAX)); EXPECT_THAT(transport->setLocalNodeId(0), Optional(testing::A())); From 090b34fea618ec4130aeaba88809aaa07d8c520a Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 25 Apr 2024 16:36:28 +0300 Subject: [PATCH 37/64] added test for CAN tx to redundant media with "temporary not ready" condition --- test/unittest/gtest_helpers.hpp | 50 ++++- test/unittest/test_utilities.hpp | 40 ++++ test/unittest/tracking_memory_resource.hpp | 6 +- .../transport/can/test_can_msg_rx_session.cpp | 11 +- .../transport/can/test_can_msg_tx_session.cpp | 145 +++---------- .../transport/can/test_can_transport.cpp | 203 ++++++++++++++++-- .../transport/test_contiguous_payload.cpp | 8 +- 7 files changed, 300 insertions(+), 163 deletions(-) create mode 100644 test/unittest/test_utilities.hpp diff --git a/test/unittest/gtest_helpers.hpp b/test/unittest/gtest_helpers.hpp index 057ed9cdf..3b8cb7cc4 100644 --- a/test/unittest/gtest_helpers.hpp +++ b/test/unittest/gtest_helpers.hpp @@ -216,12 +216,48 @@ inline testing::Matcher SubjectOfCanIdEq(PortId subject_id) return SubjectOfCanIdMatcher(subject_id); } -class FrameLastByteMatcher +class SourceNodeCanIdMatcher { public: using is_gtest_matcher = void; - explicit FrameLastByteMatcher(TransferId transfer_id, bool is_start, bool is_end, bool is_toggle) + explicit SourceNodeCanIdMatcher(NodeId node_id) + : node_id_{node_id} + { + } + + bool MatchAndExplain(const CanId& can_id, std::ostream* os) const + { + const auto node_id = can_id & CANARD_NODE_ID_MAX; + if (os) + { + *os << "node_id=" << node_id; + } + return node_id == node_id_; + } + void DescribeTo(std::ostream* os) const + { + *os << "node_id==" << node_id_; + } + void DescribeNegationTo(std::ostream* os) const + { + *os << "node_id!=" << node_id_; + } + +private: + const NodeId node_id_; +}; +inline testing::Matcher SourceNodeOfCanIdEq(NodeId node_id) +{ + return SourceNodeCanIdMatcher(node_id); +} + +class TailByteMatcher +{ +public: + using is_gtest_matcher = void; + + explicit TailByteMatcher(TransferId transfer_id, bool is_start, bool is_end, bool is_toggle) : transfer_id_{static_cast(transfer_id & CANARD_TRANSFER_ID_MAX)} , is_start_{is_start} , is_end_{is_end} @@ -268,12 +304,12 @@ class FrameLastByteMatcher const bool is_end_; const bool is_toggle_; }; -inline testing::Matcher FrameLastByteEq(TransferId transfer_id, - bool is_start = true, - bool is_end = true, - bool is_toggle = true) +inline testing::Matcher TailByteEq(TransferId transfer_id, + bool is_start = true, + bool is_end = true, + bool is_toggle = true) { - return FrameLastByteMatcher(transfer_id, is_start, is_end, is_toggle); + return TailByteMatcher(transfer_id, is_start, is_end, is_toggle); } } // namespace can diff --git a/test/unittest/test_utilities.hpp b/test/unittest/test_utilities.hpp new file mode 100644 index 000000000..9824574da --- /dev/null +++ b/test/unittest/test_utilities.hpp @@ -0,0 +1,40 @@ +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#ifndef LIBCYPHAL_TEST_UTILITIES_HPP +#define LIBCYPHAL_TEST_UTILITIES_HPP + +#include + +#include + +namespace libcyphal +{ +namespace test_utilities +{ + +inline constexpr cetl::byte b(std::uint8_t b) +{ + return static_cast(b); +} + +template +std::array makeIotaArray(const std::uint8_t init) +{ + std::array arr{}; + std::iota(reinterpret_cast(arr.begin()), reinterpret_cast(arr.end()), init); + return arr; +} + +template +std::array, 1> makeSpansFrom(const std::array& payload) +{ + return {payload}; +} + +} // namespace test_utilities +} // namespace libcyphal + +#endif // LIBCYPHAL_TEST_UTILITIES_HPP diff --git a/test/unittest/tracking_memory_resource.hpp b/test/unittest/tracking_memory_resource.hpp index 5f019f4cf..95f9598e5 100644 --- a/test/unittest/tracking_memory_resource.hpp +++ b/test/unittest/tracking_memory_resource.hpp @@ -52,9 +52,7 @@ class TrackingMemoryResource final : public cetl::pmr::memory_resource void do_deallocate(void* ptr, std::size_t size_bytes, std::size_t) override { - std::free(ptr); - - auto prev_alloc = std::find_if(allocations.cbegin(), allocations.cend(), [&](const auto& alloc) { + auto prev_alloc = std::find_if(allocations.cbegin(), allocations.cend(), [ptr](const auto& alloc) { return alloc.pointer == ptr; }); if (prev_alloc != allocations.cend()) @@ -62,6 +60,8 @@ class TrackingMemoryResource final : public cetl::pmr::memory_resource allocations.erase(prev_alloc); } total_deallocated_bytes += size_bytes; + + std::free(ptr); } #if (__cplusplus < CETL_CPP_STANDARD_17) diff --git a/test/unittest/transport/can/test_can_msg_rx_session.cpp b/test/unittest/transport/can/test_can_msg_rx_session.cpp index 7cf3f1634..f32624234 100644 --- a/test/unittest/transport/can/test_can_msg_rx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_rx_session.cpp @@ -9,6 +9,7 @@ #include "../multiplexer_mock.hpp" #include "../../gtest_helpers.hpp" #include "../../test_scheduler.hpp" +#include "../../test_utilities.hpp" #include "../../memory_resource_mock.hpp" #include "../../tracking_memory_resource.hpp" @@ -21,6 +22,7 @@ using byte = cetl::byte; using namespace libcyphal; using namespace libcyphal::transport; using namespace libcyphal::transport::can; +using namespace libcyphal::test_utilities; using testing::_; using testing::Eq; @@ -63,11 +65,6 @@ class TestCanMsgRxSession : public testing::Test return cetl::get>(std::move(maybe_transport)); } - static constexpr byte b(std::uint8_t b) - { - return static_cast(b); - } - // MARK: Data members: TestScheduler scheduler_{}; @@ -122,7 +119,7 @@ TEST_F(TestCanMsgRxSession, run_and_receive) scheduler_.setNow(TimePoint{1s}); const auto rx_timestamp = now(); - EXPECT_CALL(media_mock_, pop(_)).WillOnce([&](const cetl::span payload) { + EXPECT_CALL(media_mock_, pop(_)).WillOnce([=](auto payload) { EXPECT_THAT(payload.size(), CANARD_MTU_MAX); payload[0] = b(42); @@ -154,7 +151,7 @@ TEST_F(TestCanMsgRxSession, run_and_receive) { scheduler_.setNow(TimePoint{2s}); - EXPECT_CALL(media_mock_, pop(_)).WillOnce([](const cetl::span payload) { + EXPECT_CALL(media_mock_, pop(_)).WillOnce([](auto payload) { EXPECT_THAT(payload.size(), CANARD_MTU_MAX); return cetl::nullopt; }); diff --git a/test/unittest/transport/can/test_can_msg_tx_session.cpp b/test/unittest/transport/can/test_can_msg_tx_session.cpp index 73252bbaa..40502d965 100644 --- a/test/unittest/transport/can/test_can_msg_tx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_tx_session.cpp @@ -9,6 +9,7 @@ #include "../multiplexer_mock.hpp" #include "../../gtest_helpers.hpp" #include "../../test_scheduler.hpp" +#include "../../test_utilities.hpp" #include "../../memory_resource_mock.hpp" #include "../../tracking_memory_resource.hpp" @@ -21,6 +22,7 @@ using byte = cetl::byte; using namespace libcyphal; using namespace libcyphal::transport; using namespace libcyphal::transport::can; +using namespace libcyphal::test_utilities; using testing::_; using testing::Eq; @@ -29,10 +31,10 @@ using testing::IsNull; using testing::IsEmpty; using testing::NotNull; using testing::Optional; +using testing::InSequence; using testing::StrictMock; using testing::ElementsAre; using testing::VariantWith; -using testing::InSequence; using namespace std::chrono_literals; @@ -57,40 +59,22 @@ class TestCanMsgTxSession : public testing::Test } CETL_NODISCARD UniquePtr makeTransport(cetl::pmr::memory_resource& mr, + IMedia* extra_media = nullptr, const std::size_t tx_capacity = 16) { - std::array media_array{&media_mock_}; + std::array media_array{&media_mock_, extra_media}; auto maybe_transport = can::makeTransport(mr, mux_mock_, media_array, tx_capacity, {}); EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); return cetl::get>(std::move(maybe_transport)); } - static constexpr byte b(std::uint8_t b) - { - return static_cast(b); - } - - template - std::array makeIotaArray(const std::uint8_t init = 0) - { - std::array arr{}; - std::iota(reinterpret_cast(arr.begin()), reinterpret_cast(arr.end()), init); - return arr; - } - - template - std::array, 1> makeSpansFrom(const std::array& payload) - { - return {payload}; - } - // MARK: Data members: TestScheduler scheduler_{}; TrackingMemoryResource mr_; - StrictMock media_mock_{}; StrictMock mux_mock_{}; + StrictMock media_mock_{}; }; // MARK: Tests: @@ -164,17 +148,16 @@ TEST_F(TestCanMsgTxSession, send_empty_payload) auto maybe_error = session->send(metadata, empty_payload); EXPECT_THAT(maybe_error, Eq(cetl::nullopt)); - EXPECT_CALL(media_mock_, push(_, _, _)) - .WillOnce([&](const TimePoint deadline, const CanId can_id, const cetl::span payload) { - EXPECT_THAT(now(), send_time + 10ms); - EXPECT_THAT(deadline, send_time + timeout); - EXPECT_THAT(can_id, SubjectOfCanIdEq(123)); - EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())); + EXPECT_CALL(media_mock_, push(_, _, _)).WillOnce([&](auto deadline, auto can_id, auto payload) { + EXPECT_THAT(now(), send_time + 10ms); + EXPECT_THAT(deadline, send_time + timeout); + EXPECT_THAT(can_id, SubjectOfCanIdEq(123)); + EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())); - auto flb = FrameLastByteEq(metadata.transfer_id); - EXPECT_THAT(payload, ElementsAre(flb)); - return true; - }); + auto tbm = TailByteEq(metadata.transfer_id); + EXPECT_THAT(payload, ElementsAre(tbm)); + return true; + }); scheduler_.runNow(+10ms, [&] { transport->run(now()); }); scheduler_.runNow(+10ms, [&] { transport->run(now()); }); @@ -225,7 +208,7 @@ TEST_F(TestCanMsgTxSession, send_7bytes_payload_with_500ms_timeout) scheduler_.runNow(+10s); const auto send_time = now(); - const auto payload = makeIotaArray('0'); + const auto payload = makeIotaArray('1'); const TransferMetadata metadata{0x03, send_time, Priority::High}; auto maybe_error = session->send(metadata, makeSpansFrom(payload)); @@ -234,96 +217,18 @@ TEST_F(TestCanMsgTxSession, send_7bytes_payload_with_500ms_timeout) // Emulate run calls just on the very edge of the 500ms deadline (just 1us before it). // Payload should be sent successfully. // - EXPECT_CALL(media_mock_, push(TimePoint{send_time + timeout}, _, _)) - .WillOnce([&](const TimePoint, const CanId can_id, const cetl::span payload) { - EXPECT_THAT(now(), send_time + timeout - 1us); - EXPECT_THAT(can_id, SubjectOfCanIdEq(17)); - EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())); - - auto flb = FrameLastByteEq(metadata.transfer_id); - EXPECT_THAT(payload, ElementsAre(b('0'), b('1'), b('2'), b('3'), b('4'), b('5'), b('6'), flb)); - return true; - }); + EXPECT_CALL(media_mock_, push(TimePoint{send_time + timeout}, _, _)).WillOnce([&](auto, auto can_id, auto payload) { + EXPECT_THAT(now(), send_time + timeout - 1us); + EXPECT_THAT(can_id, SubjectOfCanIdEq(17)); + EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())); + + auto tbm = TailByteEq(metadata.transfer_id); + EXPECT_THAT(payload, ElementsAre(b('1'), b('2'), b('3'), b('4'), b('5'), b('6'), b('7'), tbm)); + return true; + }); // scheduler_.runNow(timeout - 1us, [&] { transport->run(now()); }); scheduler_.runNow(+0us, [&] { transport->run(now()); }); } -TEST_F(TestCanMsgTxSession, sending_multiframe_payload_should_fail_for_anonymous) -{ - EXPECT_CALL(media_mock_, pop(_)).WillRepeatedly(Return(cetl::nullopt)); - - auto transport = makeTransport(mr_); - - auto maybe_session = transport->makeMessageTxSession({7}); - EXPECT_THAT(maybe_session, VariantWith>(_)); - auto session = cetl::get>(std::move(maybe_session)); - EXPECT_THAT(session, NotNull()); - - scheduler_.runNow(+10s); - const auto send_time = now(); - - const auto payload = makeIotaArray('0'); - const TransferMetadata metadata{0x13, send_time, Priority::Nominal}; - - auto maybe_error = session->send(metadata, makeSpansFrom(payload)); - EXPECT_THAT(maybe_error, Optional(VariantWith(_))); - - scheduler_.runNow(+10us, [&] { transport->run(now()); }); - scheduler_.runNow(10us, [&] { session->run(now()); }); -} - -TEST_F(TestCanMsgTxSession, sending_multiframe_payload_for_non_anonymous) -{ - EXPECT_CALL(media_mock_, pop(_)).WillRepeatedly(Return(cetl::nullopt)); - - auto transport = makeTransport(mr_); - EXPECT_THAT(transport->setLocalNodeId(0x45), Eq(cetl::nullopt)); - - auto maybe_session = transport->makeMessageTxSession({7}); - EXPECT_THAT(maybe_session, VariantWith>(_)); - auto session = cetl::get>(std::move(maybe_session)); - EXPECT_THAT(session, NotNull()); - - scheduler_.runNow(+10s); - const auto timeout = 1s; - const auto send_time = now(); - - const auto payload = makeIotaArray('0'); - const TransferMetadata metadata{0x13, send_time, Priority::Nominal}; - - auto maybe_error = session->send(metadata, makeSpansFrom(payload)); - EXPECT_THAT(maybe_error, Eq(cetl::nullopt)); - - { - InSequence s; - - EXPECT_CALL(media_mock_, push(_, _, _)) - .WillOnce([&](const TimePoint deadline, const CanId can_id, const cetl::span payload) { - EXPECT_THAT(now(), send_time + 10us); - EXPECT_THAT(deadline, send_time + timeout); - EXPECT_THAT(can_id, SubjectOfCanIdEq(7)); - EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())); - - auto flb = FrameLastByteEq(metadata.transfer_id, true, false); - EXPECT_THAT(payload, ElementsAre(b('0'), b('1'), b('2'), b('3'), b('4'), b('5'), b('6'), flb)); - return true; - }); - EXPECT_CALL(media_mock_, push(_, _, _)) - .WillOnce([&](const TimePoint deadline, const CanId can_id, const cetl::span payload) { - EXPECT_THAT(now(), send_time + 10us); - EXPECT_THAT(deadline, send_time + timeout); - EXPECT_THAT(can_id, SubjectOfCanIdEq(7)); - EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())); - - auto flb = FrameLastByteEq(metadata.transfer_id, false, true, false); - EXPECT_THAT(payload, ElementsAre(b('7'), _, _ /* CRC bytes */, flb)); - return true; - }); - } - - scheduler_.runNow(+10us, [&] { transport->run(now()); }); - scheduler_.runNow(+10us, [&] { transport->run(now()); }); -} - } // namespace diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index 9569912f7..2de0af8f9 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -7,6 +7,9 @@ #include "media_mock.hpp" #include "../multiplexer_mock.hpp" +#include "../../gtest_helpers.hpp" +#include "../../test_scheduler.hpp" +#include "../../test_utilities.hpp" #include "../../memory_resource_mock.hpp" #include "../../tracking_memory_resource.hpp" @@ -18,6 +21,7 @@ namespace using namespace libcyphal; using namespace libcyphal::transport; using namespace libcyphal::transport::can; +using namespace libcyphal::test_utilities; using testing::_; using testing::Eq; @@ -26,15 +30,18 @@ using testing::IsNull; using testing::IsEmpty; using testing::NotNull; using testing::Optional; +using testing::InSequence; using testing::StrictMock; using testing::VariantWith; +using namespace std::chrono_literals; + class TestCanTransport : public testing::Test { protected: void SetUp() override { - EXPECT_CALL(media_mock_, getMtu()).WillRepeatedly(Return(CANARD_MTU_MAX)); + EXPECT_CALL(media_mock_, getMtu()).WillRepeatedly(Return(CANARD_MTU_CAN_CLASSIC)); } void TearDown() override @@ -44,8 +51,25 @@ class TestCanTransport : public testing::Test // EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); } + TimePoint now() const + { + return scheduler_.now(); + } + + CETL_NODISCARD UniquePtr makeTransport(cetl::pmr::memory_resource& mr, + IMedia* extra_media = nullptr, + const std::size_t tx_capacity = 16) + { + std::array media_array{&media_mock_, extra_media}; + + auto maybe_transport = can::makeTransport(mr, mux_mock_, media_array, tx_capacity, {}); + EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); + return cetl::get>(std::move(maybe_transport)); + } + // MARK: Data members: + TestScheduler scheduler_{}; TrackingMemoryResource mr_; StrictMock media_mock_{}; StrictMock mux_mock_{}; @@ -62,7 +86,7 @@ TEST_F(TestCanTransport, makeTransport_no_memory) EXPECT_CALL(mr_mock, do_allocate(sizeof(can::detail::TransportImpl), _)).WillOnce(Return(nullptr)); std::array media_array{&media_mock_}; - auto maybe_transport = makeTransport(mr_mock, mux_mock_, media_array, 0, {}); + auto maybe_transport = can::makeTransport(mr_mock, mux_mock_, media_array, 0, {}); EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } @@ -71,7 +95,7 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) // Anonymous node { std::array media_array{&media_mock_}; - auto maybe_transport = makeTransport(mr_, mux_mock_, media_array, 0, {}); + auto maybe_transport = can::makeTransport(mr_, mux_mock_, media_array, 0, {}); EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); auto transport = cetl::get>(std::move(maybe_transport)); @@ -83,7 +107,7 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) const auto node_id = cetl::make_optional(static_cast(42)); std::array media_array{&media_mock_}; - auto maybe_transport = makeTransport(mr_, mux_mock_, media_array, 0, node_id); + auto maybe_transport = can::makeTransport(mr_, mux_mock_, media_array, 0, node_id); EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); auto transport = cetl::get>(std::move(maybe_transport)); @@ -96,7 +120,7 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(Return(CANARD_MTU_MAX)); std::array media_array{&media_mock_, nullptr, &media_mock2}; - auto maybe_transport = makeTransport(mr_, mux_mock_, media_array, 0, {}); + auto maybe_transport = can::makeTransport(mr_, mux_mock_, media_array, 0, {}); EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); } @@ -107,17 +131,14 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) EXPECT_CALL(media_mock3, getMtu()).WillRepeatedly(Return(CANARD_MTU_MAX)); std::array media_array{&media_mock_, &media_mock2, &media_mock3}; - auto maybe_transport = makeTransport(mr_, mux_mock_, media_array, 0, {}); + auto maybe_transport = can::makeTransport(mr_, mux_mock_, media_array, 0, {}); EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); } } TEST_F(TestCanTransport, setLocalNodeId) { - std::array media_array{&media_mock_}; - auto maybe_transport = makeTransport(mr_, mux_mock_, media_array, 0, {}); - EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); - auto transport = cetl::get>(std::move(maybe_transport)); + auto transport = makeTransport(mr_); EXPECT_THAT(transport->setLocalNodeId(CANARD_NODE_ID_MAX + 1), Optional(testing::A())); EXPECT_THAT(transport->getLocalNodeId(), Eq(cetl::nullopt)); @@ -138,7 +159,7 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) { const auto node_id = cetl::make_optional(static_cast(CANARD_NODE_ID_MAX)); - const auto maybe_transport = makeTransport(mr_, mux_mock_, {}, 0, node_id); + const auto maybe_transport = can::makeTransport(mr_, mux_mock_, {}, 0, node_id); EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } @@ -147,7 +168,7 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) const auto node_id = cetl::make_optional(static_cast(CANARD_NODE_ID_MAX + 1)); std::array media_array{&media_mock_}; - const auto maybe_transport = makeTransport(mr_, mux_mock_, media_array, 0, node_id); + const auto maybe_transport = can::makeTransport(mr_, mux_mock_, media_array, 0, node_id); EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } @@ -156,7 +177,7 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) const auto node_id = cetl::make_optional(static_cast(CANARD_NODE_ID_UNSET)); std::array media_array{&media_mock_}; - const auto maybe_transport = makeTransport(mr_, mux_mock_, media_array, 0, node_id); + const auto maybe_transport = can::makeTransport(mr_, mux_mock_, media_array, 0, node_id); EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } @@ -166,7 +187,7 @@ TEST_F(TestCanTransport, makeTransport_with_invalid_arguments) const auto node_id = cetl::make_optional(too_big); std::array media_array{&media_mock_}; - const auto maybe_transport = makeTransport(mr_, mux_mock_, media_array, 0, node_id); + const auto maybe_transport = can::makeTransport(mr_, mux_mock_, media_array, 0, node_id); EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } } @@ -177,7 +198,7 @@ TEST_F(TestCanTransport, getProtocolParams) EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(Return(CANARD_MTU_MAX)); std::array media_array{&media_mock_, &media_mock2}; - auto transport = cetl::get>(makeTransport(mr_, mux_mock_, media_array, 0, {})); + auto transport = cetl::get>(can::makeTransport(mr_, mux_mock_, media_array, 0, {})); EXPECT_CALL(media_mock_, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_FD)); EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(testing::Return(CANARD_MTU_CAN_CLASSIC)); @@ -202,8 +223,7 @@ TEST_F(TestCanTransport, getProtocolParams) TEST_F(TestCanTransport, makeMessageRxSession) { - std::array media_array{&media_mock_}; - auto transport = cetl::get>(makeTransport(mr_, mux_mock_, media_array, 0, {})); + auto transport = makeTransport(mr_); auto maybe_rx_session = transport->makeMessageRxSession({42, 123}); EXPECT_THAT(maybe_rx_session, VariantWith>(NotNull())); @@ -215,8 +235,7 @@ TEST_F(TestCanTransport, makeMessageRxSession) TEST_F(TestCanTransport, makeMessageRxSession_invalid_subject_id) { - std::array media_array{&media_mock_}; - auto transport = cetl::get>(makeTransport(mr_, mux_mock_, media_array, 0, {})); + auto transport = makeTransport(mr_); auto maybe_rx_session = transport->makeMessageRxSession({0, CANARD_SUBJECT_ID_MAX + 1}); EXPECT_THAT(maybe_rx_session, VariantWith(VariantWith(_))); @@ -224,8 +243,7 @@ TEST_F(TestCanTransport, makeMessageRxSession_invalid_subject_id) TEST_F(TestCanTransport, makeMessageTxSession) { - std::array media_array{&media_mock_}; - auto transport = cetl::get>(makeTransport(mr_, mux_mock_, media_array, 0, {})); + auto transport = makeTransport(mr_); auto maybe_tx_session = transport->makeMessageTxSession({123}); EXPECT_THAT(maybe_tx_session, VariantWith>(NotNull())); @@ -234,4 +252,147 @@ TEST_F(TestCanTransport, makeMessageTxSession) EXPECT_THAT(session->getParams().subject_id, 123); } + +TEST_F(TestCanTransport, sending_multiframe_payload_should_fail_for_anonymous) +{ + EXPECT_CALL(media_mock_, pop(_)).WillRepeatedly(Return(cetl::nullopt)); + + auto transport = makeTransport(mr_); + + auto maybe_session = transport->makeMessageTxSession({7}); + EXPECT_THAT(maybe_session, VariantWith>(_)); + auto session = cetl::get>(std::move(maybe_session)); + EXPECT_THAT(session, NotNull()); + + scheduler_.runNow(+10s); + const auto send_time = now(); + + const auto payload = makeIotaArray('0'); + const TransferMetadata metadata{0x13, send_time, Priority::Nominal}; + + auto maybe_error = session->send(metadata, makeSpansFrom(payload)); + EXPECT_THAT(maybe_error, Optional(VariantWith(_))); + + scheduler_.runNow(+10us, [&] { transport->run(now()); }); + scheduler_.runNow(10us, [&] { session->run(now()); }); +} + +TEST_F(TestCanTransport, sending_multiframe_payload_for_non_anonymous) +{ + EXPECT_CALL(media_mock_, pop(_)).WillRepeatedly(Return(cetl::nullopt)); + + auto transport = makeTransport(mr_); + EXPECT_THAT(transport->setLocalNodeId(0x45), Eq(cetl::nullopt)); + + auto maybe_session = transport->makeMessageTxSession({7}); + EXPECT_THAT(maybe_session, VariantWith>(_)); + auto session = cetl::get>(std::move(maybe_session)); + EXPECT_THAT(session, NotNull()); + + scheduler_.runNow(+10s); + const auto timeout = 1s; + const auto send_time = now(); + + const auto payload = makeIotaArray('0'); + const TransferMetadata metadata{0x13, send_time, Priority::Nominal}; + + auto maybe_error = session->send(metadata, makeSpansFrom(payload)); + EXPECT_THAT(maybe_error, Eq(cetl::nullopt)); + + { + InSequence s; + + EXPECT_CALL(media_mock_, push(_, _, _)).WillOnce([&](auto deadline, auto can_id, auto payload) { + EXPECT_THAT(now(), send_time + 10us); + EXPECT_THAT(deadline, send_time + timeout); + EXPECT_THAT(can_id, AllOf(SubjectOfCanIdEq(7), SourceNodeOfCanIdEq(0x45))); + EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())); + + auto tbm = TailByteEq(metadata.transfer_id, true, false); + EXPECT_THAT(payload, ElementsAre(b('0'), b('1'), b('2'), b('3'), b('4'), b('5'), b('6'), tbm)); + return true; + }); + EXPECT_CALL(media_mock_, push(_, _, _)).WillOnce([&](auto deadline, auto can_id, auto payload) { + EXPECT_THAT(now(), send_time + 10us); + EXPECT_THAT(deadline, send_time + timeout); + EXPECT_THAT(can_id, AllOf(SubjectOfCanIdEq(7), SourceNodeOfCanIdEq(0x45))); + EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())); + + auto tbm = TailByteEq(metadata.transfer_id, false, true, false); + EXPECT_THAT(payload, ElementsAre(b('7'), _, _ /* CRC bytes */, tbm)); + return true; + }); + } + + scheduler_.runNow(+10us, [&] { transport->run(now()); }); + scheduler_.runNow(+10us, [&] { transport->run(now()); }); +} + +TEST_F(TestCanTransport, send_multiframe_payload_to_redundant_not_ready_media) +{ + StrictMock media_mock2{}; + EXPECT_CALL(media_mock_, pop(_)).WillRepeatedly(Return(cetl::nullopt)); + EXPECT_CALL(media_mock2, pop(_)).WillRepeatedly(Return(cetl::nullopt)); + EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(Return(CANARD_MTU_CAN_CLASSIC)); + + auto transport = makeTransport(mr_, &media_mock2); + EXPECT_THAT(transport->setLocalNodeId(0x45), Eq(cetl::nullopt)); + + auto maybe_session = transport->makeMessageTxSession({7}); + EXPECT_THAT(maybe_session, VariantWith>(_)); + auto session = cetl::get>(std::move(maybe_session)); + EXPECT_THAT(session, NotNull()); + + scheduler_.runNow(+10s); + const auto timeout = 1s; + const auto send_time = now(); + + const auto payload = makeIotaArray<10>('0'); + const TransferMetadata metadata{0x13, send_time, Priority::Nominal}; + + auto maybe_error = session->send(metadata, makeSpansFrom(payload)); + EXPECT_THAT(maybe_error, Eq(cetl::nullopt)); + + { + InSequence s; + + auto expectMediaCalls = [&](MediaMock& media_mock, const std::string& ctx, TimePoint when) { + EXPECT_CALL(media_mock, push(_, _, _)).WillOnce([&, ctx, when](auto deadline, auto can_id, auto payload) { + EXPECT_THAT(now(), when) << ctx; + EXPECT_THAT(deadline, send_time + timeout) << ctx; + EXPECT_THAT(can_id, AllOf(SubjectOfCanIdEq(7), SourceNodeOfCanIdEq(0x45))) << ctx; + EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())) << ctx; + + auto tbm = TailByteEq(metadata.transfer_id, true, false); + EXPECT_THAT(payload, ElementsAre(b('0'), b('1'), b('2'), b('3'), b('4'), b('5'), b('6'), tbm)) << ctx; + return true; + }); + EXPECT_CALL(media_mock, push(_, _, _)).WillOnce([&, ctx, when](auto deadline, auto can_id, auto payload) { + EXPECT_THAT(now(), when) << ctx; + EXPECT_THAT(deadline, send_time + timeout) << ctx; + EXPECT_THAT(can_id, AllOf(SubjectOfCanIdEq(7), SourceNodeOfCanIdEq(0x45))) << ctx; + EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())) << ctx; + + auto tbm = TailByteEq(metadata.transfer_id, false, true, false); + EXPECT_THAT(payload, ElementsAre(b('7'), b('8'), b('9'), _, _ /* CRC bytes */, tbm)) << ctx; + return true; + }); + }; + + // Emulate once that the first media is not ready to push fragment (@10us). So transport will + // switch to the second media, and only on the next run will (@20us) retry with the first media again. + // + EXPECT_CALL(media_mock_, push(_, _, _)).WillOnce([&](auto, auto, auto) { + EXPECT_THAT(now(), send_time + 10us); + return false; + }); + expectMediaCalls(media_mock2, "M#2", send_time + 10us); + expectMediaCalls(media_mock_, "M#1", send_time + 20us); + } + + scheduler_.runNow(+10us, [&] { transport->run(now()); }); + scheduler_.runNow(+10us, [&] { transport->run(now()); }); + scheduler_.runNow(+10us, [&] { transport->run(now()); }); +} + } // namespace diff --git a/test/unittest/transport/test_contiguous_payload.cpp b/test/unittest/transport/test_contiguous_payload.cpp index d4d9c9863..028192a75 100644 --- a/test/unittest/transport/test_contiguous_payload.cpp +++ b/test/unittest/transport/test_contiguous_payload.cpp @@ -5,6 +5,7 @@ #include +#include "../test_utilities.hpp" #include "../memory_resource_mock.hpp" #include "../tracking_memory_resource.hpp" @@ -13,6 +14,8 @@ namespace { +using namespace libcyphal::test_utilities; + using byte = cetl::byte; using ContiguousPayload = libcyphal::transport::detail::ContiguousPayload; @@ -33,11 +36,6 @@ class TestContiguousPayload : public testing::Test EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); } - static constexpr byte b(std::uint8_t b) - { - return static_cast(b); - } - // MARK: Data members: TrackingMemoryResource mr_; From 865fe6eb7ceda82805f1dde5a54d5d152f28478c Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Fri, 26 Apr 2024 12:16:11 +0300 Subject: [PATCH 38/64] adding `std::` and removing `inline` --- include/libcyphal/transport/can/delegate.hpp | 10 +++++----- include/libcyphal/transport/can/msg_rx_session.hpp | 2 +- include/libcyphal/transport/can/transport.hpp | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp index ab4a5062c..58d12effb 100644 --- a/include/libcyphal/transport/can/delegate.hpp +++ b/include/libcyphal/transport/can/delegate.hpp @@ -99,17 +99,17 @@ class TransportDelegate canard_instance().user_reference = this; } - CETL_NODISCARD inline CanardInstance& canard_instance() noexcept + CETL_NODISCARD CanardInstance& canard_instance() noexcept { return canard_instance_; } - CETL_NODISCARD inline const CanardInstance& canard_instance() const noexcept + CETL_NODISCARD const CanardInstance& canard_instance() const noexcept { return canard_instance_; } - CETL_NODISCARD inline cetl::pmr::memory_resource& memory() const noexcept + CETL_NODISCARD cetl::pmr::memory_resource& memory() const noexcept { return memory_; } @@ -163,7 +163,7 @@ class TransportDelegate alignas(std::max_align_t) std::size_t size; }; - CETL_NODISCARD static inline TransportDelegate& getSelfFrom(const CanardInstance* const ins) + CETL_NODISCARD static TransportDelegate& getSelfFrom(const CanardInstance* const ins) { CETL_DEBUG_ASSERT(ins != nullptr, "Expected canard instance."); CETL_DEBUG_ASSERT(ins->user_reference != nullptr, "Expected `this` transport as user reference."); @@ -171,7 +171,7 @@ class TransportDelegate return *static_cast(ins->user_reference); } - CETL_NODISCARD static void* allocateMemoryForCanard(CanardInstance* ins, size_t amount) + CETL_NODISCARD static void* allocateMemoryForCanard(CanardInstance* ins, std::size_t amount) { auto& self = getSelfFrom(ins); diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp index cf24d1dda..5568b0907 100644 --- a/include/libcyphal/transport/can/msg_rx_session.hpp +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -60,7 +60,7 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate const auto result = ::canardRxSubscribe(&delegate.canard_instance(), CanardTransferKindMessage, static_cast(params_.subject_id), - static_cast(params_.extent_bytes), + static_cast(params_.extent_bytes), CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC, &subscription_); if (result < 0) diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index ca6d8c9a6..850c635ea 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -55,7 +55,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate // const auto media_count = static_cast( std::count_if(media.begin(), media.end(), [](IMedia* const media) { return media != nullptr; })); - if ((media_count == 0) || (media_count > std::numeric_limits::max())) + if ((media_count == 0) || (media_count > std::numeric_limits::max())) { return ArgumentError{}; } @@ -216,7 +216,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate // MARK: TransportDelegate - CETL_NODISCARD inline TransportDelegate& asDelegate() + CETL_NODISCARD TransportDelegate& asDelegate() { return *this; } @@ -252,7 +252,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate { public: Media(const std::size_t _index, IMedia& _interface, const std::size_t tx_capacity) - : index{static_cast(_index)} + : index{static_cast(_index)} , interface{_interface} , canard_tx_queue{::canardTxInit(tx_capacity, _interface.getMtu())} { @@ -262,9 +262,9 @@ class TransportImpl final : public ICanTransport, private TransportDelegate Media& operator=(const Media&) = delete; Media& operator=(Media&&) noexcept = delete; - const uint8_t index; - IMedia& interface; - CanardTxQueue canard_tx_queue; + const std::uint8_t index; + IMedia& interface; + CanardTxQueue canard_tx_queue; }; using MediaArray = libcyphal::detail::VarArray; From 2b12f90a35e79420b00c1f3c0a4f8f479232eb9c Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Fri, 26 Apr 2024 13:29:18 +0300 Subject: [PATCH 39/64] =?UTF-8?q?`SessionAlreadyExistsError`=20=E2=86=92?= =?UTF-8?q?=20`AlreadyExistsError`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/libcyphal/transport/can/transport.hpp | 2 +- include/libcyphal/transport/errors.hpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index 850c635ea..ed2ffa9d5 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -295,7 +295,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate } if (hasSubscription > 0) { - return SessionAlreadyExistsError{}; + return AlreadyExistsError{}; } return {}; diff --git a/include/libcyphal/transport/errors.hpp b/include/libcyphal/transport/errors.hpp index c899bb890..82f12a0b3 100644 --- a/include/libcyphal/transport/errors.hpp +++ b/include/libcyphal/transport/errors.hpp @@ -33,7 +33,7 @@ struct PlatformError final std::uint32_t code; }; -struct SessionAlreadyExistsError final +struct AlreadyExistsError final {}; // TODO: Delete it when everything is implemented. @@ -48,7 +48,7 @@ using AnyError = cetl::variant; /// @brief Defines any possible factory error at Cyphal transport layer. From e69f8afa3a7405214b554b0a65575f54fecc3d6b Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Fri, 26 Apr 2024 13:31:33 +0300 Subject: [PATCH 40/64] =?UTF-8?q?`memcpy`=20=E2=86=92=20`memmove`=20#verif?= =?UTF-8?q?ication?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit also more comments --- include/libcyphal/transport/can/delegate.hpp | 45 ++++++++++++++++--- include/libcyphal/transport/can/media.hpp | 4 ++ .../transport/can/msg_rx_session.hpp | 19 +++++--- .../transport/can/msg_tx_session.hpp | 15 +++++-- include/libcyphal/transport/can/transport.hpp | 28 ++++++------ .../transport/contiguous_payload.hpp | 6 ++- include/libcyphal/transport/multiplexer.hpp | 4 ++ include/libcyphal/transport/udp/media.hpp | 4 ++ include/libcyphal/transport/udp/transport.hpp | 27 ++++++----- test/unittest/test_scheduler.hpp | 2 +- test/unittest/tracking_memory_resource.hpp | 8 ++-- .../transport/can/test_can_delegate.cpp | 2 +- 12 files changed, 114 insertions(+), 50 deletions(-) diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp index 58d12effb..d006fd783 100644 --- a/include/libcyphal/transport/can/delegate.hpp +++ b/include/libcyphal/transport/can/delegate.hpp @@ -19,9 +19,18 @@ namespace transport { namespace can { + +/// Internal implementation details of the CAN transport. +/// Not supposed to be used directly by the users of the library. +/// namespace detail { +/// This internal transport delegate class serves the following purposes: +/// 1. It provides memory management functions for the Canard library. +/// 2. It provides a way to convert Canard error codes to `AnyError` type. +/// 3. It provides interface to access the transport from various session classes. +/// class TransportDelegate { // 1141F5C0-2E61-44BF-9F0E-FA1C518CD517 @@ -29,6 +38,8 @@ class TransportDelegate type_id_type<0x11, 0x41, 0xF5, 0xC0, 0x2E, 0x61, 0x44, 0xBF, 0x9F, 0x0E, 0xFA, 0x1C, 0x51, 0x8C, 0xD5, 0x17>; public: + /// @brief RAII class to manage memory allocated by Canard library. + /// class CanardMemory final : public cetl::rtti_helper { public: @@ -48,7 +59,7 @@ class TransportDelegate } CanardMemory(const CanardMemory&) = delete; - ~CanardMemory() override + ~CanardMemory() final { if (buffer_ != nullptr) { @@ -61,14 +72,14 @@ class TransportDelegate // MARK: ScatteredBuffer::Interface - CETL_NODISCARD std::size_t size() const noexcept override + CETL_NODISCARD std::size_t size() const noexcept final { return payload_size_; } CETL_NODISCARD std::size_t copy(const std::size_t offset_bytes, void* const destination, - const std::size_t length_bytes) const override + const std::size_t length_bytes) const final { CETL_DEBUG_ASSERT((destination != nullptr) || (length_bytes == 0), "Destination could be null only with zero bytes ask."); @@ -79,7 +90,7 @@ class TransportDelegate } const auto bytes_to_copy = std::min(length_bytes, payload_size_ - offset_bytes); - memcpy(destination, static_cast(buffer_) + offset_bytes, bytes_to_copy); + std::memmove(destination, static_cast(buffer_) + offset_bytes, bytes_to_copy); return bytes_to_copy; } @@ -114,10 +125,10 @@ class TransportDelegate return memory_; } - static cetl::optional anyErrorFromCanard(const int32_t result) + static cetl::optional anyErrorFromCanard(const std::int32_t result) { // Canard error results are negative, so we need to negate them to get the error code. - const auto canard_error = static_cast(-result); + const auto canard_error = static_cast(-result); if (canard_error == CANARD_ERROR_INVALID_ARGUMENT) { @@ -131,6 +142,8 @@ class TransportDelegate return {}; } + /// @brief Releases memory allocated for canard (by previous `allocateMemoryForCanard` call). + /// void freeCanardMemory(void* const pointer) { if (pointer == nullptr) @@ -144,6 +157,10 @@ class TransportDelegate memory_.deallocate(memory_header, memory_header->size); } + /// @brief Sends transfer to each media canard TX queue of the transport. + /// + /// Internal method which is in use by TX session implementations to delegate actual sending to transport. + /// CETL_NODISCARD virtual cetl::optional sendTransfer(const CanardMicrosecond deadline, const CanardTransferMetadata& metadata, const void* const payload, @@ -163,6 +180,10 @@ class TransportDelegate alignas(std::max_align_t) std::size_t size; }; + /// @brief Converts Canard instance to the transport delegate. + /// + /// In use to bridge two worlds: canard library and transport entities. + /// CETL_NODISCARD static TransportDelegate& getSelfFrom(const CanardInstance* const ins) { CETL_DEBUG_ASSERT(ins != nullptr, "Expected canard instance."); @@ -171,6 +192,11 @@ class TransportDelegate return *static_cast(ins->user_reference); } + /// @brief Allocates memory for canard instance. + /// + /// Implicitly stores the size of the allocated memory in the prepended `CanardMemoryHeader` struct, + /// so that it will possible later (at `freeCanardMemory`) restore the original size. + /// CETL_NODISCARD static void* allocateMemoryForCanard(CanardInstance* ins, std::size_t amount) { auto& self = getSelfFrom(ins); @@ -189,6 +215,8 @@ class TransportDelegate return ++memory_header; } + /// @brief Releases memory allocated for canard instance (by previous `allocateMemoryForCanard` call). + /// static void freeCanardMemory(CanardInstance* ins, void* pointer) { auto& self = getSelfFrom(ins); @@ -202,6 +230,9 @@ class TransportDelegate }; // TransportDelegate +/// This internal session delegate class serves the following purpose: it provides interface +/// to access a session from transport (by casting canard's `user_reference` member to this class). +/// class SessionDelegate { public: @@ -210,6 +241,8 @@ class SessionDelegate SessionDelegate& operator=(const SessionDelegate&) = delete; SessionDelegate& operator=(SessionDelegate&&) noexcept = delete; + /// @brief Accepts a received transfer from the transport dedicated to this RX session. + /// virtual void acceptRxTransfer(const CanardRxTransfer& transfer) = 0; protected: diff --git a/include/libcyphal/transport/can/media.hpp b/include/libcyphal/transport/can/media.hpp index 74a8147b3..94a2f1c0b 100644 --- a/include/libcyphal/transport/can/media.hpp +++ b/include/libcyphal/transport/can/media.hpp @@ -33,6 +33,10 @@ struct RxMetadata final std::size_t payload_size; }; +/// @brief Defines interface to a custom CAN bus media implementation. +/// +/// Implementation is supposed to be provided by an user of the library. +/// class IMedia { public: diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp index 5568b0907..85a822e6e 100644 --- a/include/libcyphal/transport/can/msg_rx_session.hpp +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -19,6 +19,10 @@ namespace transport { namespace can { + +/// Internal implementation details of the CAN transport. +/// Not supposed to be used directly by the users of the library. +/// namespace detail { class MessageRxSession final : public IMessageRxSession, private SessionDelegate @@ -74,7 +78,7 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate subscription_.user_reference = static_cast(this); } - ~MessageRxSession() override + ~MessageRxSession() final { if (is_subscribed_) { @@ -87,12 +91,12 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate private: // MARK: IMessageRxSession - CETL_NODISCARD MessageRxParams getParams() const noexcept override + CETL_NODISCARD MessageRxParams getParams() const noexcept final { return params_; } - CETL_NODISCARD cetl::optional receive() override + CETL_NODISCARD cetl::optional receive() final { cetl::optional result{}; result.swap(last_rx_transfer_); @@ -101,7 +105,7 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate // MARK: IRxSession - void setTransferIdTimeout(const Duration timeout) override + void setTransferIdTimeout(const Duration timeout) final { const auto timeout_us = std::chrono::duration_cast(timeout); if (timeout_us.count() > 0) @@ -112,11 +116,14 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate // MARK: IRunnable - void run(const TimePoint) override {} + void run(const TimePoint) final + { + // Nothing to do here currently. + } // MARK: SessionDelegate - void acceptRxTransfer(const CanardRxTransfer& transfer) override + void acceptRxTransfer(const CanardRxTransfer& transfer) final { const auto priority = static_cast(transfer.metadata.priority); const auto transfer_id = static_cast(transfer.metadata.transfer_id); diff --git a/include/libcyphal/transport/can/msg_tx_session.hpp b/include/libcyphal/transport/can/msg_tx_session.hpp index a330dd4f9..fc4cf13f0 100644 --- a/include/libcyphal/transport/can/msg_tx_session.hpp +++ b/include/libcyphal/transport/can/msg_tx_session.hpp @@ -20,6 +20,10 @@ namespace transport { namespace can { + +/// Internal implementation details of the CAN transport. +/// Not supposed to be used directly by the users of the library. +/// namespace detail { class MessageTxSession final : public IMessageTxSession @@ -55,20 +59,20 @@ class MessageTxSession final : public IMessageTxSession private: // MARK: ITxSession - void setSendTimeout(const Duration timeout) override + void setSendTimeout(const Duration timeout) final { send_timeout_ = timeout; } // MARK: IMessageTxSession - CETL_NODISCARD MessageTxParams getParams() const noexcept override + CETL_NODISCARD MessageTxParams getParams() const noexcept final { return params_; } CETL_NODISCARD cetl::optional send(const TransferMetadata& metadata, - const PayloadFragments payload_fragments) override + const PayloadFragments payload_fragments) final { // libcanard currently does not support fragmented payloads (at `canardTxPush`). // so we need to concatenate them when there are more than one non-empty fragment. @@ -97,7 +101,10 @@ class MessageTxSession final : public IMessageTxSession // MARK: IRunnable - void run(const TimePoint) override {} + void run(const TimePoint) final + { + // Nothing to do here currently. + } // MARK: Data members: diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index ed2ffa9d5..e503770cd 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -28,6 +28,9 @@ namespace can class ICanTransport : public ITransport {}; +/// Internal implementation details of the CAN transport. +/// Not supposed to be used directly by the users of the library. +/// namespace detail { class TransportImpl final : public ICanTransport, private TransportDelegate @@ -98,7 +101,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate canard_instance().node_id = canard_node_id; } - ~TransportImpl() override + ~TransportImpl() final { for (Media& media : media_array_) { @@ -109,7 +112,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate private: // MARK: ITransport - CETL_NODISCARD cetl::optional getLocalNodeId() const noexcept override + CETL_NODISCARD cetl::optional getLocalNodeId() const noexcept final { if (canard_instance().node_id > CANARD_NODE_ID_MAX) { @@ -119,7 +122,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate return cetl::make_optional(static_cast(canard_instance().node_id)); } - CETL_NODISCARD cetl::optional setLocalNodeId(const NodeId node_id) noexcept override + CETL_NODISCARD cetl::optional setLocalNodeId(const NodeId node_id) noexcept final { if (node_id > CANARD_NODE_ID_MAX) { @@ -142,7 +145,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate return cetl::nullopt; } - CETL_NODISCARD ProtocolParams getProtocolParams() const noexcept override + CETL_NODISCARD ProtocolParams getProtocolParams() const noexcept final { const auto min_mtu = reduceMedia(std::numeric_limits::max(), [](const std::size_t mtu, const Media& media) { @@ -153,7 +156,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate } CETL_NODISCARD Expected, AnyError> makeMessageRxSession( - const MessageRxParams& params) override + const MessageRxParams& params) final { auto any_error = ensureNewSessionFor(CanardTransferKindMessage, params.subject_id, CANARD_SUBJECT_ID_MAX); if (any_error.has_value()) @@ -165,13 +168,13 @@ class TransportImpl final : public ICanTransport, private TransportDelegate } CETL_NODISCARD Expected, AnyError> makeMessageTxSession( - const MessageTxParams& params) override + const MessageTxParams& params) final { return MessageTxSession::make(asDelegate(), params); } CETL_NODISCARD Expected, AnyError> makeRequestRxSession( - const RequestRxParams& params) override + const RequestRxParams& params) final { auto any_error = ensureNewSessionFor(CanardTransferKindRequest, params.service_id, CANARD_SERVICE_ID_MAX); if (any_error.has_value()) @@ -182,14 +185,13 @@ class TransportImpl final : public ICanTransport, private TransportDelegate return NotImplementedError{}; } - CETL_NODISCARD Expected, AnyError> makeRequestTxSession( - const RequestTxParams&) override + CETL_NODISCARD Expected, AnyError> makeRequestTxSession(const RequestTxParams&) final { return NotImplementedError{}; } CETL_NODISCARD Expected, AnyError> makeResponseRxSession( - const ResponseRxParams& params) override + const ResponseRxParams& params) final { auto any_error = ensureNewSessionFor(CanardTransferKindResponse, params.service_id, CANARD_SERVICE_ID_MAX); if (any_error.has_value()) @@ -201,14 +203,14 @@ class TransportImpl final : public ICanTransport, private TransportDelegate } CETL_NODISCARD Expected, AnyError> makeResponseTxSession( - const ResponseTxParams&) override + const ResponseTxParams&) final { return NotImplementedError{}; } // MARK: IRunnable - void run(const TimePoint now) override + void run(const TimePoint now) final { runMediaTransmit(now); runMediaReceive(); @@ -224,7 +226,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate CETL_NODISCARD cetl::optional sendTransfer(const CanardMicrosecond deadline, const CanardTransferMetadata& metadata, const void* const payload, - const std::size_t payload_size) override + const std::size_t payload_size) final { // TODO: Rework error handling strategy. // Currently, we return the last error encountered, but we should consider all errors somehow. diff --git a/include/libcyphal/transport/contiguous_payload.hpp b/include/libcyphal/transport/contiguous_payload.hpp index 86dd093a1..1aef20216 100644 --- a/include/libcyphal/transport/contiguous_payload.hpp +++ b/include/libcyphal/transport/contiguous_payload.hpp @@ -13,6 +13,10 @@ namespace libcyphal { namespace transport { + +/// Internal implementation details of the transport layer. +/// Not supposed to be used directly by the users of the library. +/// namespace detail { @@ -56,7 +60,7 @@ class ContiguousPayload final std::size_t offset = 0; for (const Fragment frag : payload_fragments) { - std::memcpy(&buffer[offset], frag.data(), frag.size()); + std::memmove(&buffer[offset], frag.data(), frag.size()); offset += frag.size(); } } diff --git a/include/libcyphal/transport/multiplexer.hpp b/include/libcyphal/transport/multiplexer.hpp index 1300f316a..919d2eb1a 100644 --- a/include/libcyphal/transport/multiplexer.hpp +++ b/include/libcyphal/transport/multiplexer.hpp @@ -11,6 +11,10 @@ namespace libcyphal namespace transport { +/// @brief Defines interface to a custom multiplexer implementation. +/// +/// Implementation is supposed to be provided by an user of the library. +/// class IMultiplexer { public: diff --git a/include/libcyphal/transport/udp/media.hpp b/include/libcyphal/transport/udp/media.hpp index 08be99820..f85899752 100644 --- a/include/libcyphal/transport/udp/media.hpp +++ b/include/libcyphal/transport/udp/media.hpp @@ -13,6 +13,10 @@ namespace transport namespace udp { +/// @brief Defines interface to a custom UDP media implementation. +/// +/// Implementation is supposed to be provided by an user of the library. +/// class IMedia { public: diff --git a/include/libcyphal/transport/udp/transport.hpp b/include/libcyphal/transport/udp/transport.hpp index 4a64cfa3a..0b9078eeb 100644 --- a/include/libcyphal/transport/udp/transport.hpp +++ b/include/libcyphal/transport/udp/transport.hpp @@ -22,6 +22,9 @@ namespace udp class IUdpTransport : public ITransport {}; +/// Internal implementation details of the UDP transport. +/// Not supposed to be used directly by the users of the library. +/// namespace detail { @@ -53,12 +56,12 @@ class TransportImpl final : public IUdpTransport private: // MARK: ITransport - CETL_NODISCARD cetl::optional getLocalNodeId() const noexcept override + CETL_NODISCARD cetl::optional getLocalNodeId() const noexcept final { return cetl::nullopt; } - CETL_NODISCARD cetl::optional setLocalNodeId(const NodeId node_id) noexcept override + CETL_NODISCARD cetl::optional setLocalNodeId(const NodeId node_id) noexcept final { if (node_id > UDPARD_NODE_ID_MAX) { @@ -70,45 +73,41 @@ class TransportImpl final : public IUdpTransport return ArgumentError{}; } - CETL_NODISCARD ProtocolParams getProtocolParams() const noexcept override + CETL_NODISCARD ProtocolParams getProtocolParams() const noexcept final { return ProtocolParams{}; } - CETL_NODISCARD Expected, AnyError> makeMessageRxSession( - const MessageRxParams&) override + CETL_NODISCARD Expected, AnyError> makeMessageRxSession(const MessageRxParams&) final { return NotImplementedError{}; } - CETL_NODISCARD Expected, AnyError> makeMessageTxSession( - const MessageTxParams&) override + CETL_NODISCARD Expected, AnyError> makeMessageTxSession(const MessageTxParams&) final { return NotImplementedError{}; } - CETL_NODISCARD Expected, AnyError> makeRequestRxSession( - const RequestRxParams&) override + CETL_NODISCARD Expected, AnyError> makeRequestRxSession(const RequestRxParams&) final { return NotImplementedError{}; } - CETL_NODISCARD Expected, AnyError> makeRequestTxSession( - const RequestTxParams&) override + CETL_NODISCARD Expected, AnyError> makeRequestTxSession(const RequestTxParams&) final { return NotImplementedError{}; } CETL_NODISCARD Expected, AnyError> makeResponseRxSession( - const ResponseRxParams&) override + const ResponseRxParams&) final { return NotImplementedError{}; } CETL_NODISCARD Expected, AnyError> makeResponseTxSession( - const ResponseTxParams&) override + const ResponseTxParams&) final { return NotImplementedError{}; } // MARK: IRunnable - void run(const TimePoint) override {} + void run(const TimePoint) final {} }; // TransportImpl diff --git a/test/unittest/test_scheduler.hpp b/test/unittest/test_scheduler.hpp index d4e839c33..385d4ccf0 100644 --- a/test/unittest/test_scheduler.hpp +++ b/test/unittest/test_scheduler.hpp @@ -11,7 +11,7 @@ namespace libcyphal { -struct TestScheduler +struct TestScheduler final { explicit TestScheduler(const TimePoint initial_now = {}) : now_{initial_now} diff --git a/test/unittest/tracking_memory_resource.hpp b/test/unittest/tracking_memory_resource.hpp index 95f9598e5..0b0b30ed5 100644 --- a/test/unittest/tracking_memory_resource.hpp +++ b/test/unittest/tracking_memory_resource.hpp @@ -32,7 +32,7 @@ class TrackingMemoryResource final : public cetl::pmr::memory_resource private: // MARK: cetl::pmr::memory_resource - void* do_allocate(std::size_t size_bytes, std::size_t alignment) override + void* do_allocate(std::size_t size_bytes, std::size_t alignment) final { if (alignment > alignof(std::max_align_t)) { @@ -50,7 +50,7 @@ class TrackingMemoryResource final : public cetl::pmr::memory_resource return ptr; } - void do_deallocate(void* ptr, std::size_t size_bytes, std::size_t) override + void do_deallocate(void* ptr, std::size_t size_bytes, std::size_t) final { auto prev_alloc = std::find_if(allocations.cbegin(), allocations.cend(), [ptr](const auto& alloc) { return alloc.pointer == ptr; @@ -66,7 +66,7 @@ class TrackingMemoryResource final : public cetl::pmr::memory_resource #if (__cplusplus < CETL_CPP_STANDARD_17) - void* do_reallocate(void* ptr, std::size_t old_size_bytes, std::size_t new_size_bytes, std::size_t) override + void* do_reallocate(void* ptr, std::size_t old_size_bytes, std::size_t new_size_bytes, std::size_t) final { total_allocated_bytes -= old_size_bytes; total_allocated_bytes += new_size_bytes; @@ -76,7 +76,7 @@ class TrackingMemoryResource final : public cetl::pmr::memory_resource #endif - bool do_is_equal(const memory_resource& rhs) const noexcept override + bool do_is_equal(const memory_resource& rhs) const noexcept final { return (&rhs == this); } diff --git a/test/unittest/transport/can/test_can_delegate.cpp b/test/unittest/transport/can/test_can_delegate.cpp index 9d5612cef..65334573f 100644 --- a/test/unittest/transport/can/test_can_delegate.cpp +++ b/test/unittest/transport/can/test_can_delegate.cpp @@ -30,7 +30,7 @@ using testing::VariantWith; class TestCanDelegate : public testing::Test { protected: - class TransportDelegateImpl : public detail::TransportDelegate + class TransportDelegateImpl final : public detail::TransportDelegate { public: explicit TransportDelegateImpl(cetl::pmr::memory_resource& memory) From f104f7d28e09408860467f5d26725ad232fe5fd0 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Fri, 26 Apr 2024 14:16:39 +0300 Subject: [PATCH 41/64] better coverage --- test/unittest/test_utilities.hpp | 11 +++++-- .../transport/can/test_can_msg_tx_session.cpp | 33 +++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/test/unittest/test_utilities.hpp b/test/unittest/test_utilities.hpp index 9824574da..47819d537 100644 --- a/test/unittest/test_utilities.hpp +++ b/test/unittest/test_utilities.hpp @@ -34,7 +34,14 @@ std::array, 1> makeSpansFrom(const std::array +std::array, 2> makeSpansFrom(const std::array& payload1, + const std::array& payload2) +{ + return {payload1, payload2}; +} + +} // namespace test_utilities +} // namespace libcyphal #endif // LIBCYPHAL_TEST_UTILITIES_HPP diff --git a/test/unittest/transport/can/test_can_msg_tx_session.cpp b/test/unittest/transport/can/test_can_msg_tx_session.cpp index 40502d965..6049c9769 100644 --- a/test/unittest/transport/can/test_can_msg_tx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_tx_session.cpp @@ -142,10 +142,9 @@ TEST_F(TestCanMsgTxSession, send_empty_payload) const auto send_time = now(); const auto timeout = 1s; - const PayloadFragments empty_payload{}; const TransferMetadata metadata{0x3AF52, send_time, Priority::Low}; - auto maybe_error = session->send(metadata, empty_payload); + auto maybe_error = session->send(metadata, {nullptr, 0}); EXPECT_THAT(maybe_error, Eq(cetl::nullopt)); EXPECT_CALL(media_mock_, push(_, _, _)).WillOnce([&](auto deadline, auto can_id, auto payload) { @@ -231,4 +230,34 @@ TEST_F(TestCanMsgTxSession, send_7bytes_payload_with_500ms_timeout) scheduler_.runNow(+0us, [&] { transport->run(now()); }); } +TEST_F(TestCanMsgTxSession, send_when_no_memory_for_contiguous_payload) +{ + EXPECT_CALL(media_mock_, pop(_)).WillRepeatedly(Return(cetl::nullopt)); + + StrictMock mr_mock{}; + mr_mock.redirectExpectedCallsTo(mr_); + + auto transport = makeTransport(mr_mock); + + // Emulate that there is no memory available for the expected contiguous payload. + const auto payload1 = makeIotaArray<1>('0'); + const auto payload2 = makeIotaArray<2>('1'); + EXPECT_CALL(mr_mock, do_allocate(sizeof(payload1) + sizeof(payload2), _)).WillOnce(Return(nullptr)); + + auto maybe_session = transport->makeMessageTxSession({17}); + EXPECT_THAT(maybe_session, VariantWith>(_)); + auto session = cetl::get>(std::move(maybe_session)); + EXPECT_THAT(session, NotNull()); + + scheduler_.runNow(+10s); + const auto send_time = now(); + + const TransferMetadata metadata{0x03, send_time, Priority::Optional}; + + auto maybe_error = session->send(metadata, makeSpansFrom(payload1, payload2)); + EXPECT_THAT(maybe_error, Optional(VariantWith(_))); + + scheduler_.runNow(+10ms, [&] { transport->run(scheduler_.now()); }); +} + } // namespace From 98a393485a12e24e64b80901a116d648567ec8ec Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Fri, 26 Apr 2024 15:33:38 +0300 Subject: [PATCH 42/64] better coverage --- .../transport/can/msg_rx_session.hpp | 30 ++++----------- include/libcyphal/transport/can/transport.hpp | 5 +-- .../transport/can/test_can_msg_rx_session.cpp | 38 +++++++++++++++++-- .../transport/can/test_can_transport.cpp | 24 +++++++++++- 4 files changed, 66 insertions(+), 31 deletions(-) diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp index 85a822e6e..99ad9558b 100644 --- a/include/libcyphal/transport/can/msg_rx_session.hpp +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -40,12 +40,7 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate CETL_NODISCARD static Expected, AnyError> make(TransportDelegate& delegate, const MessageRxParams& params) { - cetl::optional any_error{}; - auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Tag{}, delegate, params, any_error); - if (any_error.has_value()) - { - return any_error.value(); - } + auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Tag{}, delegate, params); if (session == nullptr) { return MemoryError{}; @@ -54,10 +49,7 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate return session; } - MessageRxSession(Tag, - TransportDelegate& delegate, - const MessageRxParams& params, - cetl::optional& out_error) + MessageRxSession(Tag, TransportDelegate& delegate, const MessageRxParams& params) : delegate_{delegate} , params_{params} { @@ -67,25 +59,18 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate static_cast(params_.extent_bytes), CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC, &subscription_); - if (result < 0) - { - out_error = TransportDelegate::anyErrorFromCanard(result); - return; - } + (void) result; + CETL_DEBUG_ASSERT(result >= 0, "There is no way currently to get an error here."); CETL_DEBUG_ASSERT(result > 0, "New subscription supposed to be made."); - is_subscribed_ = true; subscription_.user_reference = static_cast(this); } ~MessageRxSession() final { - if (is_subscribed_) - { - ::canardRxUnsubscribe(&delegate_.canard_instance(), - CanardTransferKindMessage, - static_cast(params_.subject_id)); - } + ::canardRxUnsubscribe(&delegate_.canard_instance(), + CanardTransferKindMessage, + static_cast(params_.subject_id)); } private: @@ -145,7 +130,6 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate TransportDelegate& delegate_; const MessageRxParams params_; - bool is_subscribed_{false}; CanardRxSubscription subscription_{}; cetl::optional last_rx_transfer_{}; diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index e503770cd..6ad39c8a0 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -291,10 +291,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate } const auto hasSubscription = ::canardRxHasSubscription(&canard_instance(), transfer_kind, port_id); - if (hasSubscription < 0) - { - return anyErrorFromCanard(hasSubscription); - } + CETL_DEBUG_ASSERT(hasSubscription >= 0, "There is no way currently to get an error here."); if (hasSubscription > 0) { return AlreadyExistsError{}; diff --git a/test/unittest/transport/can/test_can_msg_rx_session.cpp b/test/unittest/transport/can/test_can_msg_rx_session.cpp index f32624234..4272bd780 100644 --- a/test/unittest/transport/can/test_can_msg_rx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_rx_session.cpp @@ -32,6 +32,7 @@ using testing::IsEmpty; using testing::NotNull; using testing::Optional; using testing::StrictMock; +using testing::ElementsAre; using testing::VariantWith; using namespace std::chrono_literals; @@ -132,7 +133,7 @@ TEST_F(TestCanMsgRxSession, run_and_receive) scheduler_.runNow(+10ms, [&] { session->run(now()); }); const auto maybe_rx_transfer = session->receive(); - EXPECT_THAT(maybe_rx_transfer, Optional(_)); + ASSERT_THAT(maybe_rx_transfer, Optional(_)); const auto& rx_transfer = maybe_rx_transfer.value(); EXPECT_THAT(rx_transfer.metadata.timestamp, rx_timestamp); @@ -143,8 +144,7 @@ TEST_F(TestCanMsgRxSession, run_and_receive) std::array buffer{}; EXPECT_THAT(rx_transfer.payload.size(), 2); EXPECT_THAT(rx_transfer.payload.copy(0, buffer.data(), buffer.size()), 2); - EXPECT_THAT(buffer[0], 42); - EXPECT_THAT(buffer[1], 147); + EXPECT_THAT(buffer, ElementsAre(42, 147)); } // 2-nd iteration: no frames available @@ -162,6 +162,38 @@ TEST_F(TestCanMsgRxSession, run_and_receive) const auto maybe_rx_transfer = session->receive(); EXPECT_THAT(maybe_rx_transfer, Eq(cetl::nullopt)); } + + // 3-rd iteration: one anonymous frame available @ 3s + { + scheduler_.setNow(TimePoint{3s}); + const auto rx_timestamp = now(); + + EXPECT_CALL(media_mock_, pop(_)).WillOnce([=](auto payload) { + EXPECT_THAT(payload.size(), CANARD_MTU_MAX); + + payload[0] = b(42); + payload[1] = b(147); + payload[2] = b(0xEE); + return RxMetadata{rx_timestamp, 0x01'00'23'13, 3}; + }); + + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + scheduler_.runNow(+10ms, [&] { session->run(now()); }); + + const auto maybe_rx_transfer = session->receive(); + ASSERT_THAT(maybe_rx_transfer, Optional(_)); + const auto& rx_transfer = maybe_rx_transfer.value(); + + EXPECT_THAT(rx_transfer.metadata.timestamp, rx_timestamp); + EXPECT_THAT(rx_transfer.metadata.transfer_id, 0x0E); + EXPECT_THAT(rx_transfer.metadata.priority, Priority::Exceptional); + EXPECT_THAT(rx_transfer.metadata.publisher_node_id, Eq(cetl::nullopt)); + + std::array buffer{}; + EXPECT_THAT(rx_transfer.payload.size(), 2); + EXPECT_THAT(rx_transfer.payload.copy(0, buffer.data(), buffer.size()), 2); + EXPECT_THAT(buffer, ElementsAre(42, 147)); + } } } // namespace diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index 2de0af8f9..8e79e2620 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -90,6 +90,16 @@ TEST_F(TestCanTransport, makeTransport_no_memory) EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); } +TEST_F(TestCanTransport, makeTransport_too_many_media) +{ + // Canard use `std::uint8_t` as a media index, so 256+ media interfaces are not allowed. + std::array::max() + 1> media_array{}; + std::fill(media_array.begin(), media_array.end(), &media_mock_); + + auto maybe_transport = can::makeTransport(mr_, mux_mock_, media_array, 0, {}); + EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); +} + TEST_F(TestCanTransport, makeTransport_getLocalNodeId) { // Anonymous node @@ -241,6 +251,19 @@ TEST_F(TestCanTransport, makeMessageRxSession_invalid_subject_id) EXPECT_THAT(maybe_rx_session, VariantWith(VariantWith(_))); } +TEST_F(TestCanTransport, makeMessageRxSession_invalid_resubscription) +{ + auto transport = makeTransport(mr_); + + const PortId test_subject_id = 111; + + auto maybe_rx_session1 = transport->makeMessageRxSession({0, test_subject_id}); + EXPECT_THAT(maybe_rx_session1, VariantWith>(NotNull())); + + auto maybe_rx_session2 = transport->makeMessageRxSession({0, test_subject_id}); + EXPECT_THAT(maybe_rx_session2, VariantWith(VariantWith(_))); +} + TEST_F(TestCanTransport, makeMessageTxSession) { auto transport = makeTransport(mr_); @@ -252,7 +275,6 @@ TEST_F(TestCanTransport, makeMessageTxSession) EXPECT_THAT(session->getParams().subject_id, 123); } - TEST_F(TestCanTransport, sending_multiframe_payload_should_fail_for_anonymous) { EXPECT_CALL(media_mock_, pop(_)).WillRepeatedly(Return(cetl::nullopt)); From 3a0a6b9a7ba20e7ec609c869b27ab85382ed4822 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Fri, 26 Apr 2024 15:34:45 +0300 Subject: [PATCH 43/64] Update doxygen ini file (by `doxyge -u` command) - no warnings now. #verification --- docs/doxygen.ini | 295 +++++++++++++++++++++++++++++------------------ 1 file changed, 183 insertions(+), 112 deletions(-) diff --git a/docs/doxygen.ini b/docs/doxygen.ini index 814093a88..7530e18a9 100644 --- a/docs/doxygen.ini +++ b/docs/doxygen.ini @@ -1,4 +1,4 @@ -# Doxyfile 1.9.6 +# Doxyfile 1.10.0 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -63,6 +63,12 @@ PROJECT_BRIEF = @DOXYGEN_PROJECT_BRIEF@ PROJECT_LOGO = @DOXYGEN_LOGO@ +# With the PROJECT_ICON tag one can specify an icon that is included in the tabs +# when the HTML document is shown. Doxygen will copy the logo to the output +# directory. + +PROJECT_ICON = + # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If @@ -363,6 +369,17 @@ MARKDOWN_SUPPORT = YES TOC_INCLUDE_HEADINGS = 0 +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or @@ -487,6 +504,14 @@ LOOKUP_CACHE_SIZE = 0 NUM_PROC_THREADS = 1 +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -872,7 +897,14 @@ WARN_IF_UNDOC_ENUM_VAL = YES # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # at the end of the doxygen process doxygen will return with a non-zero status. -# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. # The default value is: NO. WARN_AS_ERROR = @DOXYGEN_WARN_AS_ERROR@ @@ -950,12 +982,12 @@ INPUT_FILE_ENCODING = # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, -# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C -# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, -# *.vhdl, *.ucf, *.qsf and *.ice. +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, +# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, +# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to +# be provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ @@ -1038,9 +1070,6 @@ EXCLUDE_PATTERNS = # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # ANamespace::AClass, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = @@ -1154,7 +1183,8 @@ FORTRAN_COMMENT_AFTER = 72 SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. +# multi-line macros, enums or list initialized variables directly into the +# documentation. # The default value is: NO. INLINE_SOURCES = NO @@ -1226,49 +1256,6 @@ USE_HTAGS = NO VERBATIM_HEADERS = NO -# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the -# clang parser (see: -# http://clang.llvm.org/) for more accurate parsing at the cost of reduced -# performance. This can be particularly helpful with template rich C++ code for -# which doxygen's built-in parser lacks the necessary type information. -# Note: The availability of this option depends on whether or not doxygen was -# generated with the -Duse_libclang=ON option for CMake. -# The default value is: NO. - -# TODO: cppreference tag file doesn't seem to be the right format when this is -# turned on. Wasn't able to figure out what the problem was but clang -# parsing does work if you remove the tag file. -CLANG_ASSISTED_PARSING = NO - -# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS -# tag is set to YES then doxygen will add the directory of each input to the -# include path. -# The default value is: YES. -# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. - -CLANG_ADD_INC_PATHS = NO - -# If clang assisted parsing is enabled you can provide the compiler with command -# line options that you would normally use when invoking the compiler. Note that -# the include paths will already be set by doxygen for the files and directories -# specified with INPUT and INCLUDE_PATH. -# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. - -CLANG_OPTIONS = - -# If clang assisted parsing is enabled you can provide the clang parser with the -# path to the directory containing a file called compile_commands.json. This -# file is the compilation database (see: -# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the -# options used when the source files were built. This is equivalent to -# specifying the -p option to a clang tool, such as clang-check. These options -# will then be passed to the parser. Any options specified with CLANG_OPTIONS -# will be added as well. -# Note: The availability of this option depends on whether or not doxygen was -# generated with the -Duse_libclang=ON option for CMake. - -CLANG_DATABASE_PATH = - #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- @@ -1426,15 +1413,6 @@ HTML_COLORSTYLE_SAT = 0 HTML_COLORSTYLE_GAMMA = 100 -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = NO - # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will @@ -1454,6 +1432,33 @@ HTML_DYNAMIC_MENUS = NO HTML_DYNAMIC_SECTIONS = NO +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + +# If the HTML_COPY_CLIPBOARD tag is set to YES then doxygen will show an icon in +# the top right corner of code and text fragments that allows the user to copy +# its content to the clipboard. Note this only works if supported by the browser +# and the web page is served via a secure context (see: +# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: +# protocol. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COPY_CLIPBOARD = YES + +# Doxygen stores a couple of settings persistently in the browser (via e.g. +# cookies). By default these settings apply to all HTML pages generated by +# doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# the settings under a project specific key, such that the user preferences will +# be stored separately. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_PROJECT_COOKIE = + # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to @@ -1584,6 +1589,16 @@ BINARY_TOC = NO TOC_EXPAND = NO +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help @@ -1820,8 +1835,8 @@ MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/ # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example -# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7-latest/tex.html -# #tex-and-latex-extensions): +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # For example for MathJax version 3 (see # http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): @@ -2072,9 +2087,16 @@ PDF_HYPERLINKS = YES USE_PDFLATEX = YES -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode -# command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. +# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error. +# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch +# mode nothing is printed on the terminal, errors are scrolled as if is +# hit at every error; missing files that TeX tries to input or request from +# keyboard input (\read on a not open input stream) cause the job to abort, +# NON_STOP In nonstop mode the diagnostic message will appear on the terminal, +# but there is no possibility of user interaction just like in batch mode, +# SCROLL In scroll mode, TeX will stop only for missing files to input or if +# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at +# each error, asking for user intervention. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -2095,14 +2117,6 @@ LATEX_HIDE_INDICES = NO LATEX_BIB_STYLE = plain -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_TIMESTAMP = NO - # The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) # path from which the emoji images will be read. If a relative path is entered, # it will be relative to the LATEX_OUTPUT directory. If left blank the @@ -2268,13 +2282,39 @@ DOCBOOK_OUTPUT = docbook #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures # the structure of the code including all documentation. Note that this feature # is still experimental and incomplete at the moment. # The default value is: NO. GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to Sqlite3 output +#--------------------------------------------------------------------------- + +# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3 +# database with symbols found by doxygen stored in tables. +# The default value is: NO. + +GENERATE_SQLITE3 = NO + +# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be +# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put +# in front of it. +# The default directory is: sqlite3. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_OUTPUT = sqlite3 + +# The SQLITE3_RECREATE_DB tag is set to YES, the existing doxygen_sqlite3.db +# database file will be recreated with each doxygen run. If set to NO, doxygen +# will warn if a database file is already found and not modify it. +# The default value is: YES. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_RECREATE_DB = YES + #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- @@ -2371,7 +2411,8 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = CETL_DOXYGEN LIBCYPHAL_DOXYGEN +PREDEFINED = CETL_DOXYGEN \ + LIBCYPHAL_DOXYGEN # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The @@ -2417,15 +2458,15 @@ TAGFILES = @DOXYGEN_TAGFILES@ GENERATE_TAGFILE = @DOXYGEN_OUTPUT_TAGFILE@ -# If the ALLEXTERNALS tag is set to YES, all external class will be listed in -# the class index. If set to NO, only the inherited external classes will be -# listed. +# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces +# will be listed in the class and namespace index. If set to NO, only the +# inherited external classes will be listed. # The default value is: NO. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will be +# in the topic index. If set to NO, only the current project's groups will be # listed. # The default value is: YES. @@ -2439,16 +2480,9 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES #--------------------------------------------------------------------------- -# Configuration options related to the dot tool +# Configuration options related to diagram generator tools #--------------------------------------------------------------------------- -# You can include diagrams made with dia in doxygen documentation. Doxygen will -# then run dia to produce the diagram and insert it in the documentation. The -# DIA_PATH tag allows you to specify the directory where the dia binary resides. -# If left empty dia is assumed to be found in the default search path. - -DIA_PATH = - # If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. # The default value is: YES. @@ -2457,7 +2491,7 @@ HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO # The default value is: NO. @@ -2510,13 +2544,19 @@ DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" DOT_FONTPATH = -# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a -# graph for each documented class showing the direct and indirect inheritance -# relations. In case HAVE_DOT is set as well dot will be used to draw the graph, -# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set -# to TEXT the direct and indirect inheritance relations will be shown as texts / -# links. -# Possible values are: NO, YES, TEXT and GRAPH. +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. Explicit enabling an inheritance +# graph or choosing a different representation for an inheritance graph of a +# specific class, can be accomplished by means of the command \inheritancegraph. +# Disabling an inheritance graph can be accomplished by means of the command +# \hideinheritancegraph. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. # The default value is: YES. CLASS_GRAPH = NO @@ -2524,15 +2564,21 @@ CLASS_GRAPH = NO # If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a # graph for each documented class showing the direct and indirect implementation # dependencies (inheritance, containment, and class references variables) of the -# class with other documented classes. +# class with other documented classes. Explicit enabling a collaboration graph, +# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the +# command \collaborationgraph. Disabling a collaboration graph can be +# accomplished by means of the command \hidecollaborationgraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. COLLABORATION_GRAPH = NO # If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. See also the chapter Grouping -# in the manual. +# groups, showing the direct groups dependencies. Explicit enabling a group +# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means +# of the command \groupgraph. Disabling a directory graph can be accomplished by +# means of the command \hidegroupgraph. See also the chapter Grouping in the +# manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2574,8 +2620,8 @@ DOT_UML_DETAILS = NO # The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters # to display on a single line. If the actual line length exceeds this threshold -# significantly it will wrapped across multiple lines. Some heuristics are apply -# to avoid ugly line breaks. +# significantly it will be wrapped across multiple lines. Some heuristics are +# applied to avoid ugly line breaks. # Minimum value: 0, maximum value: 1000, default value: 17. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2592,7 +2638,9 @@ TEMPLATE_RELATIONS = YES # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to # YES then doxygen will generate a graph for each documented file showing the # direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, +# can be accomplished by means of the command \includegraph. Disabling an +# include graph can be accomplished by means of the command \hideincludegraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2601,7 +2649,10 @@ INCLUDE_GRAPH = YES # If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are # set to YES then doxygen will generate a graph for each documented file showing # the direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set +# to NO, can be accomplished by means of the command \includedbygraph. Disabling +# an included by graph can be accomplished by means of the command +# \hideincludedbygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2641,7 +2692,10 @@ GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the # dependencies a directory has on other directories in a graphical way. The # dependency relations are determined by the #include relations between the -# files in the directories. +# files in the directories. Explicit enabling a directory graph, when +# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command +# \directorygraph. Disabling a directory graph can be accomplished by means of +# the command \hidedirectorygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2657,7 +2711,7 @@ DIR_GRAPH_MAX_DEPTH = 1 # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: -# http://www.graphviz.org/)). +# https://www.graphviz.org/)). # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). @@ -2694,11 +2748,12 @@ DOT_PATH = DOTFILE_DIRS = -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the \mscfile -# command). +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. -MSCFILE_DIRS = +DIA_PATH = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile @@ -2775,3 +2830,19 @@ GENERATE_LEGEND = YES # The default value is: YES. DOT_CLEANUP = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will +# use a built-in version of mscgen tool to produce the charts. Alternatively, +# the MSCGEN_TOOL tag can also specify the name an external tool. For instance, +# specifying prog as the value, doxygen will call the tool as prog -T +# -o . The external tool should support +# output file formats "png", "eps", "svg", and "ismap". + +MSCGEN_TOOL = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = From 50f26be306dbd842480dd1a06e29403de1b6e6c6 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Fri, 26 Apr 2024 15:56:29 +0300 Subject: [PATCH 44/64] build fix #verification --- test/unittest/transport/can/test_can_msg_rx_session.cpp | 4 ++-- test/unittest/transport/can/test_can_msg_tx_session.cpp | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/unittest/transport/can/test_can_msg_rx_session.cpp b/test/unittest/transport/can/test_can_msg_rx_session.cpp index 4272bd780..ff159a442 100644 --- a/test/unittest/transport/can/test_can_msg_rx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_rx_session.cpp @@ -126,7 +126,7 @@ TEST_F(TestCanMsgRxSession, run_and_receive) payload[0] = b(42); payload[1] = b(147); payload[2] = b(0xED); - return RxMetadata{rx_timestamp, 0x0C'00'23'45, 3}; + return RxMetadata{rx_timestamp, 0x0C'60'23'45, 3}; }); scheduler_.runNow(+10ms, [&] { transport->run(now()); }); @@ -174,7 +174,7 @@ TEST_F(TestCanMsgRxSession, run_and_receive) payload[0] = b(42); payload[1] = b(147); payload[2] = b(0xEE); - return RxMetadata{rx_timestamp, 0x01'00'23'13, 3}; + return RxMetadata{rx_timestamp, 0x01'60'23'13, 3}; }); scheduler_.runNow(+10ms, [&] { transport->run(now()); }); diff --git a/test/unittest/transport/can/test_can_msg_tx_session.cpp b/test/unittest/transport/can/test_can_msg_tx_session.cpp index 6049c9769..f893adbed 100644 --- a/test/unittest/transport/can/test_can_msg_tx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_tx_session.cpp @@ -142,9 +142,10 @@ TEST_F(TestCanMsgTxSession, send_empty_payload) const auto send_time = now(); const auto timeout = 1s; + const PayloadFragments empty_payload{}; const TransferMetadata metadata{0x3AF52, send_time, Priority::Low}; - auto maybe_error = session->send(metadata, {nullptr, 0}); + auto maybe_error = session->send(metadata, empty_payload); EXPECT_THAT(maybe_error, Eq(cetl::nullopt)); EXPECT_CALL(media_mock_, push(_, _, _)).WillOnce([&](auto deadline, auto can_id, auto payload) { From 960dc205e04a0416a42161b04df5ae1551d27013 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Fri, 26 Apr 2024 16:36:56 +0300 Subject: [PATCH 45/64] minor fix --- include/libcyphal/transport/can/transport.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index 6ad39c8a0..923e0d072 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -259,6 +259,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate , canard_tx_queue{::canardTxInit(tx_capacity, _interface.getMtu())} { } + ~Media() = default; Media(const Media&) = delete; Media(Media&&) noexcept = default; Media& operator=(const Media&) = delete; From dfe95f15dcc2a123d1b56287ad550696204f151c Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Fri, 26 Apr 2024 18:11:46 +0300 Subject: [PATCH 46/64] introduced svc tx sessions --- include/libcyphal/transport/can/delegate.hpp | 16 +- .../transport/can/msg_rx_session.hpp | 6 +- .../transport/can/svc_tx_sessions.hpp | 215 ++++++++++++++++++ include/libcyphal/transport/can/transport.hpp | 12 +- .../transport/can/test_can_msg_tx_session.cpp | 3 +- .../can/test_can_svc_tx_sessions.cpp | 106 +++++++++ 6 files changed, 340 insertions(+), 18 deletions(-) create mode 100644 include/libcyphal/transport/can/svc_tx_sessions.hpp create mode 100644 test/unittest/transport/can/test_can_svc_tx_sessions.cpp diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp index d006fd783..1a6a42c7c 100644 --- a/include/libcyphal/transport/can/delegate.hpp +++ b/include/libcyphal/transport/can/delegate.hpp @@ -233,23 +233,23 @@ class TransportDelegate /// This internal session delegate class serves the following purpose: it provides interface /// to access a session from transport (by casting canard's `user_reference` member to this class). /// -class SessionDelegate +class RxSessionDelegate { public: - SessionDelegate(const SessionDelegate&) = delete; - SessionDelegate(SessionDelegate&&) noexcept = delete; - SessionDelegate& operator=(const SessionDelegate&) = delete; - SessionDelegate& operator=(SessionDelegate&&) noexcept = delete; + RxSessionDelegate(const RxSessionDelegate&) = delete; + RxSessionDelegate(RxSessionDelegate&&) noexcept = delete; + RxSessionDelegate& operator=(const RxSessionDelegate&) = delete; + RxSessionDelegate& operator=(RxSessionDelegate&&) noexcept = delete; /// @brief Accepts a received transfer from the transport dedicated to this RX session. /// virtual void acceptRxTransfer(const CanardRxTransfer& transfer) = 0; protected: - SessionDelegate() = default; - ~SessionDelegate() = default; + RxSessionDelegate() = default; + ~RxSessionDelegate() = default; -}; // SessionDelegate +}; // RxSessionDelegate } // namespace detail } // namespace can diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp index 99ad9558b..c406586b1 100644 --- a/include/libcyphal/transport/can/msg_rx_session.hpp +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -25,7 +25,7 @@ namespace can /// namespace detail { -class MessageRxSession final : public IMessageRxSession, private SessionDelegate +class MessageRxSession final : public IMessageRxSession, private RxSessionDelegate { // In use to disable public construction. // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ @@ -63,7 +63,7 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate CETL_DEBUG_ASSERT(result >= 0, "There is no way currently to get an error here."); CETL_DEBUG_ASSERT(result > 0, "New subscription supposed to be made."); - subscription_.user_reference = static_cast(this); + subscription_.user_reference = static_cast(this); } ~MessageRxSession() final @@ -106,7 +106,7 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate // Nothing to do here currently. } - // MARK: SessionDelegate + // MARK: RxSessionDelegate void acceptRxTransfer(const CanardRxTransfer& transfer) final { diff --git a/include/libcyphal/transport/can/svc_tx_sessions.hpp b/include/libcyphal/transport/can/svc_tx_sessions.hpp new file mode 100644 index 000000000..9d41f9cef --- /dev/null +++ b/include/libcyphal/transport/can/svc_tx_sessions.hpp @@ -0,0 +1,215 @@ +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#ifndef LIBCYPHAL_TRANSPORT_CAN_SVC_TX_SESSIONS_HPP_INCLUDED +#define LIBCYPHAL_TRANSPORT_CAN_SVC_TX_SESSIONS_HPP_INCLUDED + +#include "delegate.hpp" +#include "libcyphal/transport/svc_sessions.hpp" +#include "libcyphal/transport/contiguous_payload.hpp" + +#include + +#include + +namespace libcyphal +{ +namespace transport +{ +namespace can +{ + +/// Internal implementation details of the CAN transport. +/// Not supposed to be used directly by the users of the library. +/// +namespace detail +{ + +/// @brief A class to represent a service request TX session. +/// +class SvcRequestTxSession final : public IRequestTxSession +{ + // In use to disable public construction. + // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ + struct Tag + { + explicit Tag() = default; + using Interface = IRequestTxSession; + using Concrete = SvcRequestTxSession; + }; + +public: + CETL_NODISCARD static Expected, AnyError> make(TransportDelegate& delegate, + const RequestTxParams& params) + { + auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Tag{}, delegate, params); + if (session == nullptr) + { + return MemoryError{}; + } + + return session; + } + + SvcRequestTxSession(Tag, TransportDelegate& delegate, const RequestTxParams& params) + : delegate_{delegate} + , params_{params} + { + } + +private: + // MARK: ITxSession + + void setSendTimeout(const Duration timeout) final + { + send_timeout_ = timeout; + } + + // MARK: IRequestTxSession + + CETL_NODISCARD RequestTxParams getParams() const noexcept final + { + return params_; + } + + CETL_NODISCARD cetl::optional send(const TransferMetadata& metadata, + const PayloadFragments payload_fragments) final + { + // libcanard currently does not support fragmented payloads (at `canardTxPush`). + // so we need to concatenate them when there are more than one non-empty fragment. + // See https://github.com/OpenCyphal/libcanard/issues/223 + // + const transport::detail::ContiguousPayload contiguous_payload{delegate_.memory(), payload_fragments}; + if ((contiguous_payload.data() == nullptr) && (contiguous_payload.size() > 0)) + { + return MemoryError{}; + } + + const TimePoint deadline = metadata.timestamp + send_timeout_; + const auto deadline_us = std::chrono::duration_cast(deadline.time_since_epoch()); + + const auto canard_metadata = CanardTransferMetadata{static_cast(metadata.priority), + CanardTransferKindRequest, + static_cast(params_.service_id), + delegate_.canard_instance().node_id, + static_cast(metadata.transfer_id)}; + + return delegate_.sendTransfer(static_cast(deadline_us.count()), + canard_metadata, + contiguous_payload.data(), + contiguous_payload.size()); + } + + // MARK: IRunnable + + void run(const TimePoint) final + { + // Nothing to do here currently. + } + + // MARK: Data members: + + TransportDelegate& delegate_; + const RequestTxParams params_; + Duration send_timeout_ = std::chrono::seconds{1}; + +}; // SvcRequestTxSession + +/// @brief A class to represent a service response TX session. +/// +class SvcResponseTxSession final : public IResponseTxSession +{ + // In use to disable public construction. + // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ + struct Tag + { + explicit Tag() = default; + using Interface = IResponseTxSession; + using Concrete = SvcResponseTxSession; + }; + +public: + CETL_NODISCARD static Expected, AnyError> make(TransportDelegate& delegate, + const ResponseTxParams& params) + { + auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Tag{}, delegate, params); + if (session == nullptr) + { + return MemoryError{}; + } + + return session; + } + + SvcResponseTxSession(Tag, TransportDelegate& delegate, const ResponseTxParams& params) + : delegate_{delegate} + , params_{params} + { + } + +private: + // MARK: ITxSession + + void setSendTimeout(const Duration timeout) final + { + send_timeout_ = timeout; + } + + // MARK: IResponseTxSession + + CETL_NODISCARD ResponseTxParams getParams() const noexcept final + { + return params_; + } + + CETL_NODISCARD cetl::optional send(const ServiceTransferMetadata& metadata, + const PayloadFragments payload_fragments) final + { + // libcanard currently does not support fragmented payloads (at `canardTxPush`). + // so we need to concatenate them when there are more than one non-empty fragment. + // See https://github.com/OpenCyphal/libcanard/issues/223 + // + const transport::detail::ContiguousPayload contiguous_payload{delegate_.memory(), payload_fragments}; + if ((contiguous_payload.data() == nullptr) && (contiguous_payload.size() > 0)) + { + return MemoryError{}; + } + + const TimePoint deadline = metadata.timestamp + send_timeout_; + const auto deadline_us = std::chrono::duration_cast(deadline.time_since_epoch()); + + const auto canard_metadata = CanardTransferMetadata{static_cast(metadata.priority), + CanardTransferKindResponse, + static_cast(params_.service_id), + delegate_.canard_instance().node_id, + static_cast(metadata.transfer_id)}; + + return delegate_.sendTransfer(static_cast(deadline_us.count()), + canard_metadata, + contiguous_payload.data(), + contiguous_payload.size()); + } + + // MARK: IRunnable + + void run(const TimePoint) final + { + // Nothing to do here currently. + } + + // MARK: Data members: + + TransportDelegate& delegate_; + const ResponseTxParams params_; + Duration send_timeout_ = std::chrono::seconds{1}; + +}; // SvcResponseTxSession + +} // namespace detail +} // namespace can +} // namespace transport +} // namespace libcyphal + +#endif // LIBCYPHAL_TRANSPORT_CAN_SVC_TX_SESSIONS_HPP_INCLUDED diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index 923e0d072..3eb32abae 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -10,6 +10,7 @@ #include "delegate.hpp" #include "msg_rx_session.hpp" #include "msg_tx_session.hpp" +#include "svc_tx_sessions.hpp" #include "libcyphal/transport/transport.hpp" #include "libcyphal/transport/multiplexer.hpp" @@ -185,9 +186,10 @@ class TransportImpl final : public ICanTransport, private TransportDelegate return NotImplementedError{}; } - CETL_NODISCARD Expected, AnyError> makeRequestTxSession(const RequestTxParams&) final + CETL_NODISCARD Expected, AnyError> makeRequestTxSession( + const RequestTxParams& params) final { - return NotImplementedError{}; + return SvcRequestTxSession::make(asDelegate(), params); } CETL_NODISCARD Expected, AnyError> makeResponseRxSession( @@ -203,9 +205,9 @@ class TransportImpl final : public ICanTransport, private TransportDelegate } CETL_NODISCARD Expected, AnyError> makeResponseTxSession( - const ResponseTxParams&) final + const ResponseTxParams& params) final { - return NotImplementedError{}; + return SvcResponseTxSession::make(asDelegate(), params); } // MARK: IRunnable @@ -365,7 +367,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate CETL_DEBUG_ASSERT(out_subscription != nullptr, "Expected subscription."); CETL_DEBUG_ASSERT(out_subscription->user_reference != nullptr, "Expected session delegate."); - const auto delegate = static_cast(out_subscription->user_reference); + const auto delegate = static_cast(out_subscription->user_reference); delegate->acceptRxTransfer(out_transfer); } } diff --git a/test/unittest/transport/can/test_can_msg_tx_session.cpp b/test/unittest/transport/can/test_can_msg_tx_session.cpp index f893adbed..b0a08d01f 100644 --- a/test/unittest/transport/can/test_can_msg_tx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_tx_session.cpp @@ -59,10 +59,9 @@ class TestCanMsgTxSession : public testing::Test } CETL_NODISCARD UniquePtr makeTransport(cetl::pmr::memory_resource& mr, - IMedia* extra_media = nullptr, const std::size_t tx_capacity = 16) { - std::array media_array{&media_mock_, extra_media}; + std::array media_array{&media_mock_}; auto maybe_transport = can::makeTransport(mr, mux_mock_, media_array, tx_capacity, {}); EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); diff --git a/test/unittest/transport/can/test_can_svc_tx_sessions.cpp b/test/unittest/transport/can/test_can_svc_tx_sessions.cpp new file mode 100644 index 000000000..620530f08 --- /dev/null +++ b/test/unittest/transport/can/test_can_svc_tx_sessions.cpp @@ -0,0 +1,106 @@ +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#include + +#include "media_mock.hpp" +#include "../multiplexer_mock.hpp" +#include "../../gtest_helpers.hpp" +#include "../../test_scheduler.hpp" +#include "../../test_utilities.hpp" +#include "../../memory_resource_mock.hpp" +#include "../../tracking_memory_resource.hpp" + +#include + +namespace +{ +using byte = cetl::byte; + +using namespace libcyphal; +using namespace libcyphal::transport; +using namespace libcyphal::transport::can; +using namespace libcyphal::test_utilities; + +using testing::_; +using testing::Eq; +using testing::Return; +using testing::IsNull; +using testing::IsEmpty; +using testing::NotNull; +using testing::Optional; +using testing::InSequence; +using testing::StrictMock; +using testing::ElementsAre; +using testing::VariantWith; + +using namespace std::chrono_literals; + +class TestCanSvcTxSessions : public testing::Test +{ +protected: + void SetUp() override + { + EXPECT_CALL(media_mock_, getMtu()).WillRepeatedly(Return(CANARD_MTU_CAN_CLASSIC)); + } + + void TearDown() override + { + EXPECT_THAT(mr_.allocations, IsEmpty()); + // TODO: Uncomment this when PMR deleter is fixed. + // EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); + } + + TimePoint now() const + { + return scheduler_.now(); + } + + CETL_NODISCARD UniquePtr makeTransport(cetl::pmr::memory_resource& mr, + const std::size_t tx_capacity = 16) + { + std::array media_array{&media_mock_}; + + auto maybe_transport = can::makeTransport(mr, mux_mock_, media_array, tx_capacity, {}); + EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); + return cetl::get>(std::move(maybe_transport)); + } + + // MARK: Data members: + + TestScheduler scheduler_{}; + TrackingMemoryResource mr_; + StrictMock mux_mock_{}; + StrictMock media_mock_{}; +}; + +// MARK: Tests: + +TEST_F(TestCanSvcTxSessions, make_request_tx_sessions) +{ + auto transport = makeTransport(mr_); + + auto maybe_session = transport->makeRequestTxSession({123, 1024}); + EXPECT_THAT(maybe_session, VariantWith>(_)); + auto session = cetl::get>(std::move(maybe_session)); + EXPECT_THAT(session, NotNull()); + + EXPECT_THAT(session->getParams().service_id, 123); + EXPECT_THAT(session->getParams().server_node_id, 1024); +} + +TEST_F(TestCanSvcTxSessions, make_response_tx_sessions) +{ + auto transport = makeTransport(mr_); + + auto maybe_session = transport->makeResponseTxSession({123}); + EXPECT_THAT(maybe_session, VariantWith>(_)); + auto session = cetl::get>(std::move(maybe_session)); + EXPECT_THAT(session, NotNull()); + + EXPECT_THAT(session->getParams().service_id, 123); +} + +} // namespace From 99a4bb7af53eef6fbe9e613d0cf5bb882472e6e9 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Fri, 26 Apr 2024 21:05:23 +0300 Subject: [PATCH 47/64] more coverage --- include/libcyphal/transport/can/delegate.hpp | 8 ++- .../transport/can/msg_rx_session.hpp | 6 ++ .../transport/can/msg_tx_session.hpp | 24 +++----- .../transport/can/svc_tx_sessions.hpp | 56 +++++++++---------- include/libcyphal/transport/can/transport.hpp | 40 ++++++++----- .../transport/can/test_can_delegate.cpp | 7 +-- .../transport/can/test_can_msg_rx_session.cpp | 13 ++++- .../transport/can/test_can_msg_tx_session.cpp | 19 +++++-- .../can/test_can_svc_tx_sessions.cpp | 53 +++++++++++++++--- .../transport/can/test_can_transport.cpp | 16 +++--- 10 files changed, 151 insertions(+), 91 deletions(-) diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp index 1a6a42c7c..ec4ff6789 100644 --- a/include/libcyphal/transport/can/delegate.hpp +++ b/include/libcyphal/transport/can/delegate.hpp @@ -6,6 +6,7 @@ #ifndef LIBCYPHAL_TRANSPORT_CAN_DELEGATE_HPP_INCLUDED #define LIBCYPHAL_TRANSPORT_CAN_DELEGATE_HPP_INCLUDED +#include #include #include @@ -161,10 +162,9 @@ class TransportDelegate /// /// Internal method which is in use by TX session implementations to delegate actual sending to transport. /// - CETL_NODISCARD virtual cetl::optional sendTransfer(const CanardMicrosecond deadline, + CETL_NODISCARD virtual cetl::optional sendTransfer(const TimePoint deadline, const CanardTransferMetadata& metadata, - const void* const payload, - const std::size_t payload_size) = 0; + const PayloadFragments payload_fragments) = 0; protected: ~TransportDelegate() = default; @@ -230,6 +230,8 @@ class TransportDelegate }; // TransportDelegate +// MARK: - + /// This internal session delegate class serves the following purpose: it provides interface /// to access a session from transport (by casting canard's `user_reference` member to this class). /// diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp index c406586b1..82eaa1c60 100644 --- a/include/libcyphal/transport/can/msg_rx_session.hpp +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -25,6 +25,7 @@ namespace can /// namespace detail { + class MessageRxSession final : public IMessageRxSession, private RxSessionDelegate { // In use to disable public construction. @@ -40,6 +41,11 @@ class MessageRxSession final : public IMessageRxSession, private RxSessionDelega CETL_NODISCARD static Expected, AnyError> make(TransportDelegate& delegate, const MessageRxParams& params) { + if (params.subject_id > CANARD_SUBJECT_ID_MAX) + { + return ArgumentError{}; + } + auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Tag{}, delegate, params); if (session == nullptr) { diff --git a/include/libcyphal/transport/can/msg_tx_session.hpp b/include/libcyphal/transport/can/msg_tx_session.hpp index fc4cf13f0..4428708a3 100644 --- a/include/libcyphal/transport/can/msg_tx_session.hpp +++ b/include/libcyphal/transport/can/msg_tx_session.hpp @@ -26,6 +26,7 @@ namespace can /// namespace detail { + class MessageTxSession final : public IMessageTxSession { // In use to disable public construction. @@ -41,6 +42,11 @@ class MessageTxSession final : public IMessageTxSession CETL_NODISCARD static Expected, AnyError> make(TransportDelegate& delegate, const MessageTxParams& params) { + if (params.subject_id > CANARD_SUBJECT_ID_MAX) + { + return ArgumentError{}; + } + auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Tag{}, delegate, params); if (session == nullptr) { @@ -74,29 +80,13 @@ class MessageTxSession final : public IMessageTxSession CETL_NODISCARD cetl::optional send(const TransferMetadata& metadata, const PayloadFragments payload_fragments) final { - // libcanard currently does not support fragmented payloads (at `canardTxPush`). - // so we need to concatenate them when there are more than one non-empty fragment. - // See https://github.com/OpenCyphal/libcanard/issues/223 - // - const transport::detail::ContiguousPayload contiguous_payload{delegate_.memory(), payload_fragments}; - if ((contiguous_payload.data() == nullptr) && (contiguous_payload.size() > 0)) - { - return MemoryError{}; - } - - const TimePoint deadline = metadata.timestamp + send_timeout_; - const auto deadline_us = std::chrono::duration_cast(deadline.time_since_epoch()); - const auto canard_metadata = CanardTransferMetadata{static_cast(metadata.priority), CanardTransferKindMessage, static_cast(params_.subject_id), CANARD_NODE_ID_UNSET, static_cast(metadata.transfer_id)}; - return delegate_.sendTransfer(static_cast(deadline_us.count()), - canard_metadata, - contiguous_payload.data(), - contiguous_payload.size()); + return delegate_.sendTransfer(metadata.timestamp + send_timeout_, canard_metadata, payload_fragments); } // MARK: IRunnable diff --git a/include/libcyphal/transport/can/svc_tx_sessions.hpp b/include/libcyphal/transport/can/svc_tx_sessions.hpp index 9d41f9cef..9329ec622 100644 --- a/include/libcyphal/transport/can/svc_tx_sessions.hpp +++ b/include/libcyphal/transport/can/svc_tx_sessions.hpp @@ -44,6 +44,11 @@ class SvcRequestTxSession final : public IRequestTxSession CETL_NODISCARD static Expected, AnyError> make(TransportDelegate& delegate, const RequestTxParams& params) { + if ((params.service_id > CANARD_SERVICE_ID_MAX) || (params.server_node_id > CANARD_NODE_ID_MAX)) + { + return ArgumentError{}; + } + auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Tag{}, delegate, params); if (session == nullptr) { @@ -77,29 +82,23 @@ class SvcRequestTxSession final : public IRequestTxSession CETL_NODISCARD cetl::optional send(const TransferMetadata& metadata, const PayloadFragments payload_fragments) final { - // libcanard currently does not support fragmented payloads (at `canardTxPush`). - // so we need to concatenate them when there are more than one non-empty fragment. - // See https://github.com/OpenCyphal/libcanard/issues/223 + // Before delegating to transport it makes sense to do some sanity checks. + // Otherwise, transport may do some work (like possible payload allocation/copying, + // media enumeration and pushing into their TX queues) doomed to fail with argument error. // - const transport::detail::ContiguousPayload contiguous_payload{delegate_.memory(), payload_fragments}; - if ((contiguous_payload.data() == nullptr) && (contiguous_payload.size() > 0)) + const CanardNodeID local_node_id = delegate_.canard_instance().node_id; + if (local_node_id > CANARD_NODE_ID_MAX) { - return MemoryError{}; + return ArgumentError{}; } - const TimePoint deadline = metadata.timestamp + send_timeout_; - const auto deadline_us = std::chrono::duration_cast(deadline.time_since_epoch()); - const auto canard_metadata = CanardTransferMetadata{static_cast(metadata.priority), CanardTransferKindRequest, static_cast(params_.service_id), - delegate_.canard_instance().node_id, + static_cast(params_.server_node_id), static_cast(metadata.transfer_id)}; - return delegate_.sendTransfer(static_cast(deadline_us.count()), - canard_metadata, - contiguous_payload.data(), - contiguous_payload.size()); + return delegate_.sendTransfer(metadata.timestamp + send_timeout_, canard_metadata, payload_fragments); } // MARK: IRunnable @@ -117,6 +116,8 @@ class SvcRequestTxSession final : public IRequestTxSession }; // SvcRequestTxSession +// MARK: - + /// @brief A class to represent a service response TX session. /// class SvcResponseTxSession final : public IResponseTxSession @@ -134,6 +135,11 @@ class SvcResponseTxSession final : public IResponseTxSession CETL_NODISCARD static Expected, AnyError> make(TransportDelegate& delegate, const ResponseTxParams& params) { + if (params.service_id > CANARD_SERVICE_ID_MAX) + { + return ArgumentError{}; + } + auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Tag{}, delegate, params); if (session == nullptr) { @@ -167,29 +173,23 @@ class SvcResponseTxSession final : public IResponseTxSession CETL_NODISCARD cetl::optional send(const ServiceTransferMetadata& metadata, const PayloadFragments payload_fragments) final { - // libcanard currently does not support fragmented payloads (at `canardTxPush`). - // so we need to concatenate them when there are more than one non-empty fragment. - // See https://github.com/OpenCyphal/libcanard/issues/223 + // Before delegating to transport it makes sense to do some sanity checks. + // Otherwise, transport may do some work (like possible payload allocation/copying, + // media enumeration and pushing into their TX queues) doomed to fail with argument error. // - const transport::detail::ContiguousPayload contiguous_payload{delegate_.memory(), payload_fragments}; - if ((contiguous_payload.data() == nullptr) && (contiguous_payload.size() > 0)) + const CanardNodeID local_node_id = delegate_.canard_instance().node_id; + if ((local_node_id > CANARD_NODE_ID_MAX) || (metadata.remote_node_id > CANARD_NODE_ID_MAX)) { - return MemoryError{}; + return ArgumentError{}; } - const TimePoint deadline = metadata.timestamp + send_timeout_; - const auto deadline_us = std::chrono::duration_cast(deadline.time_since_epoch()); - const auto canard_metadata = CanardTransferMetadata{static_cast(metadata.priority), CanardTransferKindResponse, static_cast(params_.service_id), - delegate_.canard_instance().node_id, + static_cast(metadata.remote_node_id), static_cast(metadata.transfer_id)}; - return delegate_.sendTransfer(static_cast(deadline_us.count()), - canard_metadata, - contiguous_payload.data(), - contiguous_payload.size()); + return delegate_.sendTransfer(metadata.timestamp + send_timeout_, canard_metadata, payload_fragments); } // MARK: IRunnable diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index 3eb32abae..e85c8417f 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -34,6 +34,7 @@ class ICanTransport : public ITransport /// namespace detail { + class TransportImpl final : public ICanTransport, private TransportDelegate { // In use to disable public construction. @@ -159,7 +160,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate CETL_NODISCARD Expected, AnyError> makeMessageRxSession( const MessageRxParams& params) final { - auto any_error = ensureNewSessionFor(CanardTransferKindMessage, params.subject_id, CANARD_SUBJECT_ID_MAX); + auto any_error = ensureNewSessionFor(CanardTransferKindMessage, params.subject_id); if (any_error.has_value()) { return any_error.value(); @@ -177,7 +178,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate CETL_NODISCARD Expected, AnyError> makeRequestRxSession( const RequestRxParams& params) final { - auto any_error = ensureNewSessionFor(CanardTransferKindRequest, params.service_id, CANARD_SERVICE_ID_MAX); + auto any_error = ensureNewSessionFor(CanardTransferKindRequest, params.service_id); if (any_error.has_value()) { return any_error.value(); @@ -195,7 +196,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate CETL_NODISCARD Expected, AnyError> makeResponseRxSession( const ResponseRxParams& params) final { - auto any_error = ensureNewSessionFor(CanardTransferKindResponse, params.service_id, CANARD_SERVICE_ID_MAX); + auto any_error = ensureNewSessionFor(CanardTransferKindResponse, params.service_id); if (any_error.has_value()) { return any_error.value(); @@ -225,11 +226,22 @@ class TransportImpl final : public ICanTransport, private TransportDelegate return *this; } - CETL_NODISCARD cetl::optional sendTransfer(const CanardMicrosecond deadline, + CETL_NODISCARD cetl::optional sendTransfer(const TimePoint deadline, const CanardTransferMetadata& metadata, - const void* const payload, - const std::size_t payload_size) final + const PayloadFragments payload_fragments) final { + // libcanard currently does not support fragmented payloads (at `canardTxPush`). + // so we need to concatenate them when there are more than one non-empty fragment. + // See https://github.com/OpenCyphal/libcanard/issues/223 + // + const transport::detail::ContiguousPayload payload{memory(), payload_fragments}; + if ((payload.data() == nullptr) && (payload.size() > 0)) + { + return MemoryError{}; + } + + const auto deadline_us = std::chrono::duration_cast(deadline.time_since_epoch()); + // TODO: Rework error handling strategy. // Currently, we return the last error encountered, but we should consider all errors somehow. // @@ -239,8 +251,12 @@ class TransportImpl final : public ICanTransport, private TransportDelegate { media.canard_tx_queue.mtu_bytes = media.interface.getMtu(); - const auto result = - ::canardTxPush(&media.canard_tx_queue, &canard_instance(), deadline, &metadata, payload_size, payload); + const auto result = ::canardTxPush(&media.canard_tx_queue, + &canard_instance(), + static_cast(deadline_us.count()), + &metadata, + payload.size(), + payload.data()); if (result < 0) { maybe_error = TransportDelegate::anyErrorFromCanard(result); @@ -285,14 +301,8 @@ class TransportImpl final : public ICanTransport, private TransportDelegate } CETL_NODISCARD cetl::optional ensureNewSessionFor(const CanardTransferKind transfer_kind, - const PortId port_id, - const PortId max_port_id) noexcept + const PortId port_id) noexcept { - if (port_id > max_port_id) - { - return ArgumentError{}; - } - const auto hasSubscription = ::canardRxHasSubscription(&canard_instance(), transfer_kind, port_id); CETL_DEBUG_ASSERT(hasSubscription >= 0, "There is no way currently to get an error here."); if (hasSubscription > 0) diff --git a/test/unittest/transport/can/test_can_delegate.cpp b/test/unittest/transport/can/test_can_delegate.cpp index 65334573f..134f8a268 100644 --- a/test/unittest/transport/can/test_can_delegate.cpp +++ b/test/unittest/transport/can/test_can_delegate.cpp @@ -41,10 +41,9 @@ class TestCanDelegate : public testing::Test MOCK_METHOD((cetl::optional), sendTransfer, - (const CanardMicrosecond deadline, - const CanardTransferMetadata& metadata, - const void* const payload, - const std::size_t payload_size)); + (const libcyphal::TimePoint deadline, + const CanardTransferMetadata& metadata, + const PayloadFragments payload_fragments)); }; void TearDown() override diff --git a/test/unittest/transport/can/test_can_msg_rx_session.cpp b/test/unittest/transport/can/test_can_msg_rx_session.cpp index ff159a442..226d70eb6 100644 --- a/test/unittest/transport/can/test_can_msg_rx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_rx_session.cpp @@ -81,7 +81,7 @@ TEST_F(TestCanMsgRxSession, make_setTransferIdTimeout) auto transport = makeTransport(mr_); auto maybe_session = transport->makeMessageRxSession({42, 123}); - EXPECT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(_)); auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); @@ -106,12 +106,21 @@ TEST_F(TestCanMsgRxSession, make_no_memory) EXPECT_THAT(maybe_session, VariantWith(VariantWith(_))); } +TEST_F(TestCanMsgRxSession, make_fails_due_to_argument_error) +{ + auto transport = makeTransport(mr_); + + // Try invalid subject id + auto maybe_session = transport->makeMessageRxSession({64, CANARD_SUBJECT_ID_MAX + 1}); + EXPECT_THAT(maybe_session, VariantWith(VariantWith(_))); +} + TEST_F(TestCanMsgRxSession, run_and_receive) { auto transport = makeTransport(mr_); auto maybe_session = transport->makeMessageRxSession({4, 0x23}); - EXPECT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(_)); auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); diff --git a/test/unittest/transport/can/test_can_msg_tx_session.cpp b/test/unittest/transport/can/test_can_msg_tx_session.cpp index b0a08d01f..8d94cd3a5 100644 --- a/test/unittest/transport/can/test_can_msg_tx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_tx_session.cpp @@ -104,12 +104,21 @@ TEST_F(TestCanMsgTxSession, make_no_memory) EXPECT_THAT(maybe_session, VariantWith(VariantWith(_))); } +TEST_F(TestCanMsgTxSession, make_fails_due_to_argument_error) +{ + auto transport = makeTransport(mr_); + + // Try invalid subject id + auto maybe_session = transport->makeMessageTxSession({CANARD_SUBJECT_ID_MAX + 1}); + EXPECT_THAT(maybe_session, VariantWith(VariantWith(_))); +} + TEST_F(TestCanMsgTxSession, send_empty_payload_and_no_transport_run) { auto transport = makeTransport(mr_); auto maybe_session = transport->makeMessageTxSession({123}); - EXPECT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(_)); auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); @@ -133,7 +142,7 @@ TEST_F(TestCanMsgTxSession, send_empty_payload) auto transport = makeTransport(mr_); auto maybe_session = transport->makeMessageTxSession({123}); - EXPECT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(_)); auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); @@ -169,7 +178,7 @@ TEST_F(TestCanMsgTxSession, send_empty_expired_payload) auto transport = makeTransport(mr_); auto maybe_session = transport->makeMessageTxSession({123}); - EXPECT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(_)); auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); @@ -197,7 +206,7 @@ TEST_F(TestCanMsgTxSession, send_7bytes_payload_with_500ms_timeout) auto transport = makeTransport(mr_); auto maybe_session = transport->makeMessageTxSession({17}); - EXPECT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(_)); auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); @@ -245,7 +254,7 @@ TEST_F(TestCanMsgTxSession, send_when_no_memory_for_contiguous_payload) EXPECT_CALL(mr_mock, do_allocate(sizeof(payload1) + sizeof(payload2), _)).WillOnce(Return(nullptr)); auto maybe_session = transport->makeMessageTxSession({17}); - EXPECT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(_)); auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); diff --git a/test/unittest/transport/can/test_can_svc_tx_sessions.cpp b/test/unittest/transport/can/test_can_svc_tx_sessions.cpp index 620530f08..930ca8715 100644 --- a/test/unittest/transport/can/test_can_svc_tx_sessions.cpp +++ b/test/unittest/transport/can/test_can_svc_tx_sessions.cpp @@ -59,11 +59,16 @@ class TestCanSvcTxSessions : public testing::Test } CETL_NODISCARD UniquePtr makeTransport(cetl::pmr::memory_resource& mr, + const NodeId local_node_id, const std::size_t tx_capacity = 16) { std::array media_array{&media_mock_}; - auto maybe_transport = can::makeTransport(mr, mux_mock_, media_array, tx_capacity, {}); + // TODO: `local_node_id` could be just passed to `can::makeTransport` as an argument, + // but it's not possible due to CETL issue https://github.com/OpenCyphal/CETL/issues/119. + const auto opt_local_node_id = cetl::optional{local_node_id}; + + auto maybe_transport = can::makeTransport(mr, mux_mock_, media_array, tx_capacity, opt_local_node_id); EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); return cetl::get>(std::move(maybe_transport)); } @@ -78,29 +83,59 @@ class TestCanSvcTxSessions : public testing::Test // MARK: Tests: -TEST_F(TestCanSvcTxSessions, make_request_tx_sessions) +TEST_F(TestCanSvcTxSessions, make_request_session) { - auto transport = makeTransport(mr_); + auto transport = makeTransport(mr_, 0); - auto maybe_session = transport->makeRequestTxSession({123, 1024}); - EXPECT_THAT(maybe_session, VariantWith>(_)); + auto maybe_session = transport->makeRequestTxSession({123, CANARD_NODE_ID_MAX}); + ASSERT_THAT(maybe_session, VariantWith>(_)); auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); EXPECT_THAT(session->getParams().service_id, 123); - EXPECT_THAT(session->getParams().server_node_id, 1024); + EXPECT_THAT(session->getParams().server_node_id, CANARD_NODE_ID_MAX); + + session->run(now()); +} + +TEST_F(TestCanSvcTxSessions, make_request_fails_due_to_argument_error) +{ + auto transport = makeTransport(mr_, 0); + + // Try invalid service id + { + auto maybe_session = transport->makeRequestTxSession({CANARD_SERVICE_ID_MAX + 1, 0}); + EXPECT_THAT(maybe_session, VariantWith(VariantWith(_))); + } + + // Try invalid server node id + { + auto maybe_session = transport->makeRequestTxSession({0, CANARD_NODE_ID_MAX + 1}); + EXPECT_THAT(maybe_session, VariantWith(VariantWith(_))); + } } -TEST_F(TestCanSvcTxSessions, make_response_tx_sessions) +TEST_F(TestCanSvcTxSessions, make_response_session) { - auto transport = makeTransport(mr_); + auto transport = makeTransport(mr_, CANARD_NODE_ID_MAX, 2); auto maybe_session = transport->makeResponseTxSession({123}); - EXPECT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(_)); auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); EXPECT_THAT(session->getParams().service_id, 123); + + session->run(now()); +} + +TEST_F(TestCanSvcTxSessions, make_response_fails_due_to_argument_error) +{ + auto transport = makeTransport(mr_, 0); + + // Try invalid service id + auto maybe_session = transport->makeResponseTxSession({CANARD_SERVICE_ID_MAX + 1}); + EXPECT_THAT(maybe_session, VariantWith(VariantWith(_))); } } // namespace diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index 8e79e2620..ab803150e 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -106,7 +106,7 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) { std::array media_array{&media_mock_}; auto maybe_transport = can::makeTransport(mr_, mux_mock_, media_array, 0, {}); - EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); + ASSERT_THAT(maybe_transport, VariantWith>(NotNull())); auto transport = cetl::get>(std::move(maybe_transport)); EXPECT_THAT(transport->getLocalNodeId(), Eq(cetl::nullopt)); @@ -118,7 +118,7 @@ TEST_F(TestCanTransport, makeTransport_getLocalNodeId) std::array media_array{&media_mock_}; auto maybe_transport = can::makeTransport(mr_, mux_mock_, media_array, 0, node_id); - EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); + ASSERT_THAT(maybe_transport, VariantWith>(NotNull())); auto transport = cetl::get>(std::move(maybe_transport)); EXPECT_THAT(transport->getLocalNodeId(), Optional(42)); @@ -236,7 +236,7 @@ TEST_F(TestCanTransport, makeMessageRxSession) auto transport = makeTransport(mr_); auto maybe_rx_session = transport->makeMessageRxSession({42, 123}); - EXPECT_THAT(maybe_rx_session, VariantWith>(NotNull())); + ASSERT_THAT(maybe_rx_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_rx_session)); EXPECT_THAT(session->getParams().extent_bytes, 42); @@ -258,7 +258,7 @@ TEST_F(TestCanTransport, makeMessageRxSession_invalid_resubscription) const PortId test_subject_id = 111; auto maybe_rx_session1 = transport->makeMessageRxSession({0, test_subject_id}); - EXPECT_THAT(maybe_rx_session1, VariantWith>(NotNull())); + ASSERT_THAT(maybe_rx_session1, VariantWith>(NotNull())); auto maybe_rx_session2 = transport->makeMessageRxSession({0, test_subject_id}); EXPECT_THAT(maybe_rx_session2, VariantWith(VariantWith(_))); @@ -269,7 +269,7 @@ TEST_F(TestCanTransport, makeMessageTxSession) auto transport = makeTransport(mr_); auto maybe_tx_session = transport->makeMessageTxSession({123}); - EXPECT_THAT(maybe_tx_session, VariantWith>(NotNull())); + ASSERT_THAT(maybe_tx_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_tx_session)); EXPECT_THAT(session->getParams().subject_id, 123); @@ -282,7 +282,7 @@ TEST_F(TestCanTransport, sending_multiframe_payload_should_fail_for_anonymous) auto transport = makeTransport(mr_); auto maybe_session = transport->makeMessageTxSession({7}); - EXPECT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(_)); auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); @@ -307,7 +307,7 @@ TEST_F(TestCanTransport, sending_multiframe_payload_for_non_anonymous) EXPECT_THAT(transport->setLocalNodeId(0x45), Eq(cetl::nullopt)); auto maybe_session = transport->makeMessageTxSession({7}); - EXPECT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(_)); auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); @@ -361,7 +361,7 @@ TEST_F(TestCanTransport, send_multiframe_payload_to_redundant_not_ready_media) EXPECT_THAT(transport->setLocalNodeId(0x45), Eq(cetl::nullopt)); auto maybe_session = transport->makeMessageTxSession({7}); - EXPECT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(_)); auto session = cetl::get>(std::move(maybe_session)); EXPECT_THAT(session, NotNull()); From 08f036c6cf1ce1897bbf4fe3eb976925715ab3a8 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Fri, 26 Apr 2024 22:31:31 +0300 Subject: [PATCH 48/64] coverage for svc tx sessions #verification --- include/libcyphal/transport/types.hpp | 8 +- test/unittest/gtest_helpers.hpp | 72 ++++++ .../transport/can/test_can_msg_rx_session.cpp | 6 +- .../transport/can/test_can_msg_tx_session.cpp | 18 +- .../can/test_can_svc_tx_sessions.cpp | 210 +++++++++++++++++- .../transport/can/test_can_transport.cpp | 9 +- 6 files changed, 296 insertions(+), 27 deletions(-) diff --git a/include/libcyphal/transport/types.hpp b/include/libcyphal/transport/types.hpp index f171770e6..df7dce099 100644 --- a/include/libcyphal/transport/types.hpp +++ b/include/libcyphal/transport/types.hpp @@ -58,7 +58,7 @@ struct TransferMetadata // AUTOSAR A11-0-2 exception: we just enhance the base metadata with additional information. struct MessageTransferMetadata final : TransferMetadata { - MessageTransferMetadata(const TransferMetadata& transfer_metadata, cetl::optional _publisher_node_id) + MessageTransferMetadata(const TransferMetadata& transfer_metadata, const cetl::optional& _publisher_node_id) : TransferMetadata{transfer_metadata} , publisher_node_id{_publisher_node_id} { @@ -70,6 +70,12 @@ struct MessageTransferMetadata final : TransferMetadata // AUTOSAR A11-0-2 exception: we just enhance the base metadata with additional information. struct ServiceTransferMetadata final : TransferMetadata { + ServiceTransferMetadata(const TransferMetadata& transfer_metadata, const NodeId _remote_node_id) + : TransferMetadata{transfer_metadata} + , remote_node_id{_remote_node_id} + { + } + NodeId remote_node_id; }; diff --git a/test/unittest/gtest_helpers.hpp b/test/unittest/gtest_helpers.hpp index 3b8cb7cc4..8af382dad 100644 --- a/test/unittest/gtest_helpers.hpp +++ b/test/unittest/gtest_helpers.hpp @@ -216,6 +216,42 @@ inline testing::Matcher SubjectOfCanIdEq(PortId subject_id) return SubjectOfCanIdMatcher(subject_id); } +class ServiceOfCanIdMatcher +{ +public: + using is_gtest_matcher = void; + + explicit ServiceOfCanIdMatcher(PortId service_id) + : service_id_{service_id} + { + } + + bool MatchAndExplain(const CanId& can_id, std::ostream* os) const + { + const auto service_id = (can_id >> 14) & CANARD_SERVICE_ID_MAX; + if (os) + { + *os << "service_id=" << service_id; + } + return service_id == service_id_; + } + void DescribeTo(std::ostream* os) const + { + *os << "service_id==" << service_id_; + } + void DescribeNegationTo(std::ostream* os) const + { + *os << "service_id!=" << service_id_; + } + +private: + const PortId service_id_; +}; +inline testing::Matcher ServiceOfCanIdEq(PortId service_id) +{ + return ServiceOfCanIdMatcher(service_id); +} + class SourceNodeCanIdMatcher { public: @@ -252,6 +288,42 @@ inline testing::Matcher SourceNodeOfCanIdEq(NodeId node_id) return SourceNodeCanIdMatcher(node_id); } +class DestinationNodeCanIdMatcher +{ +public: + using is_gtest_matcher = void; + + explicit DestinationNodeCanIdMatcher(NodeId node_id) + : node_id_{node_id} + { + } + + bool MatchAndExplain(const CanId& can_id, std::ostream* os) const + { + const auto node_id = (can_id >> 7) & CANARD_NODE_ID_MAX; + if (os) + { + *os << "node_id=" << node_id; + } + return node_id == node_id_; + } + void DescribeTo(std::ostream* os) const + { + *os << "node_id==" << node_id_; + } + void DescribeNegationTo(std::ostream* os) const + { + *os << "node_id!=" << node_id_; + } + +private: + const NodeId node_id_; +}; +inline testing::Matcher DestinationNodeOfCanIdEq(NodeId node_id) +{ + return DestinationNodeCanIdMatcher(node_id); +} + class TailByteMatcher { public: diff --git a/test/unittest/transport/can/test_can_msg_rx_session.cpp b/test/unittest/transport/can/test_can_msg_rx_session.cpp index 226d70eb6..7c2e63409 100644 --- a/test/unittest/transport/can/test_can_msg_rx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_rx_session.cpp @@ -81,9 +81,8 @@ TEST_F(TestCanMsgRxSession, make_setTransferIdTimeout) auto transport = makeTransport(mr_); auto maybe_session = transport->makeMessageRxSession({42, 123}); - ASSERT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_session)); - EXPECT_THAT(session, NotNull()); EXPECT_THAT(session->getParams().extent_bytes, 42); EXPECT_THAT(session->getParams().subject_id, 123); @@ -120,9 +119,8 @@ TEST_F(TestCanMsgRxSession, run_and_receive) auto transport = makeTransport(mr_); auto maybe_session = transport->makeMessageRxSession({4, 0x23}); - ASSERT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_session)); - EXPECT_THAT(session, NotNull()); // 1-st iteration: one frame available @ 1s { diff --git a/test/unittest/transport/can/test_can_msg_tx_session.cpp b/test/unittest/transport/can/test_can_msg_tx_session.cpp index 8d94cd3a5..cc75f299c 100644 --- a/test/unittest/transport/can/test_can_msg_tx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_tx_session.cpp @@ -83,9 +83,8 @@ TEST_F(TestCanMsgTxSession, make) auto transport = makeTransport(mr_); auto maybe_session = transport->makeMessageTxSession({123}); - EXPECT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_session)); - EXPECT_THAT(session, NotNull()); EXPECT_THAT(session->getParams().subject_id, 123); } @@ -118,9 +117,8 @@ TEST_F(TestCanMsgTxSession, send_empty_payload_and_no_transport_run) auto transport = makeTransport(mr_); auto maybe_session = transport->makeMessageTxSession({123}); - ASSERT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_session)); - EXPECT_THAT(session, NotNull()); const PayloadFragments empty_payload{}; const TransferMetadata metadata{0x1AF52, {}, Priority::Low}; @@ -142,9 +140,8 @@ TEST_F(TestCanMsgTxSession, send_empty_payload) auto transport = makeTransport(mr_); auto maybe_session = transport->makeMessageTxSession({123}); - ASSERT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_session)); - EXPECT_THAT(session, NotNull()); scheduler_.runNow(+10s); const auto send_time = now(); @@ -178,9 +175,8 @@ TEST_F(TestCanMsgTxSession, send_empty_expired_payload) auto transport = makeTransport(mr_); auto maybe_session = transport->makeMessageTxSession({123}); - ASSERT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_session)); - EXPECT_THAT(session, NotNull()); scheduler_.runNow(+10s); const auto send_time = now(); @@ -206,9 +202,8 @@ TEST_F(TestCanMsgTxSession, send_7bytes_payload_with_500ms_timeout) auto transport = makeTransport(mr_); auto maybe_session = transport->makeMessageTxSession({17}); - ASSERT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_session)); - EXPECT_THAT(session, NotNull()); const auto timeout = 500ms; session->setSendTimeout(timeout); @@ -254,9 +249,8 @@ TEST_F(TestCanMsgTxSession, send_when_no_memory_for_contiguous_payload) EXPECT_CALL(mr_mock, do_allocate(sizeof(payload1) + sizeof(payload2), _)).WillOnce(Return(nullptr)); auto maybe_session = transport->makeMessageTxSession({17}); - ASSERT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_session)); - EXPECT_THAT(session, NotNull()); scheduler_.runNow(+10s); const auto send_time = now(); diff --git a/test/unittest/transport/can/test_can_svc_tx_sessions.cpp b/test/unittest/transport/can/test_can_svc_tx_sessions.cpp index 930ca8715..06b052954 100644 --- a/test/unittest/transport/can/test_can_svc_tx_sessions.cpp +++ b/test/unittest/transport/can/test_can_svc_tx_sessions.cpp @@ -88,9 +88,8 @@ TEST_F(TestCanSvcTxSessions, make_request_session) auto transport = makeTransport(mr_, 0); auto maybe_session = transport->makeRequestTxSession({123, CANARD_NODE_ID_MAX}); - ASSERT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_session)); - EXPECT_THAT(session, NotNull()); EXPECT_THAT(session->getParams().service_id, 123); EXPECT_THAT(session->getParams().server_node_id, CANARD_NODE_ID_MAX); @@ -115,14 +114,123 @@ TEST_F(TestCanSvcTxSessions, make_request_fails_due_to_argument_error) } } +TEST_F(TestCanSvcTxSessions, make_request_fails_due_to_no_memory) +{ + StrictMock mr_mock{}; + mr_mock.redirectExpectedCallsTo(mr_); + + // Emulate that there is no memory available for the message session. + EXPECT_CALL(mr_mock, do_allocate(sizeof(can::detail::SvcRequestTxSession), _)).WillOnce(Return(nullptr)); + + auto transport = makeTransport(mr_mock, CANARD_NODE_ID_MAX); + + auto maybe_session = transport->makeRequestTxSession({0x23, 0}); + EXPECT_THAT(maybe_session, VariantWith(VariantWith(_))); +} + +TEST_F(TestCanSvcTxSessions, send_request) +{ + EXPECT_CALL(media_mock_, pop(_)).WillRepeatedly(Return(cetl::nullopt)); + + auto transport = makeTransport(mr_, 13); + + auto maybe_session = transport->makeRequestTxSession({123, 31}); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); + auto session = cetl::get>(std::move(maybe_session)); + + scheduler_.runNow(+10s); + const auto send_time = now(); + const auto timeout = 100ms; + session->setSendTimeout(timeout); + + const PayloadFragments empty_payload{}; + const TransferMetadata metadata{0x66, send_time, Priority::Slow}; + + auto maybe_error = session->send(metadata, empty_payload); + EXPECT_THAT(maybe_error, Eq(cetl::nullopt)); + + EXPECT_CALL(media_mock_, push(_, _, _)).WillOnce([&](auto deadline, auto can_id, auto payload) { + EXPECT_THAT(now(), send_time + 10ms); + EXPECT_THAT(deadline, send_time + timeout); + EXPECT_THAT(can_id, ServiceOfCanIdEq(123)); + EXPECT_THAT(can_id, AllOf(SourceNodeOfCanIdEq(13), DestinationNodeOfCanIdEq(31))); + EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsServiceCanId())); + + auto tbm = TailByteEq(metadata.transfer_id); + EXPECT_THAT(payload, ElementsAre(tbm)); + return true; + }); + + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); +} + +TEST_F(TestCanSvcTxSessions, send_request_with_argument_error) +{ + EXPECT_CALL(media_mock_, pop(_)).WillRepeatedly(Return(cetl::nullopt)); + + // Make initially anonymous node transport. + // + std::array media_array{&media_mock_}; + auto maybe_transport = can::makeTransport(mr_, mux_mock_, media_array, 2, {}); + ASSERT_THAT(maybe_transport, VariantWith>(NotNull())); + auto transport = cetl::get>(std::move(maybe_transport)); + + auto maybe_session = transport->makeRequestTxSession({123, 31}); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); + auto session = cetl::get>(std::move(maybe_session)); + + scheduler_.runNow(+100ms); + const auto timeout = 1s; + const auto transfer_time = now(); + + const PayloadFragments empty_payload{}; + const TransferMetadata metadata{0x66, transfer_time, Priority::Immediate}; + + // Should fail due to anonymous node. + { + scheduler_.setNow(TimePoint{200ms}); + + const auto maybe_error = session->send(metadata, empty_payload); + EXPECT_THAT(maybe_error, Optional(VariantWith(_))); + + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + } + + // Fix anonymous node + { + scheduler_.setNow(TimePoint{300ms}); + const auto send_time = now(); + + EXPECT_THAT(transport->setLocalNodeId(13), Eq(cetl::nullopt)); + const auto maybe_error = session->send(metadata, empty_payload); + EXPECT_THAT(maybe_error, Eq(cetl::nullopt)); + + EXPECT_CALL(media_mock_, push(_, _, _)).WillOnce([&](auto deadline, auto can_id, auto payload) { + EXPECT_THAT(now(), send_time + 10ms); + EXPECT_THAT(deadline, transfer_time + timeout); + EXPECT_THAT(can_id, ServiceOfCanIdEq(123)); + EXPECT_THAT(can_id, AllOf(SourceNodeOfCanIdEq(13), DestinationNodeOfCanIdEq(31))); + EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsServiceCanId())); + + auto tbm = TailByteEq(metadata.transfer_id); + EXPECT_THAT(payload, ElementsAre(tbm)); + return true; + }); + + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + } +} + TEST_F(TestCanSvcTxSessions, make_response_session) { auto transport = makeTransport(mr_, CANARD_NODE_ID_MAX, 2); auto maybe_session = transport->makeResponseTxSession({123}); - ASSERT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_session)); - EXPECT_THAT(session, NotNull()); EXPECT_THAT(session->getParams().service_id, 123); @@ -138,4 +246,98 @@ TEST_F(TestCanSvcTxSessions, make_response_fails_due_to_argument_error) EXPECT_THAT(maybe_session, VariantWith(VariantWith(_))); } +TEST_F(TestCanSvcTxSessions, make_response_fails_due_to_no_memory) +{ + StrictMock mr_mock{}; + mr_mock.redirectExpectedCallsTo(mr_); + + // Emulate that there is no memory available for the message session. + EXPECT_CALL(mr_mock, do_allocate(sizeof(can::detail::SvcRequestTxSession), _)).WillOnce(Return(nullptr)); + + auto transport = makeTransport(mr_mock, CANARD_NODE_ID_MAX); + + auto maybe_session = transport->makeResponseTxSession({0x23}); + EXPECT_THAT(maybe_session, VariantWith(VariantWith(_))); +} + +TEST_F(TestCanSvcTxSessions, send_respose) +{ + EXPECT_CALL(media_mock_, pop(_)).WillRepeatedly(Return(cetl::nullopt)); + + auto transport = makeTransport(mr_, 31); + + auto maybe_session = transport->makeResponseTxSession({123}); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); + auto session = cetl::get>(std::move(maybe_session)); + + scheduler_.runNow(+10s); + const auto send_time = now(); + const auto timeout = 100ms; + session->setSendTimeout(timeout); + + const PayloadFragments empty_payload{}; + const ServiceTransferMetadata metadata{{0x66, send_time, Priority::Fast}, 13}; + + auto maybe_error = session->send(metadata, empty_payload); + EXPECT_THAT(maybe_error, Eq(cetl::nullopt)); + + EXPECT_CALL(media_mock_, push(_, _, _)).WillOnce([&](auto deadline, auto can_id, auto payload) { + EXPECT_THAT(now(), send_time + 10ms); + EXPECT_THAT(deadline, send_time + timeout); + EXPECT_THAT(can_id, ServiceOfCanIdEq(123)); + EXPECT_THAT(can_id, AllOf(SourceNodeOfCanIdEq(31), DestinationNodeOfCanIdEq(13))); + EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsServiceCanId())); + + auto tbm = TailByteEq(metadata.transfer_id); + EXPECT_THAT(payload, ElementsAre(tbm)); + return true; + }); + + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); +} + +TEST_F(TestCanSvcTxSessions, send_respose_with_argument_error) +{ + EXPECT_CALL(media_mock_, pop(_)).WillRepeatedly(Return(cetl::nullopt)); + + // Make initially anonymous node transport. + // + std::array media_array{&media_mock_}; + auto maybe_transport = can::makeTransport(mr_, mux_mock_, media_array, 2, {}); + ASSERT_THAT(maybe_transport, VariantWith>(NotNull())); + auto transport = cetl::get>(std::move(maybe_transport)); + + auto maybe_session = transport->makeResponseTxSession({123}); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); + auto session = cetl::get>(std::move(maybe_session)); + + const PayloadFragments empty_payload{}; + ServiceTransferMetadata metadata{{0x66, now(), Priority::Immediate}, 13}; + + // Should fail due to anonymous node. + { + scheduler_.setNow(TimePoint{100ms}); + + const auto maybe_error = session->send(metadata, empty_payload); + EXPECT_THAT(maybe_error, Optional(VariantWith(_))); + + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + } + + // Fix anonymous node, but break remote node id. + { + scheduler_.setNow(TimePoint{200ms}); + + EXPECT_THAT(transport->setLocalNodeId(31), Eq(cetl::nullopt)); + metadata.remote_node_id = CANARD_NODE_ID_MAX + 1; + const auto maybe_error = session->send(metadata, empty_payload); + EXPECT_THAT(maybe_error, Optional(VariantWith(_))); + + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + } +} + } // namespace diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index ab803150e..bdae36d2e 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -282,9 +282,8 @@ TEST_F(TestCanTransport, sending_multiframe_payload_should_fail_for_anonymous) auto transport = makeTransport(mr_); auto maybe_session = transport->makeMessageTxSession({7}); - ASSERT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_session)); - EXPECT_THAT(session, NotNull()); scheduler_.runNow(+10s); const auto send_time = now(); @@ -307,9 +306,8 @@ TEST_F(TestCanTransport, sending_multiframe_payload_for_non_anonymous) EXPECT_THAT(transport->setLocalNodeId(0x45), Eq(cetl::nullopt)); auto maybe_session = transport->makeMessageTxSession({7}); - ASSERT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_session)); - EXPECT_THAT(session, NotNull()); scheduler_.runNow(+10s); const auto timeout = 1s; @@ -361,9 +359,8 @@ TEST_F(TestCanTransport, send_multiframe_payload_to_redundant_not_ready_media) EXPECT_THAT(transport->setLocalNodeId(0x45), Eq(cetl::nullopt)); auto maybe_session = transport->makeMessageTxSession({7}); - ASSERT_THAT(maybe_session, VariantWith>(_)); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_session)); - EXPECT_THAT(session, NotNull()); scheduler_.runNow(+10s); const auto timeout = 1s; From 3aa2aa12766b1cc27a848ca39177c4414aaae3c8 Mon Sep 17 00:00:00 2001 From: Sergei Date: Sun, 28 Apr 2024 11:06:30 +0300 Subject: [PATCH 49/64] added implementation of svc rx sessions #verification --- .../transport/can/msg_rx_session.hpp | 2 + .../transport/can/svc_rx_sessions.hpp | 165 ++++++++++++ .../transport/can/svc_tx_sessions.hpp | 4 +- include/libcyphal/transport/can/transport.hpp | 5 +- test/unittest/gtest_helpers.hpp | 4 +- .../transport/can/test_can_msg_rx_session.cpp | 57 +++-- .../can/test_can_svc_rx_sessions.cpp | 238 ++++++++++++++++++ .../transport/can/test_can_transport.cpp | 100 +++++++- 8 files changed, 541 insertions(+), 34 deletions(-) create mode 100644 include/libcyphal/transport/can/svc_rx_sessions.hpp create mode 100644 test/unittest/transport/can/test_can_svc_rx_sessions.cpp diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp index 82eaa1c60..f6e379134 100644 --- a/include/libcyphal/transport/can/msg_rx_session.hpp +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -26,6 +26,8 @@ namespace can namespace detail { +/// @brief A class to represent a message subscriber RX session. +/// class MessageRxSession final : public IMessageRxSession, private RxSessionDelegate { // In use to disable public construction. diff --git a/include/libcyphal/transport/can/svc_rx_sessions.hpp b/include/libcyphal/transport/can/svc_rx_sessions.hpp new file mode 100644 index 000000000..4805b5f4b --- /dev/null +++ b/include/libcyphal/transport/can/svc_rx_sessions.hpp @@ -0,0 +1,165 @@ +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#ifndef LIBCYPHAL_TRANSPORT_CAN_SVC_RX_SESSIONS_HPP_INCLUDED +#define LIBCYPHAL_TRANSPORT_CAN_SVC_RX_SESSIONS_HPP_INCLUDED + +#include "delegate.hpp" +#include "libcyphal/transport/svc_sessions.hpp" + +#include + +#include + +namespace libcyphal +{ +namespace transport +{ +namespace can +{ + +/// Internal implementation details of the CAN transport. +/// Not supposed to be used directly by the users of the library. +/// +namespace detail +{ + +/// @brief A template class to represent a service request/response RX session (both for server and sides). +/// +/// @tparam Interface_ Type of the session interface. +/// Could be either `IRequestRxSession` or `IResponseRxSession`. +/// @tparam Params Type of the session parameters. +/// Could be either `RequestRxParams` or `ResponseRxParams`. +/// @tparam TransferKind Kind of the service transfer. +/// Could be either `CanardTransferKindRequest` or `CanardTransferKindResponse`. +/// +template +class SvcRxSession final : public Interface_, private RxSessionDelegate +{ + // In use to disable public construction. + // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ + struct Tag + { + explicit Tag() = default; + using Interface = Interface_; + using Concrete = SvcRxSession; + }; + +public: + CETL_NODISCARD static Expected, AnyError> make(TransportDelegate& delegate, + const Params& params) + { + if (params.service_id > CANARD_SERVICE_ID_MAX) + { + return ArgumentError{}; + } + + auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Tag{}, delegate, params); + if (session == nullptr) + { + return MemoryError{}; + } + + return session; + } + + SvcRxSession(Tag, TransportDelegate& delegate, const Params& params) + : delegate_{delegate} + , params_{params} + { + const auto result = ::canardRxSubscribe(&delegate.canard_instance(), + TransferKind, + static_cast(params_.service_id), + static_cast(params_.extent_bytes), + CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC, + &subscription_); + (void) result; + CETL_DEBUG_ASSERT(result >= 0, "There is no way currently to get an error here."); + CETL_DEBUG_ASSERT(result > 0, "New subscription supposed to be made."); + + subscription_.user_reference = static_cast(this); + } + + ~SvcRxSession() final + { + ::canardRxUnsubscribe(&delegate_.canard_instance(), + TransferKind, + static_cast(params_.service_id)); + } + +private: + // MARK: Interface + + CETL_NODISCARD Params getParams() const noexcept final + { + return params_; + } + + CETL_NODISCARD cetl::optional receive() final + { + cetl::optional result{}; + result.swap(last_rx_transfer_); + return result; + } + + // MARK: IRxSession + + void setTransferIdTimeout(const Duration timeout) final + { + const auto timeout_us = std::chrono::duration_cast(timeout); + if (timeout_us.count() > 0) + { + subscription_.transfer_id_timeout_usec = static_cast(timeout_us.count()); + } + } + + // MARK: IRunnable + + void run(const TimePoint) final + { + // Nothing to do here currently. + } + + // MARK: RxSessionDelegate + + void acceptRxTransfer(const CanardRxTransfer& transfer) final + { + const auto priority = static_cast(transfer.metadata.priority); + const auto remote_node_id = static_cast(transfer.metadata.remote_node_id); + const auto transfer_id = static_cast(transfer.metadata.transfer_id); + const auto timestamp = TimePoint{std::chrono::microseconds{transfer.timestamp_usec}}; + + const ServiceTransferMetadata meta{{transfer_id, timestamp, priority}, remote_node_id}; + TransportDelegate::CanardMemory canard_memory{delegate_, transfer.payload, transfer.payload_size}; + + last_rx_transfer_.emplace(ServiceRxTransfer{meta, ScatteredBuffer{std::move(canard_memory)}}); + } + + // MARK: Data members: + + TransportDelegate& delegate_; + const Params params_; + + CanardRxSubscription subscription_{}; + cetl::optional last_rx_transfer_{}; + +}; // SvcRxSession + +// MARK: - + +/// @brief A concrete class to represent a service request RX session (aka server side). +/// +using SvcRequestRxSession = SvcRxSession; + +/// @brief A concrete class to represent a service response RX session (aka client side). +/// +using SvcResponseRxSession = SvcRxSession; + +} // namespace detail +} // namespace can +} // namespace transport +} // namespace libcyphal + +#endif // LIBCYPHAL_TRANSPORT_CAN_SVC_RX_SESSIONS_HPP_INCLUDED diff --git a/include/libcyphal/transport/can/svc_tx_sessions.hpp b/include/libcyphal/transport/can/svc_tx_sessions.hpp index 9329ec622..0ba3a2fd2 100644 --- a/include/libcyphal/transport/can/svc_tx_sessions.hpp +++ b/include/libcyphal/transport/can/svc_tx_sessions.hpp @@ -27,7 +27,7 @@ namespace can namespace detail { -/// @brief A class to represent a service request TX session. +/// @brief A class to represent a service request TX session (aka client side). /// class SvcRequestTxSession final : public IRequestTxSession { @@ -118,7 +118,7 @@ class SvcRequestTxSession final : public IRequestTxSession // MARK: - -/// @brief A class to represent a service response TX session. +/// @brief A class to represent a service response TX session (aka server side). /// class SvcResponseTxSession final : public IResponseTxSession { diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index e85c8417f..ea067c537 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -10,6 +10,7 @@ #include "delegate.hpp" #include "msg_rx_session.hpp" #include "msg_tx_session.hpp" +#include "svc_rx_sessions.hpp" #include "svc_tx_sessions.hpp" #include "libcyphal/transport/transport.hpp" #include "libcyphal/transport/multiplexer.hpp" @@ -184,7 +185,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate return any_error.value(); } - return NotImplementedError{}; + return SvcRequestRxSession::make(asDelegate(), params); } CETL_NODISCARD Expected, AnyError> makeRequestTxSession( @@ -202,7 +203,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate return any_error.value(); } - return NotImplementedError{}; + return SvcResponseRxSession::make(asDelegate(), params); } CETL_NODISCARD Expected, AnyError> makeResponseTxSession( diff --git a/test/unittest/gtest_helpers.hpp b/test/unittest/gtest_helpers.hpp index 8af382dad..9ea90cbf6 100644 --- a/test/unittest/gtest_helpers.hpp +++ b/test/unittest/gtest_helpers.hpp @@ -67,8 +67,8 @@ namespace libcyphal inline void PrintTo(const Duration duration, std::ostream* os) { - auto locale = os->imbue(std::locale("en_US")); - *os << std::chrono::duration_cast(duration).count() << "_us"; + auto locale = os->imbue(std::locale("")); + *os << std::chrono::duration_cast(duration).count() << " us"; os->imbue(locale); } diff --git a/test/unittest/transport/can/test_can_msg_rx_session.cpp b/test/unittest/transport/can/test_can_msg_rx_session.cpp index 7c2e63409..10fef0ef7 100644 --- a/test/unittest/transport/can/test_can_msg_rx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_rx_session.cpp @@ -122,17 +122,18 @@ TEST_F(TestCanMsgRxSession, run_and_receive) ASSERT_THAT(maybe_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_session)); - // 1-st iteration: one frame available @ 1s { + SCOPED_TRACE("1-st iteration: one frame available @ 1s"); + scheduler_.setNow(TimePoint{1s}); const auto rx_timestamp = now(); - EXPECT_CALL(media_mock_, pop(_)).WillOnce([=](auto payload) { - EXPECT_THAT(payload.size(), CANARD_MTU_MAX); - - payload[0] = b(42); - payload[1] = b(147); - payload[2] = b(0xED); + EXPECT_CALL(media_mock_, pop(_)).WillOnce([&](auto p) { + EXPECT_THAT(now(), rx_timestamp + 10ms); + EXPECT_THAT(p.size(), CANARD_MTU_MAX); + p[0] = b('0'); + p[1] = b('1'); + p[2] = b(0b111'01101); return RxMetadata{rx_timestamp, 0x0C'60'23'45, 3}; }); @@ -148,18 +149,20 @@ TEST_F(TestCanMsgRxSession, run_and_receive) EXPECT_THAT(rx_transfer.metadata.priority, Priority::High); EXPECT_THAT(rx_transfer.metadata.publisher_node_id, Optional(0x45)); - std::array buffer{}; - EXPECT_THAT(rx_transfer.payload.size(), 2); - EXPECT_THAT(rx_transfer.payload.copy(0, buffer.data(), buffer.size()), 2); - EXPECT_THAT(buffer, ElementsAre(42, 147)); + std::array buffer{}; + ASSERT_THAT(rx_transfer.payload.size(), buffer.size()); + EXPECT_THAT(rx_transfer.payload.copy(0, buffer.data(), buffer.size()), buffer.size()); + EXPECT_THAT(buffer, ElementsAre('0', '1')); } - - // 2-nd iteration: no frames available { + SCOPED_TRACE("2-nd iteration: no frames available @ 2s"); + scheduler_.setNow(TimePoint{2s}); + const auto rx_timestamp = now(); - EXPECT_CALL(media_mock_, pop(_)).WillOnce([](auto payload) { - EXPECT_THAT(payload.size(), CANARD_MTU_MAX); + EXPECT_CALL(media_mock_, pop(_)).WillOnce([&](auto p) { + EXPECT_THAT(now(), rx_timestamp + 10ms); + EXPECT_THAT(p.size(), CANARD_MTU_MAX); return cetl::nullopt; }); @@ -169,18 +172,18 @@ TEST_F(TestCanMsgRxSession, run_and_receive) const auto maybe_rx_transfer = session->receive(); EXPECT_THAT(maybe_rx_transfer, Eq(cetl::nullopt)); } - - // 3-rd iteration: one anonymous frame available @ 3s { + SCOPED_TRACE("3-rd iteration: one anonymous frame available @ 3s"); + scheduler_.setNow(TimePoint{3s}); const auto rx_timestamp = now(); - EXPECT_CALL(media_mock_, pop(_)).WillOnce([=](auto payload) { - EXPECT_THAT(payload.size(), CANARD_MTU_MAX); - - payload[0] = b(42); - payload[1] = b(147); - payload[2] = b(0xEE); + EXPECT_CALL(media_mock_, pop(_)).WillOnce([&](auto p) { + EXPECT_THAT(now(), rx_timestamp + 10ms); + EXPECT_THAT(p.size(), CANARD_MTU_MAX); + p[0] = b('1'); + p[1] = b('2'); + p[2] = b(0b111'01110); return RxMetadata{rx_timestamp, 0x01'60'23'13, 3}; }); @@ -196,10 +199,10 @@ TEST_F(TestCanMsgRxSession, run_and_receive) EXPECT_THAT(rx_transfer.metadata.priority, Priority::Exceptional); EXPECT_THAT(rx_transfer.metadata.publisher_node_id, Eq(cetl::nullopt)); - std::array buffer{}; - EXPECT_THAT(rx_transfer.payload.size(), 2); - EXPECT_THAT(rx_transfer.payload.copy(0, buffer.data(), buffer.size()), 2); - EXPECT_THAT(buffer, ElementsAre(42, 147)); + std::array buffer{}; + ASSERT_THAT(rx_transfer.payload.size(), buffer.size()); + EXPECT_THAT(rx_transfer.payload.copy(0, buffer.data(), buffer.size()), buffer.size()); + EXPECT_THAT(buffer, ElementsAre('1', '2')); } } diff --git a/test/unittest/transport/can/test_can_svc_rx_sessions.cpp b/test/unittest/transport/can/test_can_svc_rx_sessions.cpp new file mode 100644 index 000000000..152a39dc3 --- /dev/null +++ b/test/unittest/transport/can/test_can_svc_rx_sessions.cpp @@ -0,0 +1,238 @@ +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#include + +#include "media_mock.hpp" +#include "../multiplexer_mock.hpp" +#include "../../gtest_helpers.hpp" +#include "../../test_scheduler.hpp" +#include "../../test_utilities.hpp" +#include "../../memory_resource_mock.hpp" +#include "../../tracking_memory_resource.hpp" + +#include + +namespace +{ +using byte = cetl::byte; + +using namespace libcyphal; +using namespace libcyphal::transport; +using namespace libcyphal::transport::can; +using namespace libcyphal::test_utilities; + +using testing::_; +using testing::Eq; +using testing::Return; +using testing::IsNull; +using testing::IsEmpty; +using testing::NotNull; +using testing::Optional; +using testing::InSequence; +using testing::StrictMock; +using testing::ElementsAre; +using testing::VariantWith; + +using namespace std::chrono_literals; + +class TestCanSvcRxSessions : public testing::Test +{ +protected: + void SetUp() override + { + EXPECT_CALL(media_mock_, getMtu()).WillRepeatedly(Return(CANARD_MTU_CAN_CLASSIC)); + } + + void TearDown() override + { + EXPECT_THAT(mr_.allocations, IsEmpty()); + // TODO: Uncomment this when PMR deleter is fixed. + // EXPECT_EQ(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); + } + + TimePoint now() const + { + return scheduler_.now(); + } + + CETL_NODISCARD UniquePtr makeTransport(cetl::pmr::memory_resource& mr, const NodeId local_node_id) + { + std::array media_array{&media_mock_}; + + // TODO: `local_node_id` could be just passed to `can::makeTransport` as an argument, + // but it's not possible due to CETL issue https://github.com/OpenCyphal/CETL/issues/119. + const auto opt_local_node_id = cetl::optional{local_node_id}; + + auto maybe_transport = can::makeTransport(mr, mux_mock_, media_array, 0, opt_local_node_id); + EXPECT_THAT(maybe_transport, VariantWith>(NotNull())); + return cetl::get>(std::move(maybe_transport)); + } + + // MARK: Data members: + + TestScheduler scheduler_{}; + TrackingMemoryResource mr_; + StrictMock media_mock_{}; + StrictMock mux_mock_{}; +}; + +// MARK: Tests: + +TEST_F(TestCanSvcRxSessions, make_request_setTransferIdTimeout) +{ + auto transport = makeTransport(mr_, 0x31); + + auto maybe_session = transport->makeRequestRxSession({42, 123}); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); + auto session = cetl::get>(std::move(maybe_session)); + + EXPECT_THAT(session->getParams().extent_bytes, 42); + EXPECT_THAT(session->getParams().service_id, 123); + + session->setTransferIdTimeout(0s); + session->setTransferIdTimeout(500ms); +} + +TEST_F(TestCanSvcRxSessions, make_resposnse_no_memory) +{ + StrictMock mr_mock{}; + mr_mock.redirectExpectedCallsTo(mr_); + + // Emulate that there is no memory available for the message session. + EXPECT_CALL(mr_mock, do_allocate(sizeof(can::detail::SvcResponseRxSession), _)).WillOnce(Return(nullptr)); + + auto transport = makeTransport(mr_mock, 0x13); + + auto maybe_session = transport->makeResponseRxSession({64, 0x23, 0x45}); + EXPECT_THAT(maybe_session, VariantWith(VariantWith(_))); +} + +TEST_F(TestCanSvcRxSessions, make_request_fails_due_to_argument_error) +{ + auto transport = makeTransport(mr_, 0x31); + + // Try invalid subject id + auto maybe_session = transport->makeRequestRxSession({64, CANARD_SERVICE_ID_MAX + 1}); + EXPECT_THAT(maybe_session, VariantWith(VariantWith(_))); +} + +TEST_F(TestCanSvcRxSessions, run_and_receive_requests) +{ + auto transport = makeTransport(mr_, 0x31); + + const std::size_t extent_bytes = 8; + auto maybe_session = transport->makeRequestRxSession({extent_bytes, 0x17B}); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); + auto session = cetl::get>(std::move(maybe_session)); + + const auto timeout = 200ms; + session->setTransferIdTimeout(timeout); + + { + SCOPED_TRACE("1-st iteration: one frame available @ 1s"); + + scheduler_.setNow(TimePoint{1s}); + const auto rx_timestamp = now(); + + EXPECT_CALL(media_mock_, pop(_)).WillOnce([&](auto p) { + EXPECT_THAT(now(), rx_timestamp + 10ms); + EXPECT_THAT(p.size(), CANARD_MTU_MAX); + p[0] = b(42); + p[1] = b(147); + p[2] = b(0b111'11101); + return RxMetadata{rx_timestamp, 0b011'1'1'0'101111011'0110001'0010011, 3}; + }); + + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + scheduler_.runNow(+10ms, [&] { session->run(now()); }); + + const auto maybe_rx_transfer = session->receive(); + ASSERT_THAT(maybe_rx_transfer, Optional(_)); + const auto& rx_transfer = maybe_rx_transfer.value(); + + EXPECT_THAT(rx_transfer.metadata.timestamp, rx_timestamp); + EXPECT_THAT(rx_transfer.metadata.transfer_id, 0x1D); + EXPECT_THAT(rx_transfer.metadata.priority, Priority::High); + EXPECT_THAT(rx_transfer.metadata.remote_node_id, 0x13); + + std::array buffer{}; + EXPECT_THAT(rx_transfer.payload.size(), 2); + EXPECT_THAT(rx_transfer.payload.copy(0, buffer.data(), buffer.size()), 2); + EXPECT_THAT(buffer, ElementsAre(42, 147)); + } + { + SCOPED_TRACE("2-nd iteration: no frames available @ 2s"); + + scheduler_.setNow(TimePoint{2s}); + const auto rx_timestamp = now(); + + EXPECT_CALL(media_mock_, pop(_)).WillOnce([&](auto payload) { + EXPECT_THAT(now(), rx_timestamp + 10ms); + EXPECT_THAT(payload.size(), CANARD_MTU_MAX); + return cetl::nullopt; + }); + + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + scheduler_.runNow(+10ms, [&] { session->run(now()); }); + + const auto maybe_rx_transfer = session->receive(); + EXPECT_THAT(maybe_rx_transfer, Eq(cetl::nullopt)); + } + { + SCOPED_TRACE("3-rd iteration: 2 frames available @ 3s"); + + scheduler_.setNow(TimePoint{3s}); + const auto rx_timestamp = now(); + { + InSequence seq; + + EXPECT_CALL(media_mock_, pop(_)).WillOnce([&](auto p) { + EXPECT_THAT(now(), rx_timestamp + 10ms); + EXPECT_THAT(p.size(), CANARD_MTU_MAX); + p[0] = b('0'); + p[1] = b('1'); + p[2] = b('2'); + p[3] = b('3'); + p[4] = b('4'); + p[5] = b('5'); + p[6] = b('6'); + p[7] = b(0b101'11110); + return RxMetadata{rx_timestamp, 0b000'1'1'0'101111011'0110001'0010011, 8}; + }); + EXPECT_CALL(media_mock_, pop(_)).WillOnce([&](auto p) { + EXPECT_THAT(now(), rx_timestamp + 30ms); + EXPECT_THAT(p.size(), CANARD_MTU_MAX); + p[0] = b('7'); + p[1] = b('8'); + p[2] = b('9'); + p[3] = b(0x7D); + p[4] = b(0x61); // expected 16-bit CRC + p[5] = b(0b010'11110); + return RxMetadata{rx_timestamp, 0b000'1'1'0'101111011'0110001'0010011, 6}; + }); + } + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + scheduler_.runNow(+10ms, [&] { session->run(now()); }); + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + scheduler_.runNow(+10ms, [&] { session->run(now()); }); + + const auto maybe_rx_transfer = session->receive(); + ASSERT_THAT(maybe_rx_transfer, Optional(_)); + const auto& rx_transfer = maybe_rx_transfer.value(); + + EXPECT_THAT(rx_transfer.metadata.timestamp, rx_timestamp); + EXPECT_THAT(rx_transfer.metadata.transfer_id, 0x1E); + EXPECT_THAT(rx_transfer.metadata.priority, Priority::Exceptional); + EXPECT_THAT(rx_transfer.metadata.remote_node_id, 0x13); + + std::array buffer{}; + EXPECT_THAT(rx_transfer.payload.size(), buffer.size()); + EXPECT_THAT(rx_transfer.payload.copy(0, buffer.data(), buffer.size()), buffer.size()); + EXPECT_THAT(buffer, ElementsAre('0', '1', '2', '3', '4', '5', '6', '7')); + } +} + +} // namespace diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index bdae36d2e..abf9c96e9 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -32,6 +32,7 @@ using testing::NotNull; using testing::Optional; using testing::InSequence; using testing::StrictMock; +using testing::ElementsAre; using testing::VariantWith; using namespace std::chrono_literals; @@ -393,7 +394,7 @@ TEST_F(TestCanTransport, send_multiframe_payload_to_redundant_not_ready_media) EXPECT_THAT(can_id, AllOf(PriorityOfCanIdEq(metadata.priority), IsMessageCanId())) << ctx; auto tbm = TailByteEq(metadata.transfer_id, false, true, false); - EXPECT_THAT(payload, ElementsAre(b('7'), b('8'), b('9'), _, _ /* CRC bytes */, tbm)) << ctx; + EXPECT_THAT(payload, ElementsAre(b('7'), b('8'), b('9'), b(0x7D), b(0x61) /* CRC bytes */, tbm)) << ctx; return true; }); }; @@ -414,4 +415,101 @@ TEST_F(TestCanTransport, send_multiframe_payload_to_redundant_not_ready_media) scheduler_.runNow(+10us, [&] { transport->run(now()); }); } +TEST_F(TestCanTransport, run_and_receive_svc_responses_from_redundant_media) +{ + StrictMock media_mock2{}; + EXPECT_CALL(media_mock_, pop(_)).WillRepeatedly(Return(cetl::nullopt)); + EXPECT_CALL(media_mock2, pop(_)).WillRepeatedly(Return(cetl::nullopt)); + EXPECT_CALL(media_mock2, getMtu()).WillRepeatedly(Return(CANARD_MTU_CAN_CLASSIC)); + + auto transport = makeTransport(mr_, &media_mock2); + EXPECT_THAT(transport->setLocalNodeId(0x13), Eq(cetl::nullopt)); + + auto maybe_session = transport->makeResponseRxSession({64, 0x17B, 0x31}); + ASSERT_THAT(maybe_session, VariantWith>(NotNull())); + auto session = cetl::get>(std::move(maybe_session)); + + const auto timeout = 200ms; + session->setTransferIdTimeout(timeout); + + const auto epoch = TimePoint{10s}; + scheduler_.setNow(epoch); + const auto rx1_timestamp = epoch; + const auto rx2_timestamp = epoch + 2 * timeout; + { + InSequence seq; + + // 1. Emulate that only one 1st frame came from the 1st media interface (@ rx1_timestamp+10ms)... + // + EXPECT_CALL(media_mock_, pop(_)).WillOnce([&](auto p) { + EXPECT_THAT(now(), rx1_timestamp + 10ms); + EXPECT_THAT(p.size(), CANARD_MTU_MAX); + p[0] = b('0'); + p[1] = b('1'); + p[2] = b('2'); + p[3] = b('3'); + p[4] = b('4'); + p[5] = b('5'); + p[6] = b('6'); + p[7] = b(0b101'11101); + return RxMetadata{rx1_timestamp, 0b111'1'0'0'101111011'0010011'0110001, 8}; + }); + EXPECT_CALL(media_mock2, pop(_)).WillOnce([&](auto) { + EXPECT_THAT(now(), rx1_timestamp + 10ms); + return cetl::nullopt; + }); + // 2. And then 2nd media delivered all frames ones again after timeout (@ rx2_timestamp+10ms). + // + EXPECT_CALL(media_mock_, pop(_)).WillOnce([&](auto) { + EXPECT_THAT(now(), rx2_timestamp + 10ms); + return cetl::nullopt; + }); + EXPECT_CALL(media_mock2, pop(_)).WillOnce([&](auto p) { + EXPECT_THAT(now(), rx2_timestamp + 10ms); + EXPECT_THAT(p.size(), CANARD_MTU_MAX); + p[0] = b('0'); + p[1] = b('1'); + p[2] = b('2'); + p[3] = b('3'); + p[4] = b('4'); + p[5] = b('5'); + p[6] = b('6'); + p[7] = b(0b101'11110); + return RxMetadata{rx2_timestamp, 0b111'1'0'0'101111011'0010011'0110001, 8}; + }); + EXPECT_CALL(media_mock2, pop(_)).WillOnce([&](auto p) { + EXPECT_THAT(now(), rx2_timestamp + 30ms); + EXPECT_THAT(p.size(), CANARD_MTU_MAX); + p[0] = b('7'); + p[1] = b('8'); + p[2] = b('9'); + p[3] = b(0x7D); + p[4] = b(0x61); // expected 16-bit CRC + p[5] = b(0b010'11110); + return RxMetadata{rx2_timestamp + 1ms, 0b111'1'0'0'101111011'0010011'0110001, 6}; + }); + } + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + scheduler_.runNow(+10ms, [&] { session->run(now()); }); + scheduler_.setNow(rx2_timestamp); + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + scheduler_.runNow(+10ms, [&] { session->run(now()); }); + scheduler_.runNow(+10ms, [&] { transport->run(now()); }); + scheduler_.runNow(+10ms, [&] { session->run(now()); }); + + const auto maybe_rx_transfer = session->receive(); + ASSERT_THAT(maybe_rx_transfer, Optional(_)); + const auto& rx_transfer = maybe_rx_transfer.value(); + + EXPECT_THAT(rx_transfer.metadata.timestamp, rx2_timestamp); + EXPECT_THAT(rx_transfer.metadata.transfer_id, 0x1E); + EXPECT_THAT(rx_transfer.metadata.priority, Priority::Optional); + EXPECT_THAT(rx_transfer.metadata.remote_node_id, 0x31); + + std::array buffer{}; + EXPECT_THAT(rx_transfer.payload.size(), buffer.size()); + EXPECT_THAT(rx_transfer.payload.copy(0, buffer.data(), buffer.size()), buffer.size()); + EXPECT_THAT(buffer, ElementsAre('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')); +} + } // namespace From 296f699a09da1dc0074297d178ee00f75045673e Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Sun, 28 Apr 2024 14:01:42 +0300 Subject: [PATCH 50/64] more coverage #verification --- .../can/test_can_svc_rx_sessions.cpp | 6 +++- .../transport/can/test_can_transport.cpp | 31 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/test/unittest/transport/can/test_can_svc_rx_sessions.cpp b/test/unittest/transport/can/test_can_svc_rx_sessions.cpp index 152a39dc3..c1eb57044 100644 --- a/test/unittest/transport/can/test_can_svc_rx_sessions.cpp +++ b/test/unittest/transport/can/test_can_svc_rx_sessions.cpp @@ -128,7 +128,11 @@ TEST_F(TestCanSvcRxSessions, run_and_receive_requests) ASSERT_THAT(maybe_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_session)); - const auto timeout = 200ms; + const auto params = session->getParams(); + EXPECT_THAT(params.extent_bytes, extent_bytes); + EXPECT_THAT(params.service_id, 0x17B); + + const auto timeout = 200ms; session->setTransferIdTimeout(timeout); { diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index abf9c96e9..3a3dbfebb 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -265,6 +265,32 @@ TEST_F(TestCanTransport, makeMessageRxSession_invalid_resubscription) EXPECT_THAT(maybe_rx_session2, VariantWith(VariantWith(_))); } +TEST_F(TestCanTransport, makeRequestRxSession_invalid_resubscription) +{ + auto transport = makeTransport(mr_); + + const PortId test_subject_id = 111; + + auto maybe_rx_session1 = transport->makeRequestRxSession({0, test_subject_id}); + ASSERT_THAT(maybe_rx_session1, VariantWith>(NotNull())); + + auto maybe_rx_session2 = transport->makeRequestRxSession({0, test_subject_id}); + EXPECT_THAT(maybe_rx_session2, VariantWith(VariantWith(_))); +} + +TEST_F(TestCanTransport, makeResponseRxSession_invalid_resubscription) +{ + auto transport = makeTransport(mr_); + + const PortId test_subject_id = 111; + + auto maybe_rx_session1 = transport->makeResponseRxSession({0, test_subject_id, 0x31}); + ASSERT_THAT(maybe_rx_session1, VariantWith>(NotNull())); + + auto maybe_rx_session2 = transport->makeResponseRxSession({0, test_subject_id, 0x31}); + EXPECT_THAT(maybe_rx_session2, VariantWith(VariantWith(_))); +} + TEST_F(TestCanTransport, makeMessageTxSession) { auto transport = makeTransport(mr_); @@ -429,6 +455,11 @@ TEST_F(TestCanTransport, run_and_receive_svc_responses_from_redundant_media) ASSERT_THAT(maybe_session, VariantWith>(NotNull())); auto session = cetl::get>(std::move(maybe_session)); + const auto params = session->getParams(); + EXPECT_THAT(params.extent_bytes, 64); + EXPECT_THAT(params.service_id, 0x17B); + EXPECT_THAT(params.server_node_id, 0x31); + const auto timeout = 200ms; session->setTransferIdTimeout(timeout); From 1f2d74470950ffe6359a83f6f4f22e992a39dfe2 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Sun, 28 Apr 2024 14:24:05 +0300 Subject: [PATCH 51/64] Autosar A12-1-2 fixes --- include/libcyphal/transport/can/msg_rx_session.hpp | 6 ++++-- include/libcyphal/transport/can/msg_tx_session.hpp | 3 ++- include/libcyphal/transport/contiguous_payload.hpp | 9 ++++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp index 99ad9558b..9a366e90b 100644 --- a/include/libcyphal/transport/can/msg_rx_session.hpp +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -52,6 +52,8 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate MessageRxSession(Tag, TransportDelegate& delegate, const MessageRxParams& params) : delegate_{delegate} , params_{params} + , subscription_{} + , last_rx_transfer_{} { const auto result = ::canardRxSubscribe(&delegate.canard_instance(), CanardTransferKindMessage, @@ -130,8 +132,8 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate TransportDelegate& delegate_; const MessageRxParams params_; - CanardRxSubscription subscription_{}; - cetl::optional last_rx_transfer_{}; + CanardRxSubscription subscription_; + cetl::optional last_rx_transfer_; }; // MessageRxSession diff --git a/include/libcyphal/transport/can/msg_tx_session.hpp b/include/libcyphal/transport/can/msg_tx_session.hpp index fc4cf13f0..420162104 100644 --- a/include/libcyphal/transport/can/msg_tx_session.hpp +++ b/include/libcyphal/transport/can/msg_tx_session.hpp @@ -53,6 +53,7 @@ class MessageTxSession final : public IMessageTxSession MessageTxSession(Tag, TransportDelegate& delegate, const MessageTxParams& params) : delegate_{delegate} , params_{params} + , send_timeout_{std::chrono::seconds{1}} { } @@ -110,7 +111,7 @@ class MessageTxSession final : public IMessageTxSession TransportDelegate& delegate_; const MessageTxParams params_; - Duration send_timeout_ = std::chrono::seconds{1}; + Duration send_timeout_; }; // MessageTxSession diff --git a/include/libcyphal/transport/contiguous_payload.hpp b/include/libcyphal/transport/contiguous_payload.hpp index 1aef20216..7d064a222 100644 --- a/include/libcyphal/transport/contiguous_payload.hpp +++ b/include/libcyphal/transport/contiguous_payload.hpp @@ -34,6 +34,9 @@ class ContiguousPayload final public: ContiguousPayload(cetl::pmr::memory_resource& mr, const PayloadFragments payload_fragments) : mr_{mr} + , payload_{nullptr} + , payload_size_{0} + , allocated_buffer_{nullptr} { using Fragment = cetl::span; @@ -93,9 +96,9 @@ class ContiguousPayload final // MARK: Data members: cetl::pmr::memory_resource& mr_; - const cetl::byte* payload_{nullptr}; - std::size_t payload_size_{0}; - cetl::byte* allocated_buffer_{nullptr}; + const cetl::byte* payload_; + std::size_t payload_size_; + cetl::byte* allocated_buffer_; }; // ContiguousBytes From b94e0c80613d2557ef79988a788a05bb13dfbfa0 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Sun, 28 Apr 2024 16:10:54 +0300 Subject: [PATCH 52/64] PR fixes: less `auto` in production when result type is not obvious --- include/libcyphal/transport/can/delegate.hpp | 6 +-- .../transport/can/msg_rx_session.hpp | 12 +++--- include/libcyphal/transport/can/transport.hpp | 41 ++++++++++--------- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp index d006fd783..9b371d6ef 100644 --- a/include/libcyphal/transport/can/delegate.hpp +++ b/include/libcyphal/transport/can/delegate.hpp @@ -89,7 +89,7 @@ class TransportDelegate return 0; } - const auto bytes_to_copy = std::min(length_bytes, payload_size_ - offset_bytes); + const std::size_t bytes_to_copy = std::min(length_bytes, payload_size_ - offset_bytes); std::memmove(destination, static_cast(buffer_) + offset_bytes, bytes_to_copy); return bytes_to_copy; } @@ -199,7 +199,7 @@ class TransportDelegate /// CETL_NODISCARD static void* allocateMemoryForCanard(CanardInstance* ins, std::size_t amount) { - auto& self = getSelfFrom(ins); + TransportDelegate& self = getSelfFrom(ins); const std::size_t memory_size = sizeof(CanardMemoryHeader) + amount; auto memory_header = static_cast(self.memory_.allocate(memory_size)); @@ -219,7 +219,7 @@ class TransportDelegate /// static void freeCanardMemory(CanardInstance* ins, void* pointer) { - auto& self = getSelfFrom(ins); + TransportDelegate& self = getSelfFrom(ins); self.freeCanardMemory(pointer); } diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp index 9a366e90b..d0c70b1ee 100644 --- a/include/libcyphal/transport/can/msg_rx_session.hpp +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -55,12 +55,12 @@ class MessageRxSession final : public IMessageRxSession, private SessionDelegate , subscription_{} , last_rx_transfer_{} { - const auto result = ::canardRxSubscribe(&delegate.canard_instance(), - CanardTransferKindMessage, - static_cast(params_.subject_id), - static_cast(params_.extent_bytes), - CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC, - &subscription_); + const std::int8_t result = ::canardRxSubscribe(&delegate.canard_instance(), + CanardTransferKindMessage, + static_cast(params_.subject_id), + static_cast(params_.extent_bytes), + CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC, + &subscription_); (void) result; CETL_DEBUG_ASSERT(result >= 0, "There is no way currently to get an error here."); CETL_DEBUG_ASSERT(result > 0, "New subscription supposed to be made."); diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index 923e0d072..904bbb3e9 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -131,7 +131,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate // Allow setting the same node ID multiple times, but only once otherwise. // - auto& ins = canard_instance(); + CanardInstance& ins = canard_instance(); if (ins.node_id == node_id) { return cetl::nullopt; @@ -147,7 +147,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate CETL_NODISCARD ProtocolParams getProtocolParams() const noexcept final { - const auto min_mtu = + const std::size_t min_mtu = reduceMedia(std::numeric_limits::max(), [](const std::size_t mtu, const Media& media) { return std::min(mtu, media.interface.getMtu()); }); @@ -158,7 +158,8 @@ class TransportImpl final : public ICanTransport, private TransportDelegate CETL_NODISCARD Expected, AnyError> makeMessageRxSession( const MessageRxParams& params) final { - auto any_error = ensureNewSessionFor(CanardTransferKindMessage, params.subject_id, CANARD_SUBJECT_ID_MAX); + const cetl::optional any_error = + ensureNewSessionFor(CanardTransferKindMessage, params.subject_id, CANARD_SUBJECT_ID_MAX); if (any_error.has_value()) { return any_error.value(); @@ -176,7 +177,8 @@ class TransportImpl final : public ICanTransport, private TransportDelegate CETL_NODISCARD Expected, AnyError> makeRequestRxSession( const RequestRxParams& params) final { - auto any_error = ensureNewSessionFor(CanardTransferKindRequest, params.service_id, CANARD_SERVICE_ID_MAX); + const cetl::optional any_error = + ensureNewSessionFor(CanardTransferKindRequest, params.service_id, CANARD_SERVICE_ID_MAX); if (any_error.has_value()) { return any_error.value(); @@ -193,7 +195,8 @@ class TransportImpl final : public ICanTransport, private TransportDelegate CETL_NODISCARD Expected, AnyError> makeResponseRxSession( const ResponseRxParams& params) final { - auto any_error = ensureNewSessionFor(CanardTransferKindResponse, params.service_id, CANARD_SERVICE_ID_MAX); + const cetl::optional any_error = + ensureNewSessionFor(CanardTransferKindResponse, params.service_id, CANARD_SERVICE_ID_MAX); if (any_error.has_value()) { return any_error.value(); @@ -237,7 +240,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate { media.canard_tx_queue.mtu_bytes = media.interface.getMtu(); - const auto result = + const std::int32_t result = ::canardTxPush(&media.canard_tx_queue, &canard_instance(), deadline, &metadata, payload_size, payload); if (result < 0) { @@ -291,7 +294,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate return ArgumentError{}; } - const auto hasSubscription = ::canardRxHasSubscription(&canard_instance(), transfer_kind, port_id); + const std::int8_t hasSubscription = ::canardRxHasSubscription(&canard_instance(), transfer_kind, port_id); CETL_DEBUG_ASSERT(hasSubscription >= 0, "There is no way currently to get an error here."); if (hasSubscription > 0) { @@ -325,9 +328,9 @@ class TransportImpl final : public ICanTransport, private TransportDelegate void flushCanardTxQueue(CanardTxQueue& canard_tx_queue) { - while (const auto maybe_item = ::canardTxPeek(&canard_tx_queue)) + while (const CanardTxQueueItem* const maybe_item = ::canardTxPeek(&canard_tx_queue)) { - auto item = ::canardTxPop(&canard_tx_queue, maybe_item); + CanardTxQueueItem* const item = ::canardTxPop(&canard_tx_queue, maybe_item); freeCanardMemory(item); } } @@ -339,12 +342,12 @@ class TransportImpl final : public ICanTransport, private TransportDelegate for (const Media& media : media_array_) { // TODO: Handle errors. - const auto pop_result = media.interface.pop(payload); + const Expected, MediaError> pop_result = media.interface.pop(payload); if (const auto opt_rx_meta = cetl::get_if>(&pop_result)) { if (opt_rx_meta->has_value()) { - const auto& rx_meta = opt_rx_meta->value(); + const RxMetadata& rx_meta = opt_rx_meta->value(); const auto timestamp_us = std::chrono::duration_cast(rx_meta.timestamp.time_since_epoch()); @@ -354,12 +357,12 @@ class TransportImpl final : public ICanTransport, private TransportDelegate CanardRxSubscription* out_subscription{}; // TODO: Handle errors. - const auto result = ::canardRxAccept(&canard_instance(), - static_cast(timestamp_us.count()), - &canard_frame, - media.index, - &out_transfer, - &out_subscription); + const std::int8_t result = ::canardRxAccept(&canard_instance(), + static_cast(timestamp_us.count()), + &canard_frame, + media.index, + &out_transfer, + &out_subscription); if (result > 0) { CETL_DEBUG_ASSERT(out_subscription != nullptr, "Expected subscription."); @@ -378,7 +381,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate { for (Media& media : media_array_) { - while (const auto tx_item = ::canardTxPeek(&media.canard_tx_queue)) + while (const CanardTxQueueItem* const tx_item = ::canardTxPeek(&media.canard_tx_queue)) { // We are dropping any TX item that has expired. // Otherwise, we would send it to the media interface. @@ -389,7 +392,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate { const cetl::span payload{static_cast(tx_item->frame.payload), tx_item->frame.payload_size}; - const auto maybe_pushed = + const Expected maybe_pushed = media.interface.push(deadline, static_cast(tx_item->frame.extended_can_id), payload); if (const auto is_pushed = cetl::get_if(&maybe_pushed)) { From 85aa367463790be61f040ba5e517948a499606ec Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Sun, 28 Apr 2024 16:22:44 +0300 Subject: [PATCH 53/64] =?UTF-8?q?PR=20fixes:=20rename=20`TestScheduler`=20?= =?UTF-8?q?=E2=86=92=20`VirtualTimeScheduler`.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transport/can/test_can_msg_rx_session.cpp | 4 ++-- .../transport/can/test_can_msg_tx_session.cpp | 4 ++-- test/unittest/transport/can/test_can_transport.cpp | 4 ++-- ...test_scheduler.hpp => virtual_time_scheduler.hpp} | 12 ++++++------ 4 files changed, 12 insertions(+), 12 deletions(-) rename test/unittest/{test_scheduler.hpp => virtual_time_scheduler.hpp} (71%) diff --git a/test/unittest/transport/can/test_can_msg_rx_session.cpp b/test/unittest/transport/can/test_can_msg_rx_session.cpp index ff159a442..ed0223b0d 100644 --- a/test/unittest/transport/can/test_can_msg_rx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_rx_session.cpp @@ -8,9 +8,9 @@ #include "media_mock.hpp" #include "../multiplexer_mock.hpp" #include "../../gtest_helpers.hpp" -#include "../../test_scheduler.hpp" #include "../../test_utilities.hpp" #include "../../memory_resource_mock.hpp" +#include "../../virtual_time_scheduler.hpp" #include "../../tracking_memory_resource.hpp" #include @@ -68,7 +68,7 @@ class TestCanMsgRxSession : public testing::Test // MARK: Data members: - TestScheduler scheduler_{}; + VirtualTimeScheduler scheduler_{}; TrackingMemoryResource mr_; StrictMock media_mock_{}; StrictMock mux_mock_{}; diff --git a/test/unittest/transport/can/test_can_msg_tx_session.cpp b/test/unittest/transport/can/test_can_msg_tx_session.cpp index f893adbed..d3cd17660 100644 --- a/test/unittest/transport/can/test_can_msg_tx_session.cpp +++ b/test/unittest/transport/can/test_can_msg_tx_session.cpp @@ -8,9 +8,9 @@ #include "media_mock.hpp" #include "../multiplexer_mock.hpp" #include "../../gtest_helpers.hpp" -#include "../../test_scheduler.hpp" #include "../../test_utilities.hpp" #include "../../memory_resource_mock.hpp" +#include "../../virtual_time_scheduler.hpp" #include "../../tracking_memory_resource.hpp" #include @@ -71,7 +71,7 @@ class TestCanMsgTxSession : public testing::Test // MARK: Data members: - TestScheduler scheduler_{}; + VirtualTimeScheduler scheduler_{}; TrackingMemoryResource mr_; StrictMock mux_mock_{}; StrictMock media_mock_{}; diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index 8e79e2620..8ae177589 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -8,9 +8,9 @@ #include "media_mock.hpp" #include "../multiplexer_mock.hpp" #include "../../gtest_helpers.hpp" -#include "../../test_scheduler.hpp" #include "../../test_utilities.hpp" #include "../../memory_resource_mock.hpp" +#include "../../virtual_time_scheduler.hpp" #include "../../tracking_memory_resource.hpp" #include @@ -69,7 +69,7 @@ class TestCanTransport : public testing::Test // MARK: Data members: - TestScheduler scheduler_{}; + VirtualTimeScheduler scheduler_{}; TrackingMemoryResource mr_; StrictMock media_mock_{}; StrictMock mux_mock_{}; diff --git a/test/unittest/test_scheduler.hpp b/test/unittest/virtual_time_scheduler.hpp similarity index 71% rename from test/unittest/test_scheduler.hpp rename to test/unittest/virtual_time_scheduler.hpp index 385d4ccf0..b8ba365ba 100644 --- a/test/unittest/test_scheduler.hpp +++ b/test/unittest/virtual_time_scheduler.hpp @@ -3,17 +3,17 @@ /// Copyright Amazon.com Inc. or its affiliates. /// SPDX-License-Identifier: MIT -#ifndef LIBCYPHAL_TEST_SCHEDULER_HPP -#define LIBCYPHAL_TEST_SCHEDULER_HPP +#ifndef LIBCYPHAL_VIRTUAL_TIME_SCHEDULER_HPP +#define LIBCYPHAL_VIRTUAL_TIME_SCHEDULER_HPP #include namespace libcyphal { -struct TestScheduler final +struct VirtualTimeScheduler final { - explicit TestScheduler(const TimePoint initial_now = {}) + explicit VirtualTimeScheduler(const TimePoint initial_now = {}) : now_{initial_now} { } @@ -41,9 +41,9 @@ struct TestScheduler final } private: - TimePoint now_{}; + TimePoint now_; }; } // namespace libcyphal -#endif // LIBCYPHAL_TEST_SCHEDULER_HPP +#endif // LIBCYPHAL_VIRTUAL_TIME_SCHEDULER_HPP From 6d66d697c5cf1b6d820f0fbb5a20a8ec19ab0007 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Sun, 28 Apr 2024 17:31:19 +0300 Subject: [PATCH 54/64] minor changes --- .../transport/can/msg_rx_session.hpp | 5 ++-- .../transport/can/svc_rx_sessions.hpp | 23 ++++++++++--------- .../transport/can/svc_tx_sessions.hpp | 6 +++-- .../can/test_can_svc_rx_sessions.cpp | 4 ++-- .../can/test_can_svc_tx_sessions.cpp | 6 ++--- 5 files changed, 23 insertions(+), 21 deletions(-) diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp index b5b077d20..36bfa323a 100644 --- a/include/libcyphal/transport/can/msg_rx_session.hpp +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -137,9 +137,8 @@ class MessageRxSession final : public IMessageRxSession, private RxSessionDelega // MARK: Data members: - TransportDelegate& delegate_; - const MessageRxParams params_; - + TransportDelegate& delegate_; + const MessageRxParams params_; CanardRxSubscription subscription_; cetl::optional last_rx_transfer_; diff --git a/include/libcyphal/transport/can/svc_rx_sessions.hpp b/include/libcyphal/transport/can/svc_rx_sessions.hpp index 4805b5f4b..04dfbef76 100644 --- a/include/libcyphal/transport/can/svc_rx_sessions.hpp +++ b/include/libcyphal/transport/can/svc_rx_sessions.hpp @@ -68,13 +68,15 @@ class SvcRxSession final : public Interface_, private RxSessionDelegate SvcRxSession(Tag, TransportDelegate& delegate, const Params& params) : delegate_{delegate} , params_{params} + , subscription_{} + , last_rx_transfer_{} { - const auto result = ::canardRxSubscribe(&delegate.canard_instance(), - TransferKind, - static_cast(params_.service_id), - static_cast(params_.extent_bytes), - CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC, - &subscription_); + const int8_t result = ::canardRxSubscribe(&delegate.canard_instance(), + TransferKind, + static_cast(params_.service_id), + static_cast(params_.extent_bytes), + CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC, + &subscription_); (void) result; CETL_DEBUG_ASSERT(result >= 0, "There is no way currently to get an error here."); CETL_DEBUG_ASSERT(result > 0, "New subscription supposed to be made."); @@ -139,11 +141,10 @@ class SvcRxSession final : public Interface_, private RxSessionDelegate // MARK: Data members: - TransportDelegate& delegate_; - const Params params_; - - CanardRxSubscription subscription_{}; - cetl::optional last_rx_transfer_{}; + TransportDelegate& delegate_; + const Params params_; + CanardRxSubscription subscription_; + cetl::optional last_rx_transfer_; }; // SvcRxSession diff --git a/include/libcyphal/transport/can/svc_tx_sessions.hpp b/include/libcyphal/transport/can/svc_tx_sessions.hpp index 0ba3a2fd2..7e04a0b56 100644 --- a/include/libcyphal/transport/can/svc_tx_sessions.hpp +++ b/include/libcyphal/transport/can/svc_tx_sessions.hpp @@ -61,6 +61,7 @@ class SvcRequestTxSession final : public IRequestTxSession SvcRequestTxSession(Tag, TransportDelegate& delegate, const RequestTxParams& params) : delegate_{delegate} , params_{params} + , send_timeout_{std::chrono::seconds{1}} { } @@ -112,7 +113,7 @@ class SvcRequestTxSession final : public IRequestTxSession TransportDelegate& delegate_; const RequestTxParams params_; - Duration send_timeout_ = std::chrono::seconds{1}; + Duration send_timeout_; }; // SvcRequestTxSession @@ -152,6 +153,7 @@ class SvcResponseTxSession final : public IResponseTxSession SvcResponseTxSession(Tag, TransportDelegate& delegate, const ResponseTxParams& params) : delegate_{delegate} , params_{params} + , send_timeout_{std::chrono::seconds{1}} { } @@ -203,7 +205,7 @@ class SvcResponseTxSession final : public IResponseTxSession TransportDelegate& delegate_; const ResponseTxParams params_; - Duration send_timeout_ = std::chrono::seconds{1}; + Duration send_timeout_; }; // SvcResponseTxSession diff --git a/test/unittest/transport/can/test_can_svc_rx_sessions.cpp b/test/unittest/transport/can/test_can_svc_rx_sessions.cpp index c1eb57044..2d3a15402 100644 --- a/test/unittest/transport/can/test_can_svc_rx_sessions.cpp +++ b/test/unittest/transport/can/test_can_svc_rx_sessions.cpp @@ -8,9 +8,9 @@ #include "media_mock.hpp" #include "../multiplexer_mock.hpp" #include "../../gtest_helpers.hpp" -#include "../../test_scheduler.hpp" #include "../../test_utilities.hpp" #include "../../memory_resource_mock.hpp" +#include "../../virtual_time_scheduler.hpp" #include "../../tracking_memory_resource.hpp" #include @@ -73,7 +73,7 @@ class TestCanSvcRxSessions : public testing::Test // MARK: Data members: - TestScheduler scheduler_{}; + VirtualTimeScheduler scheduler_{}; TrackingMemoryResource mr_; StrictMock media_mock_{}; StrictMock mux_mock_{}; diff --git a/test/unittest/transport/can/test_can_svc_tx_sessions.cpp b/test/unittest/transport/can/test_can_svc_tx_sessions.cpp index 06b052954..b1b5e62a1 100644 --- a/test/unittest/transport/can/test_can_svc_tx_sessions.cpp +++ b/test/unittest/transport/can/test_can_svc_tx_sessions.cpp @@ -8,9 +8,9 @@ #include "media_mock.hpp" #include "../multiplexer_mock.hpp" #include "../../gtest_helpers.hpp" -#include "../../test_scheduler.hpp" #include "../../test_utilities.hpp" #include "../../memory_resource_mock.hpp" +#include "../../virtual_time_scheduler.hpp" #include "../../tracking_memory_resource.hpp" #include @@ -75,7 +75,7 @@ class TestCanSvcTxSessions : public testing::Test // MARK: Data members: - TestScheduler scheduler_{}; + VirtualTimeScheduler scheduler_{}; TrackingMemoryResource mr_; StrictMock mux_mock_{}; StrictMock media_mock_{}; @@ -181,7 +181,7 @@ TEST_F(TestCanSvcTxSessions, send_request_with_argument_error) auto session = cetl::get>(std::move(maybe_session)); scheduler_.runNow(+100ms); - const auto timeout = 1s; + const auto timeout = 1s; const auto transfer_time = now(); const PayloadFragments empty_payload{}; From 9c711dfa69ed3d0a543cf8dab6ecd0eb92292124 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Sun, 28 Apr 2024 20:29:54 +0300 Subject: [PATCH 55/64] fix OOM for CAN transport --- include/libcyphal/transport/can/transport.hpp | 102 ++++++++++-------- .../transport/can/test_can_transport.cpp | 18 +++- 2 files changed, 72 insertions(+), 48 deletions(-) diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index 904bbb3e9..562b2c67e 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -33,17 +33,43 @@ class ICanTransport : public ITransport /// namespace detail { + class TransportImpl final : public ICanTransport, private TransportDelegate { - // In use to disable public construction. - // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ - struct Tag + /// @brief Defines specification for making interface unique ptr. + /// + struct Spec { - explicit Tag() = default; using Interface = ICanTransport; using Concrete = TransportImpl; }; + /// @brief Internal (private) storage of a media index, its interface and TX queue. + /// + /// B/c it's private, it also disables public construction of `TransportImpl`. + /// See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ + /// + class Media final + { + public: + Media(const std::size_t _index, IMedia& _interface, const std::size_t tx_capacity) + : index{static_cast(_index)} + , interface{_interface} + , canard_tx_queue{::canardTxInit(tx_capacity, _interface.getMtu())} + { + } + ~Media() = default; + Media(const Media&) = delete; + Media(Media&&) noexcept = default; + Media& operator=(const Media&) = delete; + Media& operator=(Media&&) noexcept = delete; + + const std::uint8_t index; + IMedia& interface; + CanardTxQueue canard_tx_queue; + }; + using MediaArray = libcyphal::detail::VarArray; + public: CETL_NODISCARD static Expected, FactoryError> make( cetl::pmr::memory_resource& memory, @@ -62,21 +88,21 @@ class TransportImpl final : public ICanTransport, private TransportDelegate { return ArgumentError{}; } - if (local_node_id.has_value() && local_node_id.value() > CANARD_NODE_ID_MAX) + if (local_node_id.has_value() && (local_node_id.value() > CANARD_NODE_ID_MAX)) { return ArgumentError{}; } + MediaArray media_array{make_media_array(memory, tx_capacity, media_count, media)}; + if (media_array.size() != media_count) + { + return MemoryError{}; + } + const auto canard_node_id = static_cast(local_node_id.value_or(CANARD_NODE_ID_UNSET)); - auto transport = libcyphal::detail::makeUniquePtr(memory, - Tag{}, - memory, - multiplexer, - media_count, - media, - tx_capacity, - canard_node_id); + auto transport = + libcyphal::detail::makeUniquePtr(memory, memory, multiplexer, std::move(media_array), canard_node_id); if (transport == nullptr) { return MemoryError{}; @@ -85,15 +111,12 @@ class TransportImpl final : public ICanTransport, private TransportDelegate return transport; } - TransportImpl(Tag, - cetl::pmr::memory_resource& memory, + TransportImpl(cetl::pmr::memory_resource& memory, IMultiplexer& multiplexer, - const std::size_t media_count, - const cetl::span media_interfaces, - const std::size_t tx_capacity, + MediaArray&& media_array, const CanardNodeID canard_node_id) : TransportDelegate{memory} - , media_array_{make_media_array(memory, tx_capacity, media_count, media_interfaces)} + , media_array_{std::move(media_array)} { // TODO: Use it! (void) multiplexer; @@ -253,27 +276,6 @@ class TransportImpl final : public ICanTransport, private TransportDelegate // MARK: Privates: - class Media final - { - public: - Media(const std::size_t _index, IMedia& _interface, const std::size_t tx_capacity) - : index{static_cast(_index)} - , interface{_interface} - , canard_tx_queue{::canardTxInit(tx_capacity, _interface.getMtu())} - { - } - ~Media() = default; - Media(const Media&) = delete; - Media(Media&&) noexcept = default; - Media& operator=(const Media&) = delete; - Media& operator=(Media&&) noexcept = delete; - - const std::uint8_t index; - IMedia& interface; - CanardTxQueue canard_tx_queue; - }; - using MediaArray = libcyphal::detail::VarArray; - template CETL_NODISCARD T reduceMedia(const T init, Reducer reducer) const { @@ -310,18 +312,24 @@ class TransportImpl final : public ICanTransport, private TransportDelegate const cetl::span media_interfaces) { MediaArray media_array{media_count, &memory}; - media_array.reserve(media_count); - std::size_t index = 0; - for (IMedia* const media_interface : media_interfaces) + // Reserve the space for the whole array (to avoid reallocations). + // Capacity will be less than requested in case of out of memory. + media_array.reserve(media_count); + if (media_array.capacity() >= media_count) { - if (media_interface != nullptr) + std::size_t index = 0; + for (IMedia* const media_interface : media_interfaces) { - IMedia& media = *media_interface; - media_array.emplace_back(index++, media, tx_capacity); + if (media_interface != nullptr) + { + IMedia& media = *media_interface; + media_array.emplace_back(index++, media, tx_capacity); + } } + CETL_DEBUG_ASSERT(index == media_count, ""); + CETL_DEBUG_ASSERT(media_array.size() == media_count, ""); } - CETL_DEBUG_ASSERT(!media_array.empty() && (media_array.size() == media_count) && (index == media_count), ""); return media_array; } diff --git a/test/unittest/transport/can/test_can_transport.cpp b/test/unittest/transport/can/test_can_transport.cpp index 8ae177589..1d41a94ac 100644 --- a/test/unittest/transport/can/test_can_transport.cpp +++ b/test/unittest/transport/can/test_can_transport.cpp @@ -77,7 +77,23 @@ class TestCanTransport : public testing::Test // MARK: Tests: -TEST_F(TestCanTransport, makeTransport_no_memory) +TEST_F(TestCanTransport, makeTransport_no_memory_at_all) +{ + StrictMock mr_mock{}; + mr_mock.redirectExpectedCallsTo(mr_); + + // Emulate that there is no memory at all (even for initial array of media). + EXPECT_CALL(mr_mock, do_allocate(_, _)).WillRepeatedly(Return(nullptr)); +#if (__cplusplus < CETL_CPP_STANDARD_17) + EXPECT_CALL(mr_mock, do_reallocate(nullptr, 0, _, _)).WillRepeatedly(Return(nullptr)); +#endif + + std::array media_array{&media_mock_}; + auto maybe_transport = can::makeTransport(mr_mock, mux_mock_, media_array, 0, {}); + EXPECT_THAT(maybe_transport, VariantWith(VariantWith(_))); +} + +TEST_F(TestCanTransport, makeTransport_no_memory_for_impl) { StrictMock mr_mock{}; mr_mock.redirectExpectedCallsTo(mr_); From 05c4ad5fa3177faf6f9d18c88762d34089f2c2d2 Mon Sep 17 00:00:00 2001 From: Sergei Date: Mon, 29 Apr 2024 00:03:15 +0300 Subject: [PATCH 56/64] rename `Tag` -> `Spec` #verification --- .../transport/can/msg_rx_session.hpp | 15 ++++++---- .../transport/can/msg_tx_session.hpp | 15 ++++++---- .../transport/can/svc_rx_sessions.hpp | 15 ++++++---- .../transport/can/svc_tx_sessions.hpp | 30 +++++++++++-------- include/libcyphal/transport/can/transport.hpp | 18 +++++++---- include/libcyphal/transport/udp/transport.hpp | 13 ++++---- include/libcyphal/types.hpp | 10 +++---- 7 files changed, 70 insertions(+), 46 deletions(-) diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp index 36bfa323a..daebfec1e 100644 --- a/include/libcyphal/transport/can/msg_rx_session.hpp +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -30,13 +30,16 @@ namespace detail /// class MessageRxSession final : public IMessageRxSession, private RxSessionDelegate { - // In use to disable public construction. - // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ - struct Tag + /// @brief Defines specification for making interface unique ptr. + /// + struct Spec { - explicit Tag() = default; using Interface = IMessageRxSession; using Concrete = MessageRxSession; + + // In use to disable public construction. + // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ + explicit Spec() = default; }; public: @@ -48,7 +51,7 @@ class MessageRxSession final : public IMessageRxSession, private RxSessionDelega return ArgumentError{}; } - auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Tag{}, delegate, params); + auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Spec{}, delegate, params); if (session == nullptr) { return MemoryError{}; @@ -57,7 +60,7 @@ class MessageRxSession final : public IMessageRxSession, private RxSessionDelega return session; } - MessageRxSession(Tag, TransportDelegate& delegate, const MessageRxParams& params) + MessageRxSession(Spec, TransportDelegate& delegate, const MessageRxParams& params) : delegate_{delegate} , params_{params} , subscription_{} diff --git a/include/libcyphal/transport/can/msg_tx_session.hpp b/include/libcyphal/transport/can/msg_tx_session.hpp index 7cdc95ea7..f52dd6f12 100644 --- a/include/libcyphal/transport/can/msg_tx_session.hpp +++ b/include/libcyphal/transport/can/msg_tx_session.hpp @@ -29,13 +29,16 @@ namespace detail class MessageTxSession final : public IMessageTxSession { - // In use to disable public construction. - // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ - struct Tag + /// @brief Defines specification for making interface unique ptr. + /// + struct Spec { - explicit Tag() = default; using Interface = IMessageTxSession; using Concrete = MessageTxSession; + + // In use to disable public construction. + // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ + explicit Spec() = default; }; public: @@ -47,7 +50,7 @@ class MessageTxSession final : public IMessageTxSession return ArgumentError{}; } - auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Tag{}, delegate, params); + auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Spec{}, delegate, params); if (session == nullptr) { return MemoryError{}; @@ -56,7 +59,7 @@ class MessageTxSession final : public IMessageTxSession return session; } - MessageTxSession(Tag, TransportDelegate& delegate, const MessageTxParams& params) + MessageTxSession(Spec, TransportDelegate& delegate, const MessageTxParams& params) : delegate_{delegate} , params_{params} , send_timeout_{std::chrono::seconds{1}} diff --git a/include/libcyphal/transport/can/svc_rx_sessions.hpp b/include/libcyphal/transport/can/svc_rx_sessions.hpp index 04dfbef76..0555758c5 100644 --- a/include/libcyphal/transport/can/svc_rx_sessions.hpp +++ b/include/libcyphal/transport/can/svc_rx_sessions.hpp @@ -38,13 +38,16 @@ namespace detail template class SvcRxSession final : public Interface_, private RxSessionDelegate { - // In use to disable public construction. - // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ - struct Tag + /// @brief Defines specification for making interface unique ptr. + /// + struct Spec { - explicit Tag() = default; using Interface = Interface_; using Concrete = SvcRxSession; + + // In use to disable public construction. + // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ + explicit Spec() = default; }; public: @@ -56,7 +59,7 @@ class SvcRxSession final : public Interface_, private RxSessionDelegate return ArgumentError{}; } - auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Tag{}, delegate, params); + auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Spec{}, delegate, params); if (session == nullptr) { return MemoryError{}; @@ -65,7 +68,7 @@ class SvcRxSession final : public Interface_, private RxSessionDelegate return session; } - SvcRxSession(Tag, TransportDelegate& delegate, const Params& params) + SvcRxSession(Spec, TransportDelegate& delegate, const Params& params) : delegate_{delegate} , params_{params} , subscription_{} diff --git a/include/libcyphal/transport/can/svc_tx_sessions.hpp b/include/libcyphal/transport/can/svc_tx_sessions.hpp index 7e04a0b56..9348492d0 100644 --- a/include/libcyphal/transport/can/svc_tx_sessions.hpp +++ b/include/libcyphal/transport/can/svc_tx_sessions.hpp @@ -31,13 +31,16 @@ namespace detail /// class SvcRequestTxSession final : public IRequestTxSession { - // In use to disable public construction. - // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ - struct Tag + /// @brief Defines specification for making interface unique ptr. + /// + struct Spec { - explicit Tag() = default; using Interface = IRequestTxSession; using Concrete = SvcRequestTxSession; + + // In use to disable public construction. + // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ + explicit Spec() = default; }; public: @@ -49,7 +52,7 @@ class SvcRequestTxSession final : public IRequestTxSession return ArgumentError{}; } - auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Tag{}, delegate, params); + auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Spec{}, delegate, params); if (session == nullptr) { return MemoryError{}; @@ -58,7 +61,7 @@ class SvcRequestTxSession final : public IRequestTxSession return session; } - SvcRequestTxSession(Tag, TransportDelegate& delegate, const RequestTxParams& params) + SvcRequestTxSession(Spec, TransportDelegate& delegate, const RequestTxParams& params) : delegate_{delegate} , params_{params} , send_timeout_{std::chrono::seconds{1}} @@ -123,13 +126,16 @@ class SvcRequestTxSession final : public IRequestTxSession /// class SvcResponseTxSession final : public IResponseTxSession { - // In use to disable public construction. - // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ - struct Tag + /// @brief Defines specification for making interface unique ptr. + /// + struct Spec { - explicit Tag() = default; using Interface = IResponseTxSession; using Concrete = SvcResponseTxSession; + + // In use to disable public construction. + // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ + explicit Spec() = default; }; public: @@ -141,7 +147,7 @@ class SvcResponseTxSession final : public IResponseTxSession return ArgumentError{}; } - auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Tag{}, delegate, params); + auto session = libcyphal::detail::makeUniquePtr(delegate.memory(), Spec{}, delegate, params); if (session == nullptr) { return MemoryError{}; @@ -150,7 +156,7 @@ class SvcResponseTxSession final : public IResponseTxSession return session; } - SvcResponseTxSession(Tag, TransportDelegate& delegate, const ResponseTxParams& params) + SvcResponseTxSession(Spec, TransportDelegate& delegate, const ResponseTxParams& params) : delegate_{delegate} , params_{params} , send_timeout_{std::chrono::seconds{1}} diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index 88032e226..13fa46b0f 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -44,13 +44,14 @@ class TransportImpl final : public ICanTransport, private TransportDelegate { using Interface = ICanTransport; using Concrete = TransportImpl; + + // In use to disable public construction. + // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ + explicit Spec() = default; }; /// @brief Internal (private) storage of a media index, its interface and TX queue. /// - /// B/c it's private, it also disables public construction of `TransportImpl`. - /// See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ - /// class Media final { public: @@ -103,8 +104,12 @@ class TransportImpl final : public ICanTransport, private TransportDelegate const auto canard_node_id = static_cast(local_node_id.value_or(CANARD_NODE_ID_UNSET)); - auto transport = - libcyphal::detail::makeUniquePtr(memory, memory, multiplexer, std::move(media_array), canard_node_id); + auto transport = libcyphal::detail::makeUniquePtr(memory, + Spec{}, + memory, + multiplexer, + std::move(media_array), + canard_node_id); if (transport == nullptr) { return MemoryError{}; @@ -113,7 +118,8 @@ class TransportImpl final : public ICanTransport, private TransportDelegate return transport; } - TransportImpl(cetl::pmr::memory_resource& memory, + TransportImpl(Spec, + cetl::pmr::memory_resource& memory, IMultiplexer& multiplexer, MediaArray&& media_array, const CanardNodeID canard_node_id) diff --git a/include/libcyphal/transport/udp/transport.hpp b/include/libcyphal/transport/udp/transport.hpp index 0b9078eeb..441719dc5 100644 --- a/include/libcyphal/transport/udp/transport.hpp +++ b/include/libcyphal/transport/udp/transport.hpp @@ -30,17 +30,20 @@ namespace detail class TransportImpl final : public IUdpTransport { - // In use to disable public construction. - // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ - struct Tag + /// @brief Defines specification for making interface unique ptr. + /// + struct Spec { - explicit Tag() = default; using Interface = IUdpTransport; using Concrete = TransportImpl; + + // In use to disable public construction. + // See https://seanmiddleditch.github.io/enabling-make-unique-with-private-constructors/ + explicit Spec() = default; }; public: - TransportImpl(Tag, + TransportImpl(Spec, cetl::pmr::memory_resource& memory, IMultiplexer& multiplexer, libcyphal::detail::VarArray&& media_array, diff --git a/include/libcyphal/types.hpp b/include/libcyphal/types.hpp index fae42e2d0..56bca75f7 100644 --- a/include/libcyphal/types.hpp +++ b/include/libcyphal/types.hpp @@ -65,14 +65,14 @@ using PmrAllocator = cetl::pmr::polymorphic_allocator; template using VarArray = cetl::VariableLengthArray>; -template -CETL_NODISCARD UniquePtr makeUniquePtr(cetl::pmr::memory_resource& memory, Args&&... args) +template +CETL_NODISCARD UniquePtr makeUniquePtr(cetl::pmr::memory_resource& memory, Args&&... args) { - PmrAllocator allocator{&memory}; - auto interface_deleter = typename UniquePtr::deleter_type{allocator, 1}; + PmrAllocator allocator{&memory}; + auto interface_deleter = typename UniquePtr::deleter_type{allocator, 1}; auto concrete = cetl::pmr::Factory::make_unique(allocator, std::forward(args)...); - auto interface = UniquePtr{concrete.release(), interface_deleter}; + auto interface = UniquePtr{concrete.release(), interface_deleter}; return interface; } From db0c79667f13b8639f8d8cda6bc812afa5f72df3 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Mon, 29 Apr 2024 10:02:07 +0300 Subject: [PATCH 57/64] =?UTF-8?q?Rename=20`ScatteredBuffer::Interface`=20?= =?UTF-8?q?=E2=86=92=20`Storage`.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit omit `detail` sub-namespaces from docs --- docs/doxygen.ini | 2 +- include/libcyphal/transport/can/delegate.hpp | 4 +- .../transport/can/svc_rx_sessions.hpp | 2 +- .../libcyphal/transport/scattered_buffer.hpp | 138 ++++++++++++------ include/libcyphal/transport/transport.hpp | 11 ++ include/libcyphal/transport/udp/media.hpp | 9 +- .../transport/test_scattered_buffer.cpp | 50 +++---- test/unittest/transport/udp/media_mock.hpp | 2 + 8 files changed, 147 insertions(+), 71 deletions(-) diff --git a/docs/doxygen.ini b/docs/doxygen.ini index 7530e18a9..bbca28ed5 100644 --- a/docs/doxygen.ini +++ b/docs/doxygen.ini @@ -1071,7 +1071,7 @@ EXCLUDE_PATTERNS = # wildcard * is used, a substring. Examples: ANamespace, AClass, # ANamespace::AClass, ANamespace::*Test -EXCLUDE_SYMBOLS = +EXCLUDE_SYMBOLS = *::detail::* # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp index a7ed54561..360c0cd7b 100644 --- a/include/libcyphal/transport/can/delegate.hpp +++ b/include/libcyphal/transport/can/delegate.hpp @@ -41,7 +41,7 @@ class TransportDelegate public: /// @brief RAII class to manage memory allocated by Canard library. /// - class CanardMemory final : public cetl::rtti_helper + class CanardMemory final : public cetl::rtti_helper { public: CanardMemory(TransportDelegate& delegate, void* const buffer, const std::size_t payload_size) @@ -71,7 +71,7 @@ class TransportDelegate CanardMemory& operator=(const CanardMemory&) = delete; CanardMemory& operator=(CanardMemory&&) noexcept = delete; - // MARK: ScatteredBuffer::Interface + // MARK: ScatteredBuffer::Storage CETL_NODISCARD std::size_t size() const noexcept final { diff --git a/include/libcyphal/transport/can/svc_rx_sessions.hpp b/include/libcyphal/transport/can/svc_rx_sessions.hpp index 0555758c5..40cc992f3 100644 --- a/include/libcyphal/transport/can/svc_rx_sessions.hpp +++ b/include/libcyphal/transport/can/svc_rx_sessions.hpp @@ -26,7 +26,7 @@ namespace can namespace detail { -/// @brief A template class to represent a service request/response RX session (both for server and sides). +/// @brief A template class to represent a service request/response RX session (both for server and client sides). /// /// @tparam Interface_ Type of the session interface. /// Could be either `IRequestRxSession` or `IResponseRxSession`. diff --git a/include/libcyphal/transport/scattered_buffer.hpp b/include/libcyphal/transport/scattered_buffer.hpp index 8d89a3daa..1b27e4826 100644 --- a/include/libcyphal/transport/scattered_buffer.hpp +++ b/include/libcyphal/transport/scattered_buffer.hpp @@ -15,53 +15,99 @@ namespace libcyphal namespace transport { +/// @brief Represents a buffer that could be scattered across multiple memory regions of an abstract storage. +/// /// The buffer is movable but not copyable because copying the contents of a buffer is considered wasteful. /// The buffer behaves as if it's empty if the underlying implementation is moved away. /// class ScatteredBuffer final { // 91C1B109-F90E-45BE-95CF-6ED02AC3FFAA - using InterfaceTypeIdType = cetl:: + using StorageTypeIdType = cetl:: type_id_type<0x91, 0xC1, 0xB1, 0x09, 0xF9, 0x0E, 0x45, 0xBE, 0x95, 0xCF, 0x6E, 0xD0, 0x2A, 0xC3, 0xFF, 0xAA>; public: - static constexpr std::size_t ImplementationFootprint = sizeof(void*) * 8; + /// @brief Defines maximum size (aka footprint) of the storage variant. + /// + static constexpr std::size_t StorageVariantFootprint = sizeof(void*) * 8; - class Interface : public cetl::rtti_helper + /// @brief Defines storage interface for the scattered buffer. + /// + /// @see ScatteredBuffer::ScatteredBuffer(AnyStorage&& any_storage) + /// + class Storage : public cetl::rtti_helper { public: - Interface(const Interface&) = delete; - Interface& operator=(const Interface&) = delete; - - CETL_NODISCARD virtual std::size_t size() const noexcept = 0; - CETL_NODISCARD virtual std::size_t copy(const std::size_t offset_bytes, - void* const destination, - const std::size_t length_bytes) const = 0; + // No copying, but move only! + Storage(const Storage&) = delete; + Storage& operator=(const Storage&) = delete; + + /// @brief Gets the total number of bytes stored in the buffer. + /// + /// The storage could be possibly scattered, but this is hidden from the user. + /// + virtual std::size_t size() const noexcept = 0; + + /// @brief Copies a fragment of the specified size at the specified offset out of the storage. + /// + /// The request `[offset, offset+length)` range is truncated to prevent out-of-range memory access. + /// The storage memory could be possibly scattered, but this is hidden from the user. + /// + /// @param offset_bytes The offset in bytes from the beginning of the storage. + /// @param destination The pointer to the destination buffer. Should be at least `length_bytes` long. + /// Could be `nullptr` if `length_bytes` is zero. + /// @param length_bytes The number of bytes to copy. + /// @return The number of bytes copied. + /// + virtual std::size_t copy(const std::size_t offset_bytes, + void* const destination, + const std::size_t length_bytes) const = 0; protected: - Interface() = default; - ~Interface() override = default; - Interface(Interface&&) noexcept = default; - Interface& operator=(Interface&&) noexcept = default; + Storage() = default; + ~Storage() override = default; + Storage(Storage&&) noexcept = default; + Storage& operator=(Storage&&) noexcept = default; + + }; // Storage + + /// @brief Default constructor of empty buffer with no storage attached. + /// + /// `copy()` method will do no operation, and returns zero (as `size()` does). + /// + ScatteredBuffer() + : storage_{} + { + } - }; // Interface + // No copying, but move only! + ScatteredBuffer(const ScatteredBuffer& other) = delete; + ScatteredBuffer& operator=(const ScatteredBuffer& other) = delete; - ScatteredBuffer() = default; - ScatteredBuffer(const ScatteredBuffer& other) = delete; + /// @brief Moves other buffer into this new scattered buffer instance. + /// + /// The buffer is moved by moving the internal storage variant. + /// + /// @param other The other buffer to move into. + /// ScatteredBuffer(ScatteredBuffer&& other) noexcept { - storage_ = std::move(other.storage_); + storage_variant_ = std::move(other.storage_variant_); - other.interface_ = nullptr; - interface_ = cetl::any_cast(&storage_); + other.storage_ = nullptr; + storage_ = cetl::any_cast(&storage_variant_); } - /// @brief Accepts a Lizard-specific implementation of `Interface` and moves it into the internal storage. + /// @brief Constructs buffer by accepting a Lizard-specific implementation of `Storage` + /// and moving it into the internal storage variant. + /// + /// @tparam AnyStorage The type of the storage implementation. Should fit into \ref StorageVariantFootprint. + /// @param any_storage The storage to move into the buffer. /// - template ::value>> - explicit ScatteredBuffer(T&& source) noexcept - : storage_(std::forward(source)) - , interface_{cetl::any_cast(&storage_)} + template ::value>> + explicit ScatteredBuffer(AnyStorage&& any_storage) noexcept + : storage_variant_(std::forward(any_storage)) + , storage_{cetl::any_cast(&storage_variant_)} { } @@ -70,48 +116,58 @@ class ScatteredBuffer final reset(); } - ScatteredBuffer& operator=(const ScatteredBuffer& other) = delete; + /// @brief Assigns other scattered buffer by moving it into the this one. + /// + /// @param other The other buffer to move into. + /// ScatteredBuffer& operator=(ScatteredBuffer&& other) noexcept { - storage_ = std::move(other.storage_); + storage_variant_ = std::move(other.storage_variant_); - other.interface_ = nullptr; - interface_ = cetl::any_cast(&storage_); + other.storage_ = nullptr; + storage_ = cetl::any_cast(&storage_variant_); return *this; } + /// @brief Resets the buffer by releasing its internal source. + /// + /// Has similar effect as if moved away. Has no effect if the buffer is moved away already. + /// void reset() noexcept { - storage_.reset(); - interface_ = nullptr; + storage_variant_.reset(); + storage_ = nullptr; } /// @brief Gets the number of bytes stored in the buffer (possibly scattered, but this is hidden from the user). /// /// @return Returns zero if the buffer is moved away. /// - CETL_NODISCARD std::size_t size() const noexcept + std::size_t size() const noexcept { - return interface_ ? interface_->size() : 0; + return storage_ ? storage_->size() : 0; } /// @brief Copies a fragment of the specified size at the specified offset out of the buffer. /// - /// The request is truncated to prevent out-of-range memory access. - /// Returns the number of bytes copied. + /// The request `[offset, offset+length)` range is truncated to prevent out-of-range memory access. /// Does nothing and returns zero if the instance has been moved away. /// - CETL_NODISCARD std::size_t copy(const std::size_t offset_bytes, - void* const destination, - const std::size_t length_bytes) const + /// @param offset_bytes The offset in bytes from the beginning of the buffer. + /// @param destination The pointer to the destination buffer. Should be at least `length_bytes` long. + /// Could be `nullptr` if `length_bytes` is zero. + /// @param length_bytes The number of bytes to copy. + /// @return The number of bytes copied. + /// + std::size_t copy(const std::size_t offset_bytes, void* const destination, const std::size_t length_bytes) const { - return interface_ ? interface_->copy(offset_bytes, destination, length_bytes) : 0; + return storage_ ? storage_->copy(offset_bytes, destination, length_bytes) : 0; } private: - cetl::any storage_; - const Interface* interface_ = nullptr; + cetl::any storage_variant_; + const Storage* storage_; }; // ScatteredBuffer diff --git a/include/libcyphal/transport/transport.hpp b/include/libcyphal/transport/transport.hpp index 779eaa805..b3444724a 100644 --- a/include/libcyphal/transport/transport.hpp +++ b/include/libcyphal/transport/transport.hpp @@ -15,9 +15,20 @@ namespace libcyphal namespace transport { +/// @brief Interface for a transport layer. +/// class ITransport : public IRunnable { public: + /// @brief Gets the protocol parameters. + /// + /// @return Almost the same parameters as they were passed to the corresponding transport layer factory. + /// The only difference is that the `mtu_bytes` is calculated at run-time as current maximum for + /// all media interfaces (see f.e. `can::IMedia::getMtu` method). + /// + /// @see can::IMedia::getMtu() + /// @see udp::IMedia::getMtu() + /// CETL_NODISCARD virtual ProtocolParams getProtocolParams() const noexcept = 0; /// @brief Gets the local node ID (if any). diff --git a/include/libcyphal/transport/udp/media.hpp b/include/libcyphal/transport/udp/media.hpp index f85899752..744ac36b2 100644 --- a/include/libcyphal/transport/udp/media.hpp +++ b/include/libcyphal/transport/udp/media.hpp @@ -6,6 +6,8 @@ #ifndef LIBCYPHAL_TRANSPORT_UDP_MEDIA_HPP_INCLUDED #define LIBCYPHAL_TRANSPORT_UDP_MEDIA_HPP_INCLUDED +#include "libcyphal/types.hpp" + namespace libcyphal { namespace transport @@ -25,7 +27,12 @@ class IMedia IMedia& operator=(const IMedia&) = delete; IMedia& operator=(IMedia&&) noexcept = delete; - // TODO: Add methods here + /// @brief Get the maximum transmission unit (MTU) of the UDP media. + /// + /// This value may change arbitrarily at runtime. The transport implementation will query it before every + /// transmission on the port. This value has no effect on the reception pipeline as it can accept arbitrary MTU. + /// + CETL_NODISCARD virtual std::size_t getMtu() const noexcept = 0; protected: IMedia() = default; diff --git a/test/unittest/transport/test_scattered_buffer.cpp b/test/unittest/transport/test_scattered_buffer.cpp index 10a05cd96..44c7e0f69 100644 --- a/test/unittest/transport/test_scattered_buffer.cpp +++ b/test/unittest/transport/test_scattered_buffer.cpp @@ -18,10 +18,10 @@ using testing::Return; using testing::StrictMock; // Just random id: 277C3545-564C-4617-993D-27B1043ECEBA -using TestTypeIdType = +using StorageWrapperTypeIdType = cetl::type_id_type<0x27, 0x7C, 0x35, 0x45, 0x56, 0x4C, 0x46, 0x17, 0x99, 0x3D, 0x27, 0xB1, 0x04, 0x3E, 0xCE, 0xBA>; -class InterfaceMock : public ScatteredBuffer::Interface +class StorageMock : public ScatteredBuffer::Storage { public: MOCK_METHOD(void, moved, ()); @@ -30,23 +30,23 @@ class InterfaceMock : public ScatteredBuffer::Interface MOCK_METHOD(std::size_t, size, (), (const, noexcept, override)); MOCK_METHOD(std::size_t, copy, (const std::size_t, void* const, const std::size_t), (const, override)); }; -class InterfaceWrapper final : public rtti_helper +class StorageWrapper final : public rtti_helper { public: - explicit InterfaceWrapper(InterfaceMock* mock) + explicit StorageWrapper(StorageMock* mock) : mock_{mock} { } - InterfaceWrapper(InterfaceWrapper&& other) noexcept + StorageWrapper(StorageWrapper&& other) noexcept { move_from(std::move(other)); } - InterfaceWrapper& operator=(InterfaceWrapper&& other) noexcept + StorageWrapper& operator=(StorageWrapper&& other) noexcept { move_from(std::move(other)); return *this; } - ~InterfaceWrapper() override + ~StorageWrapper() final { if (mock_ != nullptr) { @@ -55,23 +55,23 @@ class InterfaceWrapper final : public rtti_helpersize() : 0; } - CETL_NODISCARD std::size_t copy(const std::size_t offset_bytes, - void* const destination, - const std::size_t length_bytes) const override + std::size_t copy(const std::size_t offset_bytes, + void* const destination, + const std::size_t length_bytes) const final { return mock_ ? mock_->copy(offset_bytes, destination, length_bytes) : 0; } private: - InterfaceMock* mock_ = nullptr; + StorageMock* mock_ = nullptr; - void move_from(InterfaceWrapper&& other) + void move_from(StorageWrapper&& other) { mock_ = other.mock_; other.mock_ = nullptr; @@ -82,18 +82,18 @@ class InterfaceWrapper final : public rtti_helper interface_mock{}; - EXPECT_CALL(interface_mock, deinit()).Times(1); - EXPECT_CALL(interface_mock, moved()).Times(1 + 2 + 2); - EXPECT_CALL(interface_mock, size()).Times(3).WillRepeatedly(Return(42)); + StrictMock storage_mock{}; + EXPECT_CALL(storage_mock, deinit()).Times(1); + EXPECT_CALL(storage_mock, moved()).Times(1 + 2 + 2); + EXPECT_CALL(storage_mock, size()).Times(3).WillRepeatedly(Return(42)); { - ScatteredBuffer src{InterfaceWrapper{&interface_mock}}; //< +1 move + ScatteredBuffer src{StorageWrapper{&storage_mock}}; //< +1 move EXPECT_THAT(src.size(), 42); ScatteredBuffer dst{std::move(src)}; //< +2 moves b/c of `cetl::any` specifics (via swap with tmp) @@ -110,12 +110,12 @@ TEST(TestScatteredBuffer, copy_reset) { std::array test_dst{}; - StrictMock interface_mock{}; - EXPECT_CALL(interface_mock, deinit()).Times(1); - EXPECT_CALL(interface_mock, moved()).Times(1); - EXPECT_CALL(interface_mock, copy(13, test_dst.data(), test_dst.size())).WillOnce(Return(7)); + StrictMock storage_mock{}; + EXPECT_CALL(storage_mock, deinit()).Times(1); + EXPECT_CALL(storage_mock, moved()).Times(1); + EXPECT_CALL(storage_mock, copy(13, test_dst.data(), test_dst.size())).WillOnce(Return(7)); { - ScatteredBuffer buffer{InterfaceWrapper{&interface_mock}}; + ScatteredBuffer buffer{StorageWrapper{&storage_mock}}; auto copied_bytes = buffer.copy(13, test_dst.data(), test_dst.size()); EXPECT_THAT(copied_bytes, 7); diff --git a/test/unittest/transport/udp/media_mock.hpp b/test/unittest/transport/udp/media_mock.hpp index 2a981463e..686ce8b68 100644 --- a/test/unittest/transport/udp/media_mock.hpp +++ b/test/unittest/transport/udp/media_mock.hpp @@ -23,6 +23,8 @@ class MediaMock : public IMedia MediaMock() = default; virtual ~MediaMock() = default; + MOCK_METHOD(std::size_t, getMtu, (), (const, noexcept, override)); + }; // MediaMock } // namespace udp From 6065693038b0fc5a5a7aab9830d4dbd8e9527f85 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Mon, 29 Apr 2024 16:37:11 +0300 Subject: [PATCH 58/64] switch to the latest `cetl::unbounded_variant` #verification --- cmake/modules/Findcetl.cmake | 2 +- include/libcyphal/transport/scattered_buffer.hpp | 12 ++++++------ include/libcyphal/transport/session.hpp | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cmake/modules/Findcetl.cmake b/cmake/modules/Findcetl.cmake index 94659983e..a2cc329cc 100644 --- a/cmake/modules/Findcetl.cmake +++ b/cmake/modules/Findcetl.cmake @@ -6,7 +6,7 @@ include(FetchContent) set(cetl_GIT_REPOSITORY "https://github.com/OpenCyphal/cetl.git") -set(cetl_GIT_TAG "886a0d227a043511eed6b252ea0f788590c50e75") +set(cetl_GIT_TAG "2dd01e0af6a2dcff01c1cdab35b6747e0aa8b3b3") FetchContent_Declare( cetl diff --git a/include/libcyphal/transport/scattered_buffer.hpp b/include/libcyphal/transport/scattered_buffer.hpp index 8d89a3daa..10324c187 100644 --- a/include/libcyphal/transport/scattered_buffer.hpp +++ b/include/libcyphal/transport/scattered_buffer.hpp @@ -6,7 +6,7 @@ #ifndef LIBCYPHAL_TRANSPORT_SCATTERED_BUFFER_HPP_INCLUDED #define LIBCYPHAL_TRANSPORT_SCATTERED_BUFFER_HPP_INCLUDED -#include +#include #include @@ -53,7 +53,7 @@ class ScatteredBuffer final storage_ = std::move(other.storage_); other.interface_ = nullptr; - interface_ = cetl::any_cast(&storage_); + interface_ = cetl::get_if(&storage_); } /// @brief Accepts a Lizard-specific implementation of `Interface` and moves it into the internal storage. @@ -61,7 +61,7 @@ class ScatteredBuffer final template ::value>> explicit ScatteredBuffer(T&& source) noexcept : storage_(std::forward(source)) - , interface_{cetl::any_cast(&storage_)} + , interface_{cetl::get_if(&storage_)} { } @@ -76,7 +76,7 @@ class ScatteredBuffer final storage_ = std::move(other.storage_); other.interface_ = nullptr; - interface_ = cetl::any_cast(&storage_); + interface_ = cetl::get_if(&storage_); return *this; } @@ -110,8 +110,8 @@ class ScatteredBuffer final } private: - cetl::any storage_; - const Interface* interface_ = nullptr; + cetl::unbounded_variant storage_; + const Interface* interface_ = nullptr; }; // ScatteredBuffer diff --git a/include/libcyphal/transport/session.hpp b/include/libcyphal/transport/session.hpp index 7a94d4bfb..3cf618cae 100644 --- a/include/libcyphal/transport/session.hpp +++ b/include/libcyphal/transport/session.hpp @@ -31,7 +31,7 @@ class ITxSession : public ISession /// @brief Sets the timeout for a transmission. /// /// The value is added to the original transfer timestamp to determine its deadline. - /// Any transfer that exceeded this deadline would be dropped by the transport. + /// Any transfer that exceeded this deadline would be dropped. /// /// @param timeout - Positive duration for transmission timeout. Default value is 1 second. /// From e366c98e5bc28032541b810c15dafb675309ce03 Mon Sep 17 00:00:00 2001 From: Sergei Date: Tue, 30 Apr 2024 08:16:50 +0300 Subject: [PATCH 59/64] docs improvements --- include/libcyphal/transport/can/delegate.hpp | 6 +++--- include/libcyphal/transport/session.hpp | 2 +- include/libcyphal/transport/types.hpp | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp index a7ed54561..6db632e51 100644 --- a/include/libcyphal/transport/can/delegate.hpp +++ b/include/libcyphal/transport/can/delegate.hpp @@ -30,7 +30,7 @@ namespace detail /// This internal transport delegate class serves the following purposes: /// 1. It provides memory management functions for the Canard library. /// 2. It provides a way to convert Canard error codes to `AnyError` type. -/// 3. It provides interface to access the transport from various session classes. +/// 3. It provides an interface to access the transport from various session classes. /// class TransportDelegate { @@ -232,8 +232,8 @@ class TransportDelegate // MARK: - -/// This internal session delegate class serves the following purpose: it provides interface -/// to access a session from transport (by casting canard's `user_reference` member to this class). +/// This internal session delegate class serves the following purpose: it provides an interface (aka gateway) +/// to access RX session from transport (by casting canard's `user_reference` member to this class). /// class RxSessionDelegate { diff --git a/include/libcyphal/transport/session.hpp b/include/libcyphal/transport/session.hpp index 7a94d4bfb..3cf618cae 100644 --- a/include/libcyphal/transport/session.hpp +++ b/include/libcyphal/transport/session.hpp @@ -31,7 +31,7 @@ class ITxSession : public ISession /// @brief Sets the timeout for a transmission. /// /// The value is added to the original transfer timestamp to determine its deadline. - /// Any transfer that exceeded this deadline would be dropped by the transport. + /// Any transfer that exceeded this deadline would be dropped. /// /// @param timeout - Positive duration for transmission timeout. Default value is 1 second. /// diff --git a/include/libcyphal/transport/types.hpp b/include/libcyphal/transport/types.hpp index df7dce099..62a414834 100644 --- a/include/libcyphal/transport/types.hpp +++ b/include/libcyphal/transport/types.hpp @@ -20,12 +20,12 @@ namespace transport /// using NodeId = std::uint16_t; -/// @brief `PortId` is a 16-bit unsigned integer that represents a port (subject & service) in a Cyphal network. +/// @brief `PortId` is a 16-bit unsigned integer that represents a port (subject or service) in a Cyphal network. /// using PortId = std::uint16_t; -/// @brief `TransferId` is a 64-bit unsigned integer that represents a service transfer (request & response) -/// in a Cyphal network. +/// @brief TransferId is a 64-bit unsigned integer that represents a message +/// or service transfer (request & response) in a Cyphal network. /// using TransferId = std::uint64_t; From ffa98c5f44c32019b7e4224e2ebf93f98b35b055 Mon Sep 17 00:00:00 2001 From: Sergei Date: Tue, 30 Apr 2024 08:41:32 +0300 Subject: [PATCH 60/64] added `.clang-tidy` file #verification --- .clang-tidy | 31 +++++++++++++++++++++++++++++++ CMakeLists.txt | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 .clang-tidy diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 000000000..51113017d --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,31 @@ +Checks: >- + boost-*, + bugprone-*, + cert-*, + clang-analyzer-*, + cppcoreguidelines-*, + google-*, + hicpp-*, + llvm-*, + misc-*, + modernize-*, + performance-*, + portability-*, + readability-*, + -cppcoreguidelines-avoid-const-or-ref-data-members, + -google-readability-todo, + -readability-avoid-const-params-in-decls, + -readability-identifier-length, + -llvm-header-guard, + -llvm-include-order, + -*-use-trailing-return-type, + -*-named-parameter, +CheckOptions: + - key: readability-function-cognitive-complexity.Threshold + value: '90' + - key: readability-magic-numbers.IgnoredIntegerValues + value: '1;2;3;4;5;8;10;16;20;32;60;64;100;128;256;500;512;1000' +WarningsAsErrors: '*' +HeaderFilterRegex: '.*\.hpp' +AnalyzeTemporaryDtors: false +FormatStyle: file diff --git a/CMakeLists.txt b/CMakeLists.txt index 8cace2941..920c084cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,7 +62,7 @@ if (${CMAKE_CXX_PLATFORM_ID} STREQUAL "Linux") endif() # Don't normalize deviance: if CMAKE_TOOLCHAIN_FILE is not set then provide -# an initalized default to display in the status thus avoiding a warning about +# an initialized default to display in the status thus avoiding a warning about # using an uninitialized variable. if (NOT CMAKE_TOOLCHAIN_FILE) set(LOCAL_CMAKE_TOOLCHAIN_FILE "(not set)") From 377f0e09d9dd9ebe3b0437edf15e5436e9bbaec1 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Tue, 30 Apr 2024 11:53:31 +0300 Subject: [PATCH 61/64] =?UTF-8?q?RxSessionDelegate=20=E2=86=92=20IRxSessio?= =?UTF-8?q?nDelegate;=20Storage=20=E2=86=92=20IStorage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/libcyphal/transport/can/delegate.hpp | 18 ++++---- .../transport/can/msg_rx_session.hpp | 6 +-- .../transport/can/svc_rx_sessions.hpp | 6 +-- include/libcyphal/transport/can/transport.hpp | 2 +- .../libcyphal/transport/scattered_buffer.hpp | 44 ++++++++++--------- .../transport/test_scattered_buffer.cpp | 4 +- 6 files changed, 41 insertions(+), 39 deletions(-) diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp index 259c93021..75c531da4 100644 --- a/include/libcyphal/transport/can/delegate.hpp +++ b/include/libcyphal/transport/can/delegate.hpp @@ -41,7 +41,7 @@ class TransportDelegate public: /// @brief RAII class to manage memory allocated by Canard library. /// - class CanardMemory final : public cetl::rtti_helper + class CanardMemory final : public cetl::rtti_helper { public: CanardMemory(TransportDelegate& delegate, void* const buffer, const std::size_t payload_size) @@ -235,23 +235,23 @@ class TransportDelegate /// This internal session delegate class serves the following purpose: it provides an interface (aka gateway) /// to access RX session from transport (by casting canard's `user_reference` member to this class). /// -class RxSessionDelegate +class IRxSessionDelegate { public: - RxSessionDelegate(const RxSessionDelegate&) = delete; - RxSessionDelegate(RxSessionDelegate&&) noexcept = delete; - RxSessionDelegate& operator=(const RxSessionDelegate&) = delete; - RxSessionDelegate& operator=(RxSessionDelegate&&) noexcept = delete; + IRxSessionDelegate(const IRxSessionDelegate&) = delete; + IRxSessionDelegate(IRxSessionDelegate&&) noexcept = delete; + IRxSessionDelegate& operator=(const IRxSessionDelegate&) = delete; + IRxSessionDelegate& operator=(IRxSessionDelegate&&) noexcept = delete; /// @brief Accepts a received transfer from the transport dedicated to this RX session. /// virtual void acceptRxTransfer(const CanardRxTransfer& transfer) = 0; protected: - RxSessionDelegate() = default; - ~RxSessionDelegate() = default; + IRxSessionDelegate() = default; + ~IRxSessionDelegate() = default; -}; // RxSessionDelegate +}; // IRxSessionDelegate } // namespace detail } // namespace can diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp index daebfec1e..69574759f 100644 --- a/include/libcyphal/transport/can/msg_rx_session.hpp +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -28,7 +28,7 @@ namespace detail /// @brief A class to represent a message subscriber RX session. /// -class MessageRxSession final : public IMessageRxSession, private RxSessionDelegate +class MessageRxSession final : public IMessageRxSession, private IRxSessionDelegate { /// @brief Defines specification for making interface unique ptr. /// @@ -76,7 +76,7 @@ class MessageRxSession final : public IMessageRxSession, private RxSessionDelega CETL_DEBUG_ASSERT(result >= 0, "There is no way currently to get an error here."); CETL_DEBUG_ASSERT(result > 0, "New subscription supposed to be made."); - subscription_.user_reference = static_cast(this); + subscription_.user_reference = static_cast(this); } ~MessageRxSession() final @@ -119,7 +119,7 @@ class MessageRxSession final : public IMessageRxSession, private RxSessionDelega // Nothing to do here currently. } - // MARK: RxSessionDelegate + // MARK: IRxSessionDelegate void acceptRxTransfer(const CanardRxTransfer& transfer) final { diff --git a/include/libcyphal/transport/can/svc_rx_sessions.hpp b/include/libcyphal/transport/can/svc_rx_sessions.hpp index 40cc992f3..8abb3be6c 100644 --- a/include/libcyphal/transport/can/svc_rx_sessions.hpp +++ b/include/libcyphal/transport/can/svc_rx_sessions.hpp @@ -36,7 +36,7 @@ namespace detail /// Could be either `CanardTransferKindRequest` or `CanardTransferKindResponse`. /// template -class SvcRxSession final : public Interface_, private RxSessionDelegate +class SvcRxSession final : public Interface_, private IRxSessionDelegate { /// @brief Defines specification for making interface unique ptr. /// @@ -84,7 +84,7 @@ class SvcRxSession final : public Interface_, private RxSessionDelegate CETL_DEBUG_ASSERT(result >= 0, "There is no way currently to get an error here."); CETL_DEBUG_ASSERT(result > 0, "New subscription supposed to be made."); - subscription_.user_reference = static_cast(this); + subscription_.user_reference = static_cast(this); } ~SvcRxSession() final @@ -127,7 +127,7 @@ class SvcRxSession final : public Interface_, private RxSessionDelegate // Nothing to do here currently. } - // MARK: RxSessionDelegate + // MARK: IRxSessionDelegate void acceptRxTransfer(const CanardRxTransfer& transfer) final { diff --git a/include/libcyphal/transport/can/transport.hpp b/include/libcyphal/transport/can/transport.hpp index 13fa46b0f..1804f290f 100644 --- a/include/libcyphal/transport/can/transport.hpp +++ b/include/libcyphal/transport/can/transport.hpp @@ -391,7 +391,7 @@ class TransportImpl final : public ICanTransport, private TransportDelegate CETL_DEBUG_ASSERT(out_subscription != nullptr, "Expected subscription."); CETL_DEBUG_ASSERT(out_subscription->user_reference != nullptr, "Expected session delegate."); - const auto delegate = static_cast(out_subscription->user_reference); + const auto delegate = static_cast(out_subscription->user_reference); delegate->acceptRxTransfer(out_transfer); } } diff --git a/include/libcyphal/transport/scattered_buffer.hpp b/include/libcyphal/transport/scattered_buffer.hpp index 9cd9fddeb..6c2772e91 100644 --- a/include/libcyphal/transport/scattered_buffer.hpp +++ b/include/libcyphal/transport/scattered_buffer.hpp @@ -23,7 +23,7 @@ namespace transport class ScatteredBuffer final { // 91C1B109-F90E-45BE-95CF-6ED02AC3FFAA - using StorageTypeIdType = cetl:: + using IStorageTypeIdType = cetl:: type_id_type<0x91, 0xC1, 0xB1, 0x09, 0xF9, 0x0E, 0x45, 0xBE, 0x95, 0xCF, 0x6E, 0xD0, 0x2A, 0xC3, 0xFF, 0xAA>; public: @@ -35,18 +35,18 @@ class ScatteredBuffer final /// /// @see ScatteredBuffer::ScatteredBuffer(AnyStorage&& any_storage) /// - class Storage : public cetl::rtti_helper + class IStorage : public cetl::rtti_helper { public: // No copying, but move only! - Storage(const Storage&) = delete; - Storage& operator=(const Storage&) = delete; + IStorage(const IStorage&) = delete; + IStorage& operator=(const IStorage&) = delete; /// @brief Gets the total number of bytes stored in the buffer. /// /// The storage could be possibly scattered, but this is hidden from the user. /// - virtual std::size_t size() const noexcept = 0; + CETL_NODISCARD virtual std::size_t size() const noexcept = 0; /// @brief Copies a fragment of the specified size at the specified offset out of the storage. /// @@ -59,17 +59,17 @@ class ScatteredBuffer final /// @param length_bytes The number of bytes to copy. /// @return The number of bytes copied. /// - virtual std::size_t copy(const std::size_t offset_bytes, - void* const destination, - const std::size_t length_bytes) const = 0; + CETL_NODISCARD virtual std::size_t copy(const std::size_t offset_bytes, + void* const destination, + const std::size_t length_bytes) const = 0; protected: - Storage() = default; - ~Storage() override = default; - Storage(Storage&&) noexcept = default; - Storage& operator=(Storage&&) noexcept = default; + IStorage() = default; + ~IStorage() override = default; + IStorage(IStorage&&) noexcept = default; + IStorage& operator=(IStorage&&) noexcept = default; - }; // Storage + }; // IStorage /// @brief Default constructor of empty buffer with no storage attached. /// @@ -95,19 +95,19 @@ class ScatteredBuffer final storage_variant_ = std::move(other.storage_variant_); other.storage_ = nullptr; - storage_ = cetl::get_if(&storage_variant_); + storage_ = cetl::get_if(&storage_variant_); } - /// @brief Constructs buffer by accepting a Lizard-specific implementation of `Storage` + /// @brief Constructs buffer by accepting a Lizard-specific implementation of `IStorage` /// and moving it into the internal storage variant. /// /// @tparam AnyStorage The type of the storage implementation. Should fit into \ref StorageVariantFootprint. /// @param any_storage The storage to move into the buffer. /// - template ::value>> + template ::value>> explicit ScatteredBuffer(AnyStorage&& any_storage) noexcept : storage_variant_(std::forward(any_storage)) - , storage_{cetl::get_if(&storage_variant_)} + , storage_{cetl::get_if(&storage_variant_)} { } @@ -125,7 +125,7 @@ class ScatteredBuffer final storage_variant_ = std::move(other.storage_variant_); other.storage_ = nullptr; - storage_ = cetl::get_if(&storage_variant_); + storage_ = cetl::get_if(&storage_variant_); return *this; } @@ -144,7 +144,7 @@ class ScatteredBuffer final /// /// @return Returns zero if the buffer is moved away. /// - std::size_t size() const noexcept + CETL_NODISCARD std::size_t size() const noexcept { return storage_ ? storage_->size() : 0; } @@ -160,14 +160,16 @@ class ScatteredBuffer final /// @param length_bytes The number of bytes to copy. /// @return The number of bytes copied. /// - std::size_t copy(const std::size_t offset_bytes, void* const destination, const std::size_t length_bytes) const + CETL_NODISCARD std::size_t copy(const std::size_t offset_bytes, + void* const destination, + const std::size_t length_bytes) const { return storage_ ? storage_->copy(offset_bytes, destination, length_bytes) : 0; } private: cetl::unbounded_variant storage_variant_; - const Storage* storage_; + const IStorage* storage_; }; // ScatteredBuffer diff --git a/test/unittest/transport/test_scattered_buffer.cpp b/test/unittest/transport/test_scattered_buffer.cpp index 44c7e0f69..3e289195b 100644 --- a/test/unittest/transport/test_scattered_buffer.cpp +++ b/test/unittest/transport/test_scattered_buffer.cpp @@ -21,7 +21,7 @@ using testing::StrictMock; using StorageWrapperTypeIdType = cetl::type_id_type<0x27, 0x7C, 0x35, 0x45, 0x56, 0x4C, 0x46, 0x17, 0x99, 0x3D, 0x27, 0xB1, 0x04, 0x3E, 0xCE, 0xBA>; -class StorageMock : public ScatteredBuffer::Storage +class StorageMock : public ScatteredBuffer::IStorage { public: MOCK_METHOD(void, moved, ()); @@ -30,7 +30,7 @@ class StorageMock : public ScatteredBuffer::Storage MOCK_METHOD(std::size_t, size, (), (const, noexcept, override)); MOCK_METHOD(std::size_t, copy, (const std::size_t, void* const, const std::size_t), (const, override)); }; -class StorageWrapper final : public rtti_helper +class StorageWrapper final : public rtti_helper { public: explicit StorageWrapper(StorageMock* mock) From a4c8ff115430f55f9e3c187888c43767f8e65a5d Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Wed, 1 May 2024 15:47:35 +0300 Subject: [PATCH 62/64] switch to CETL's `main` branch commit #verification --- cmake/modules/Findcetl.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/modules/Findcetl.cmake b/cmake/modules/Findcetl.cmake index a2cc329cc..383261d8e 100644 --- a/cmake/modules/Findcetl.cmake +++ b/cmake/modules/Findcetl.cmake @@ -6,7 +6,7 @@ include(FetchContent) set(cetl_GIT_REPOSITORY "https://github.com/OpenCyphal/cetl.git") -set(cetl_GIT_TAG "2dd01e0af6a2dcff01c1cdab35b6747e0aa8b3b3") +set(cetl_GIT_TAG "10fbb2b7b89473d68e73db7235848b0692169e5a") FetchContent_Declare( cetl From 975e6a7f8cee20588cf590c4c22238e958d46b8f Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 2 May 2024 09:59:09 +0300 Subject: [PATCH 63/64] minor change --- include/libcyphal/transport/can/delegate.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libcyphal/transport/can/delegate.hpp b/include/libcyphal/transport/can/delegate.hpp index 75c531da4..69f162a2f 100644 --- a/include/libcyphal/transport/can/delegate.hpp +++ b/include/libcyphal/transport/can/delegate.hpp @@ -71,7 +71,7 @@ class TransportDelegate CanardMemory& operator=(const CanardMemory&) = delete; CanardMemory& operator=(CanardMemory&&) noexcept = delete; - // MARK: ScatteredBuffer::Storage + // MARK: ScatteredBuffer::IStorage CETL_NODISCARD std::size_t size() const noexcept final { From 2b06c32f93d60ab73645ecf0357681b2c14d87e7 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Fri, 3 May 2024 19:55:18 +0300 Subject: [PATCH 64/64] PR fixes --- include/libcyphal/transport/can/media.hpp | 2 +- include/libcyphal/transport/can/msg_rx_session.hpp | 9 ++++++--- include/libcyphal/transport/can/svc_rx_sessions.hpp | 9 ++++++--- include/libcyphal/transport/udp/media.hpp | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/include/libcyphal/transport/can/media.hpp b/include/libcyphal/transport/can/media.hpp index 94a2f1c0b..f092c72cc 100644 --- a/include/libcyphal/transport/can/media.hpp +++ b/include/libcyphal/transport/can/media.hpp @@ -50,7 +50,7 @@ class IMedia /// This value may change arbitrarily at runtime. The transport implementation will query it before every /// transmission on the port. This value has no effect on the reception pipeline as it can accept arbitrary MTU. /// - CETL_NODISCARD virtual std::size_t getMtu() const noexcept = 0; + virtual std::size_t getMtu() const noexcept = 0; /// @brief Set the filters for the CAN bus. /// diff --git a/include/libcyphal/transport/can/msg_rx_session.hpp b/include/libcyphal/transport/can/msg_rx_session.hpp index 69574759f..294339d5e 100644 --- a/include/libcyphal/transport/can/msg_rx_session.hpp +++ b/include/libcyphal/transport/can/msg_rx_session.hpp @@ -81,9 +81,12 @@ class MessageRxSession final : public IMessageRxSession, private IRxSessionDeleg ~MessageRxSession() final { - ::canardRxUnsubscribe(&delegate_.canard_instance(), - CanardTransferKindMessage, - static_cast(params_.subject_id)); + const int8_t result = ::canardRxUnsubscribe(&delegate_.canard_instance(), + CanardTransferKindMessage, + static_cast(params_.subject_id)); + (void) result; + CETL_DEBUG_ASSERT(result >= 0, "There is no way currently to get an error here."); + CETL_DEBUG_ASSERT(result > 0, "Subscription supposed to be made at constructor."); } private: diff --git a/include/libcyphal/transport/can/svc_rx_sessions.hpp b/include/libcyphal/transport/can/svc_rx_sessions.hpp index 8abb3be6c..d289f46d7 100644 --- a/include/libcyphal/transport/can/svc_rx_sessions.hpp +++ b/include/libcyphal/transport/can/svc_rx_sessions.hpp @@ -89,9 +89,12 @@ class SvcRxSession final : public Interface_, private IRxSessionDelegate ~SvcRxSession() final { - ::canardRxUnsubscribe(&delegate_.canard_instance(), - TransferKind, - static_cast(params_.service_id)); + const int8_t result = ::canardRxUnsubscribe(&delegate_.canard_instance(), + TransferKind, + static_cast(params_.service_id)); + (void) result; + CETL_DEBUG_ASSERT(result >= 0, "There is no way currently to get an error here."); + CETL_DEBUG_ASSERT(result > 0, "Subscription supposed to be made at constructor."); } private: diff --git a/include/libcyphal/transport/udp/media.hpp b/include/libcyphal/transport/udp/media.hpp index 744ac36b2..9f890517d 100644 --- a/include/libcyphal/transport/udp/media.hpp +++ b/include/libcyphal/transport/udp/media.hpp @@ -32,7 +32,7 @@ class IMedia /// This value may change arbitrarily at runtime. The transport implementation will query it before every /// transmission on the port. This value has no effect on the reception pipeline as it can accept arbitrary MTU. /// - CETL_NODISCARD virtual std::size_t getMtu() const noexcept = 0; + virtual std::size_t getMtu() const noexcept = 0; protected: IMedia() = default;