Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions include/libcyphal/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
#ifndef LIBCYPHAL_CONFIG_HPP_INCLUDED
#define LIBCYPHAL_CONFIG_HPP_INCLUDED

#include <chrono>
#include <cstddef>
#include <cstdint>

namespace libcyphal
{
Expand All @@ -24,6 +26,21 @@ namespace libcyphal
///
struct Config
{
/// Defines time representation as 64-bit microseconds.
///
/// This is in line with the lizards that use `uint64_t`-typed microsecond counters throughout.
///
struct MonotonicClock final
{
using rep = std::int64_t;
using period = std::micro;
using duration = std::chrono::duration<rep, period>;
using time_point = std::chrono::time_point<MonotonicClock>;

static constexpr bool is_steady = true;

}; // MonotonicClock

/// Defines max footprint of a callback function in use by the executor.
///
static constexpr std::size_t IExecutor_Callback_FunctionMaxSize() // NOSONAR cpp:S799
Expand Down
17 changes: 10 additions & 7 deletions include/libcyphal/transport/can/can_transport_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -578,8 +578,8 @@ class TransportImpl final : private TransportDelegate, public ICanTransport
}
const IMedia::PopResult::Metadata& pop_meta = *pop_success;

const auto timestamp_us =
std::chrono::duration_cast<std::chrono::microseconds>(pop_meta.timestamp.time_since_epoch());
const auto timestamp_us = std::chrono::duration_cast<std::chrono::microseconds>( //
pop_meta.timestamp.time_since_epoch());
const CanardFrame canard_frame{pop_meta.can_id, {pop_meta.payload_size, payload.data()}};

CanardRxTransfer out_transfer{};
Expand All @@ -605,7 +605,8 @@ class TransportImpl final : private TransportDelegate, public ICanTransport

const auto transfer_id = static_cast<TransferId>(out_transfer.metadata.transfer_id);
const auto priority = static_cast<Priority>(out_transfer.metadata.priority);
const auto timestamp = TimePoint{std::chrono::microseconds{out_transfer.timestamp_usec}};
const auto timestamp = TimePoint{std::chrono::duration_cast<Duration>( //
std::chrono::microseconds{out_transfer.timestamp_usec})};

session_delegate->acceptRxTransfer(CanardMemory{memory(), out_transfer.payload},
TransferRxMetadata{{transfer_id, priority}, timestamp},
Expand All @@ -625,9 +626,10 @@ class TransportImpl final : private TransportDelegate, public ICanTransport
&media.interface().getTxMemoryResource()};
frame.payload = {0, nullptr, 0};

auto push_result = media.interface().push(TimePoint{std::chrono::microseconds{deadline}}, //
frame.extended_can_id,
payload);
auto push_result = media.interface().push( //
TimePoint{std::chrono::duration_cast<Duration>(std::chrono::microseconds{deadline})},
frame.extended_can_id,
payload);

if (const auto* const push = cetl::get_if<IMedia::PushResult::Success>(&push_result))
{
Expand Down Expand Up @@ -716,7 +718,8 @@ class TransportImpl final : private TransportDelegate, public ICanTransport
// Otherwise, we would push it to the media interface.
// We use strictly `<` (instead of `<=`) to give this frame a chance (one extra 1us) at the media.
//
const auto deadline = TimePoint{std::chrono::microseconds{tx_item->tx_deadline_usec}};
const auto deadline = TimePoint{std::chrono::duration_cast<Duration>( //
std::chrono::microseconds{tx_item->tx_deadline_usec})};
if (now < deadline)
{
out_deadline = deadline;
Expand Down
4 changes: 2 additions & 2 deletions include/libcyphal/transport/udp/msg_tx_session.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ class MessageTxSession final : public IMessageTxSession
CETL_NODISCARD cetl::optional<AnyFailure> send(const TransferTxMetadata& metadata,
const PayloadFragments payload_fragments) override
{
const auto deadline_us =
std::chrono::duration_cast<std::chrono::microseconds>(metadata.deadline.time_since_epoch());
const auto deadline_us = std::chrono::duration_cast<std::chrono::microseconds>( //
metadata.deadline.time_since_epoch());

const auto tx_metadata = AnyUdpardTxMetadata::Publish{static_cast<UdpardMicrosecond>(deadline_us.count()),
static_cast<UdpardPriority>(metadata.base.priority),
Expand Down
8 changes: 4 additions & 4 deletions include/libcyphal/transport/udp/svc_tx_sessions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ class SvcRequestTxSession final : public IRequestTxSession
return ArgumentError{};
}

const auto deadline_us =
std::chrono::duration_cast<std::chrono::microseconds>(metadata.deadline.time_since_epoch());
const auto deadline_us = std::chrono::duration_cast<std::chrono::microseconds>( //
metadata.deadline.time_since_epoch());

const auto tx_metadata = AnyUdpardTxMetadata::Request{static_cast<UdpardMicrosecond>(deadline_us.count()),
static_cast<UdpardPriority>(metadata.base.priority),
Expand Down Expand Up @@ -171,8 +171,8 @@ class SvcResponseTxSession final : public IResponseTxSession
return ArgumentError{};
}

const auto deadline_us =
std::chrono::duration_cast<std::chrono::microseconds>(metadata.tx_meta.deadline.time_since_epoch());
const auto deadline_us = std::chrono::duration_cast<std::chrono::microseconds>( //
metadata.tx_meta.deadline.time_since_epoch());

const auto tx_metadata =
AnyUdpardTxMetadata::Respond{static_cast<UdpardMicrosecond>(deadline_us.count()),
Expand Down
17 changes: 10 additions & 7 deletions include/libcyphal/transport/udp/udp_transport_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,8 @@ class TransportImpl final : private TransportDelegate, public IUdpTransport
// Otherwise, we would send it to the media TX socket interface.
// We use strictly `<` (instead of `<=`) to give this frame a chance (one extra 1us) at the socket.
//
const auto deadline = TimePoint{std::chrono::microseconds{tx_item->deadline_usec}};
const auto deadline = TimePoint{std::chrono::duration_cast<Duration>( //
std::chrono::microseconds{tx_item->deadline_usec})};
if (now < deadline)
{
out_deadline = deadline;
Expand Down Expand Up @@ -919,8 +920,8 @@ class TransportImpl final : private TransportDelegate, public IUdpTransport

// 2. We've got a new frame from the media RX socket, so let's try to pass it into libudpard RPC dispatcher.

const auto timestamp_us =
std::chrono::duration_cast<std::chrono::microseconds>(rx_meta.timestamp.time_since_epoch());
const auto timestamp_us = std::chrono::duration_cast<std::chrono::microseconds>( //
rx_meta.timestamp.time_since_epoch());

const auto payload_deleter = rx_meta.payload_ptr.get_deleter();

Expand Down Expand Up @@ -961,7 +962,8 @@ class TransportImpl final : private TransportDelegate, public IUdpTransport

const auto transfer_id = out_transfer.base.transfer_id;
const auto priority = static_cast<Priority>(out_transfer.base.priority);
const auto timestamp = TimePoint{std::chrono::microseconds{out_transfer.base.timestamp_usec}};
const auto timestamp = TimePoint{std::chrono::duration_cast<Duration>( //
std::chrono::microseconds{out_transfer.base.timestamp_usec})};

session_delegate->acceptRxTransfer(UdpardMemory{memoryResources(), out_transfer.base},
TransferRxMetadata{{transfer_id, priority}, timestamp},
Expand All @@ -985,8 +987,8 @@ class TransportImpl final : private TransportDelegate, public IUdpTransport

// 2. We've got a new frame from the media RX socket, so let's try to pass it into libudpard subscription.

const auto timestamp_us =
std::chrono::duration_cast<std::chrono::microseconds>(rx_meta.timestamp.time_since_epoch());
const auto timestamp_us = std::chrono::duration_cast<std::chrono::microseconds>( //
rx_meta.timestamp.time_since_epoch());

const auto payload_deleter = rx_meta.payload_ptr.get_deleter();

Expand Down Expand Up @@ -1017,7 +1019,8 @@ class TransportImpl final : private TransportDelegate, public IUdpTransport
{
const auto transfer_id = out_transfer.transfer_id;
const auto priority = static_cast<Priority>(out_transfer.priority);
const auto timestamp = TimePoint{std::chrono::microseconds{out_transfer.timestamp_usec}};
const auto timestamp = TimePoint{std::chrono::duration_cast<Duration>( //
std::chrono::microseconds{out_transfer.timestamp_usec})};

session_delegate.acceptRxTransfer(UdpardMemory{memoryResources(), out_transfer},
TransferRxMetadata{{transfer_id, priority}, timestamp},
Expand Down
21 changes: 4 additions & 17 deletions include/libcyphal/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#ifndef LIBCYPHAL_TYPES_HPP_INCLUDED
#define LIBCYPHAL_TYPES_HPP_INCLUDED

#include "libcyphal/config.hpp"

#include <cetl/cetl.hpp>
#include <cetl/pf17/cetlpf.hpp>
#include <cetl/pmr/interface_ptr.hpp>
Expand All @@ -23,23 +25,8 @@
namespace libcyphal
{

/// @brief The internal time representation is in microseconds.
///
/// This is in line with the lizards that use `uint64_t`-typed microsecond counters throughout.
///
struct MonotonicClock final
{
using rep = std::int64_t;
using period = std::micro;
using duration = std::chrono::duration<rep, period>;
using time_point = std::chrono::time_point<MonotonicClock>;

static constexpr bool is_steady = true;

}; // MonotonicClock

using TimePoint = MonotonicClock::time_point;
using Duration = MonotonicClock::duration;
using TimePoint = config::MonotonicClock::time_point;
using Duration = config::MonotonicClock::duration;

template <typename T>
using UniquePtr = cetl::pmr::InterfacePtr<T>;
Expand Down
17 changes: 17 additions & 0 deletions test/unittest/custom_libcyphal_config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ namespace custom

struct MyConfig : libcyphal::Config
{
/// Redefines time representation as 32-bit milliseconds.
///
/// Milliseconds are chosen b/c there is no implicit conversion from native lizard's microseconds
/// to lower precision units like milliseconds, so proper explicit `std::chrono::duration_cast` is needed.
/// For details also see https://github.com/OpenCyphal-Garage/libcyphal/issues/431.
///
struct MonotonicClock final
{
using rep = std::int32_t;
using period = std::milli;
using duration = std::chrono::duration<rep, period>;
using time_point = std::chrono::time_point<MonotonicClock>;

static constexpr bool is_steady = true;

}; // MonotonicClock

struct Presentation : Config::Presentation
{
static constexpr std::size_t SmallPayloadSize()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/// @copyright
/// Copyright (C) OpenCyphal Development Team <opencyphal.org>
/// Copyright Amazon.com Inc. or its affiliates.
/// SPDX-License-Identifier: MIT

// The main purpose of this test file is make sure that CAN transport
// could be compiled with custom time representation (32-bit milliseconds) instead of the default one.
// Milliseconds are chosen b/c there is no implicit conversion from native lizard's microseconds
// to lower precision units like milliseconds, so proper explicit `std::chrono::duration_cast` is needed.
// For details also see https://github.com/OpenCyphal-Garage/libcyphal/issues/431.
#include "custom_libcyphal_config.hpp" // NOLINT(misc-include-cleaner)

#include "media_mock.hpp"
#include "tracking_memory_resource.hpp"
#include "virtual_time_scheduler.hpp"

#include <canard.h>
#include <cetl/pf17/cetlpf.hpp>
#include <libcyphal/transport/can/can_transport.hpp>
#include <libcyphal/transport/can/can_transport_impl.hpp>
#include <libcyphal/transport/can/media.hpp>
#include <libcyphal/types.hpp>

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <array>
#include <utility>

namespace
{

using libcyphal::UniquePtr;
using namespace libcyphal::transport::can; // NOLINT This our main concern here in the unit tests.

using testing::Eq;
using testing::Return;
using testing::IsEmpty;
using testing::NotNull;
using testing::ReturnRef;
using testing::StrictMock;
using testing::VariantWith;

class TestCanTransportCustomConfigMilliseconds : public testing::Test
{
protected:
void SetUp() override
{
cetl::pmr::set_default_resource(&mr_);

EXPECT_CALL(media_mock_, getMtu()).WillRepeatedly(Return(CANARD_MTU_CAN_CLASSIC));
EXPECT_CALL(media_mock_, getTxMemoryResource()).WillRepeatedly(ReturnRef(mr_));
}

void TearDown() override
{
EXPECT_THAT(mr_.allocations, IsEmpty());
EXPECT_THAT(mr_.total_allocated_bytes, mr_.total_deallocated_bytes);
}

// MARK: Data members:

// NOLINTBEGIN
libcyphal::VirtualTimeScheduler scheduler_{};
TrackingMemoryResource mr_;
StrictMock<MediaMock> media_mock_{};
// NOLINTEND
};

// MARK: - Tests:

TEST_F(TestCanTransportCustomConfigMilliseconds, makeTransport_getLocalNodeId)
{
std::array<IMedia*, 1> media_array{&media_mock_};
auto maybe_transport = makeTransport(mr_, scheduler_, media_array, 0);
ASSERT_THAT(maybe_transport, VariantWith<UniquePtr<ICanTransport>>(NotNull()));

const auto transport = cetl::get<UniquePtr<ICanTransport>>(std::move(maybe_transport));
EXPECT_THAT(transport->getLocalNodeId(), Eq(cetl::nullopt));
}

} // namespace
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/// @copyright
/// Copyright (C) OpenCyphal Development Team <opencyphal.org>
/// Copyright Amazon.com Inc. or its affiliates.
/// SPDX-License-Identifier: MIT

// The main purpose of this test file is make sure that UDP transport
// could be compiled with custom time representation (32-bit milliseconds) instead of the default one.
// Milliseconds are chosen b/c there is no implicit conversion from native lizard's microseconds
// to lower precision units like milliseconds, so proper explicit `std::chrono::duration_cast` is needed.
// For details also see https://github.com/OpenCyphal-Garage/libcyphal/issues/431.
#include "custom_libcyphal_config.hpp" // NOLINT(misc-include-cleaner)

#include "media_mock.hpp"
#include "tracking_memory_resource.hpp"
#include "virtual_time_scheduler.hpp"

#include <cetl/pf17/cetlpf.hpp>
#include <libcyphal/transport/udp/media.hpp>
#include <libcyphal/transport/udp/udp_transport.hpp>
#include <libcyphal/transport/udp/udp_transport_impl.hpp>
#include <libcyphal/types.hpp>

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <array>
#include <utility>

namespace
{

using libcyphal::UniquePtr;
using namespace libcyphal::transport::udp; // NOLINT This our main concern here in the unit tests.

using testing::Eq;
using testing::IsEmpty;
using testing::NotNull;
using testing::ReturnRef;
using testing::StrictMock;
using testing::VariantWith;

class TestUpdTransportCustomConfigMilliseconds : public testing::Test
{
protected:
void SetUp() override
{
cetl::pmr::set_default_resource(&mr_);

EXPECT_CALL(media_mock_, getTxMemoryResource()).WillRepeatedly(ReturnRef(mr_));
}

void TearDown() override
{
EXPECT_THAT(mr_.allocations, IsEmpty());
EXPECT_THAT(mr_.total_allocated_bytes, mr_.total_deallocated_bytes);
}

// MARK: Data members:

// NOLINTBEGIN
libcyphal::VirtualTimeScheduler scheduler_{};
TrackingMemoryResource mr_;
StrictMock<MediaMock> media_mock_{};
// NOLINTEND
};

// MARK: - Tests:

TEST_F(TestUpdTransportCustomConfigMilliseconds, makeTransport_getLocalNodeId)
{
std::array<IMedia*, 1> media_array{&media_mock_};
auto maybe_transport = makeTransport({mr_}, scheduler_, media_array, 0);
ASSERT_THAT(maybe_transport, VariantWith<UniquePtr<IUdpTransport>>(NotNull()));

const auto transport = cetl::get<UniquePtr<IUdpTransport>>(std::move(maybe_transport));
EXPECT_THAT(transport->getLocalNodeId(), Eq(cetl::nullopt));
}

} // namespace