Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transmission pipeline and cosmetic adjustments to the API #34

Merged
merged 31 commits into from
Jul 10, 2023
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9be32a8
Add CRC implementations #sonar
pavel-kirienko Jun 30, 2023
7ff170a
Cavl test: reduce the iteration count in the randomized test from 100…
pavel-kirienko Jun 30, 2023
1988da6
udpardTxInit
pavel-kirienko Jun 30, 2023
f5e9b13
Add txPushSingleFrame() #sonar
pavel-kirienko Jun 30, 2023
4ebcffd
Test header serialization
pavel-kirienko Jun 30, 2023
140129c
nits #sonar
pavel-kirienko Jun 30, 2023
99e4892
Add txGenerateMultiFrameChain
pavel-kirienko Jun 30, 2023
66ccad1
AVR-specific conversion fixes
pavel-kirienko Jun 30, 2023
65a523b
Check deallocation size; initialize tx precedence based on priority
pavel-kirienko Jul 1, 2023
4a8eb09
txMakeChain is finished but not all cases are covered yet #sonar
pavel-kirienko Jul 1, 2023
b9c252b
make chain tests done #sonar
pavel-kirienko Jul 1, 2023
5b834b7
txPush
pavel-kirienko Jul 1, 2023
3cff534
Finish the TX pipeline, two tests missing #sonar
pavel-kirienko Jul 1, 2023
f1a0936
Extend error handling
pavel-kirienko Jul 1, 2023
6bfea6d
Fix nits #sonar
pavel-kirienko Jul 1, 2023
5de3f60
Add missing tests #sonar
pavel-kirienko Jul 1, 2023
5e27852
Minor adjustmet before sending the PR
pavel-kirienko Jul 1, 2023
e9929e6
Use plain priority instead of precedence for simplification
pavel-kirienko Jul 2, 2023
1564317
Use fewer test data strings
pavel-kirienko Jul 2, 2023
53e9f87
Add prioritization test across multiple transfers
pavel-kirienko Jul 2, 2023
8974689
Address simple review comments; mostly renamings and comments
pavel-kirienko Jul 4, 2023
0c6e7f8
Define HEADER_FRAME_INDEX_MAX for clarity
pavel-kirienko Jul 4, 2023
d4875cd
UDPARD_UDP_PORT does not need to be public
pavel-kirienko Jul 4, 2023
b583110
Nits
pavel-kirienko Jul 6, 2023
a35339c
BROKEN: switch to one-test-per-executable with #include <udpard.c>; n…
pavel-kirienko Jul 6, 2023
1f711fa
Revert "BROKEN: switch to one-test-per-executable with #include <udpa…
pavel-kirienko Jul 6, 2023
055358d
Switch from Google Test to Unity; make the internal tests include udp…
pavel-kirienko Jul 8, 2023
7a85301
Add a test for UDPARD_MTU_DEFAULT_MAX_SINGLE_FRAME
pavel-kirienko Jul 8, 2023
f871b4a
Do not leak memory from intrusive tests
pavel-kirienko Jul 8, 2023
a3f64f0
Add a NOLINT comment in Cavl to suppress bogus warning from Clang flo…
pavel-kirienko Jul 8, 2023
a6837dd
Naming nits & formatting, nothing to see here
pavel-kirienko Jul 8, 2023
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
639 changes: 637 additions & 2 deletions libudpard/udpard.c

Large diffs are not rendered by default.

139 changes: 70 additions & 69 deletions libudpard/udpard.h

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions tests/.clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Checks: >-
-cert-msc30-c,
-cert-msc50-cpp,
-modernize-macro-to-enum,
-modernize-use-trailing-return-type,
-cppcoreguidelines-owning-memory,
WarningsAsErrors: '*'
HeaderFilterRegex: '.*\.hpp'
Expand Down
2 changes: 2 additions & 0 deletions tests/.idea/dictionaries/pavel.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ endfunction()

# Disable missing declaration warning to allow exposure of private definitions.
gen_test_matrix(test_private
"test_private_cavl.cpp;"
"test_private_cavl.cpp;test_private_crc.cpp;test_private_tx.cpp;"
pavel-kirienko marked this conversation as resolved.
Show resolved Hide resolved
"-DUDPARD_CONFIG_HEADER=\"${CMAKE_CURRENT_SOURCE_DIR}/udpard_config_private.h\""
"-Wno-missing-declarations"
"")

gen_test_matrix(test_public
"test_self.cpp"
"test_self.cpp;test_public_tx.cpp;"
""
"-Wmissing-declarations"
"")
70 changes: 70 additions & 0 deletions tests/exposed.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/// This software is distributed under the terms of the MIT License.
/// Copyright (C) OpenCyphal Development Team <opencyphal.org>
/// Copyright Amazon.com Inc. or its affiliates.
/// SPDX-License-Identifier: MIT

#pragma once

#include <udpard.h> // Must be always included first.
#include <cstdarg>
#include <cstdint>
#include <limits>
#include <stdexcept>

/// Definitions that are not exposed by the library but that are needed for testing.
/// Please keep them in sync with the library by manually updating as necessary.
namespace exposed
{
using byte_t = std::uint_least8_t;

constexpr std::size_t HeaderSize = 24U;

struct TransferMetadata final
{
UdpardPriority priority;
UdpardNodeID src_node_id;
UdpardNodeID dst_node_id;
std::uint16_t data_specifier;
UdpardTransferID transfer_id;
};

struct TxItem final : public UdpardTxItem
{
UdpardPriority priority;
// flex array not included
};

struct TxChain final
{
TxItem* head;
TxItem* tail;
std::size_t count;
};

extern "C" {
std::uint16_t headerCRCCompute(const std::size_t size, const void* const data);

std::uint32_t transferCRCAdd(const std::uint32_t crc, const std::size_t size, const void* const data);

byte_t* txSerializeHeader(byte_t* const destination_buffer,
const TransferMetadata meta,
const std::uint32_t frame_index,
const bool end_of_transfer);

TxChain txMakeChain(UdpardMemoryResource* const memory,
const std::uint_least8_t dscp_value_per_priority[UDPARD_PRIORITY_MAX + 1U],
const std::size_t mtu,
const UdpardMicrosecond deadline_usec,
const TransferMetadata meta,
const UdpardUDPIPEndpoint endpoint,
const UdpardConstPayload payload,
void* const user_transfer_reference);

std::int32_t txPush(UdpardTx* const tx,
const UdpardMicrosecond deadline_usec,
const TransferMetadata meta,
const UdpardUDPIPEndpoint endpoint,
const UdpardConstPayload payload,
void* const user_transfer_reference);
}
} // namespace exposed
56 changes: 36 additions & 20 deletions tests/helpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@ namespace helpers
{
namespace dummy_allocator
{
inline auto allocate(UdpardMemoryResource* const ins, const std::size_t amount) -> void*
inline auto allocate(UdpardMemoryResource* const self, const std::size_t amount) -> void*
{
(void) ins;
(void) self;
(void) amount;
return nullptr;
}

inline void free(UdpardMemoryResource* const ins, const size_t size, void* const pointer)
inline void free(UdpardMemoryResource* const self, const size_t size, void* const pointer)
{
(void) ins;
(void) self;
(void) size;
(void) pointer;
}
Expand All @@ -65,10 +65,14 @@ inline void traverse(const UdpardTreeNode* const root, const F& fun)
/// An allocator that sits on top of the standard malloc() providing additional testing capabilities.
/// It allows the user to specify the maximum amount of memory that can be allocated; further requests will emulate OOM.
/// It also performs correctness checks on the memory use.
class TestAllocator
class TestAllocator final : public UdpardMemoryResource
{
public:
TestAllocator() = default;
TestAllocator() :
UdpardMemoryResource{.allocate = &TestAllocator::trampolineAllocate,
.free = &TestAllocator::trampolineFree,
.user_reference = this}
{}
TestAllocator(const TestAllocator&) = delete;
TestAllocator(const TestAllocator&&) = delete;
auto operator=(const TestAllocator&) -> TestAllocator& = delete;
Expand All @@ -79,36 +83,33 @@ class TestAllocator
const std::unique_lock locker(lock_);
for (const auto& pair : allocated_)
{
// Clang-tidy complains about manual memory management. Suppressed because we need it for testing purposes.
std::free(pair.first - canary_.size()); // NOLINT
std::free(pair.first - canary_.size());
}
}

[[nodiscard]] auto allocate(const std::size_t amount) -> void*
[[nodiscard]] auto allocate(const std::size_t size) -> void*
{
const std::unique_lock locker(lock_);
std::uint8_t* p = nullptr;
if ((amount > 0U) && ((getTotalAllocatedAmount() + amount) <= ceiling_))
if ((size > 0U) && ((getTotalAllocatedAmount() + size) <= ceiling_))
{
const auto amount_with_canaries = amount + canary_.size() * 2U;
// Clang-tidy complains about manual memory management. Suppressed because we need it for testing purposes.
p = static_cast<std::uint8_t*>(std::malloc(amount_with_canaries)); // NOLINT
const auto size_with_canaries = size + canary_.size() * 2U;
p = static_cast<std::uint8_t*>(std::malloc(size_with_canaries));
if (p == nullptr)
{
throw std::bad_alloc(); // This is a test suite failure, not a failed test. Mind the difference.
}
p += canary_.size();
std::generate_n(p, amount, []() { return static_cast<std::uint8_t>(getRandomNatural(256U)); });
std::generate_n(p, size, [] { return static_cast<std::uint8_t>(getRandomNatural(256U)); });
std::memcpy(p - canary_.size(), canary_.begin(), canary_.size());
std::memcpy(p + amount, canary_.begin(), canary_.size());
allocated_.emplace(p, amount);
std::memcpy(p + size, canary_.begin(), canary_.size());
allocated_.emplace(p, size);
}
return p;
}

void free(const std::size_t size, void* const pointer)
{
(void) size; // TODO FIXME ensure the size passed to this function is correct.
if (pointer != nullptr)
{
const std::unique_lock locker(lock_);
Expand All @@ -118,15 +119,20 @@ class TestAllocator
throw std::logic_error("Attempted to deallocate memory that was never allocated; ptr=" +
std::to_string(reinterpret_cast<std::uint64_t>(pointer)));
}
const auto [p, amount] = *it;
const auto [p, true_size] = *it;
if (size != true_size)
{
throw std::logic_error("Attempted to deallocate memory with a wrong size; ptr=" +
std::to_string(reinterpret_cast<std::uint64_t>(pointer)));
}
if ((0 != std::memcmp(p - canary_.size(), canary_.begin(), canary_.size())) ||
(0 != std::memcmp(p + amount, canary_.begin(), canary_.size())))
(0 != std::memcmp(p + true_size, canary_.begin(), canary_.size())))
{
throw std::logic_error("Dead canary detected at ptr=" +
std::to_string(reinterpret_cast<std::uint64_t>(pointer)));
}
std::generate_n(p - canary_.size(), // Damage the memory to make sure it's not used after deallocation.
amount + canary_.size() * 2U,
true_size + canary_.size() * 2U,
[]() { return static_cast<std::uint8_t>(getRandomNatural(256U)); });
std::free(p - canary_.size());
allocated_.erase(it);
Expand Down Expand Up @@ -161,6 +167,16 @@ class TestAllocator
return out;
}

static auto trampolineAllocate(UdpardMemoryResource* const self, const size_t size) -> void*
pavel-kirienko marked this conversation as resolved.
Show resolved Hide resolved
{
return static_cast<TestAllocator*>(self->user_reference)->allocate(size);
}

static void trampolineFree(UdpardMemoryResource* const self, const size_t size, void* const pointer)
{
return static_cast<TestAllocator*>(self->user_reference)->free(size, pointer);
}

const std::array<std::uint8_t, 256> canary_ = makeCanary();

mutable std::recursive_mutex lock_;
Expand Down
80 changes: 80 additions & 0 deletions tests/hexdump.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/// This software is distributed under the terms of the MIT License.
/// Copyright (C) OpenCyphal Development Team <opencyphal.org>
/// Copyright Amazon.com Inc. or its affiliates.
/// SPDX-License-Identifier: MIT
/// Author: Pavel Kirienko <[email protected]>

#include <string>
#include <sstream>
#include <iomanip>

namespace hexdump
{
template <std::uint8_t BytesPerRow = 16, typename InputIterator>
[[nodiscard]] std::string hexdump(InputIterator begin, const InputIterator end)
{
static_assert(BytesPerRow > 0);
static constexpr std::pair<std::uint8_t, std::uint8_t> PrintableASCIIRange{32, 126};
std::uint32_t offset = 0;
std::ostringstream output;
bool first = true;
output << std::hex << std::setfill('0');
do
{
if (first)
{
first = false;
}
else
{
output << "\n";
}
output << std::setw(8) << offset << " ";
offset += BytesPerRow;
auto it = begin;
for (std::uint8_t i = 0; i < BytesPerRow; ++i)
{
if (i == 8)
{
output << ' ';
}
if (it != end)
{
output << std::setw(2) << static_cast<std::uint32_t>(*it) << ' ';
++it;
}
else
{
output << " ";
}
}
output << " ";
for (std::uint8_t i = 0; i < BytesPerRow; ++i)
{
if (begin != end)
{
output << (((static_cast<std::uint32_t>(*begin) >= PrintableASCIIRange.first) &&
(static_cast<std::uint32_t>(*begin) <= PrintableASCIIRange.second))
? static_cast<char>(*begin) // NOSONAR intentional conversion to plain char
: '.');
++begin;
}
else
{
output << ' ';
}
}
} while (begin != end);
return output.str();
}

[[nodiscard]] auto hexdump(const auto& cont)
{
return hexdump(std::begin(cont), std::end(cont));
}

[[nodiscard]] inline auto hexdump(const void* const data, const std::size_t size)
{
return hexdump(static_cast<const std::uint8_t*>(data), static_cast<const std::uint8_t*>(data) + size);
}
} // namespace hexdump
2 changes: 1 addition & 1 deletion tests/test_private_cavl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1347,7 +1347,7 @@ TEST(Cavl, MutationRandomized)
};

std::puts("Running the randomized test...");
for (std::uint32_t iteration = 0U; iteration < 100'000U; iteration++)
for (std::uint32_t iteration = 0U; iteration < 10'000U; iteration++)
{
if ((getRandomByte() % 2U) != 0)
{
Expand Down
31 changes: 31 additions & 0 deletions tests/test_private_crc.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/// This software is distributed under the terms of the MIT License.
/// Copyright (C) OpenCyphal Development Team <opencyphal.org>
/// Copyright Amazon.com Inc. or its affiliates.
/// SPDX-License-Identifier: MIT

#include "exposed.hpp"
#include <gtest/gtest.h>

TEST(CRC, Header)
{
using exposed::headerCRCCompute;
ASSERT_EQ(0x29B1U, headerCRCCompute(9, "123456789"));
}

TEST(CRC, Transfer)
{
using exposed::transferCRCAdd;
constexpr std::uint32_t OutputXOR = 0xFFFFFFFFU;
auto crc = transferCRCAdd(0xFFFFFFFFU, 3, "123");
crc = transferCRCAdd(crc, 6, "456789");
ASSERT_EQ(0x1CF96D7CUL, crc);
ASSERT_EQ(0xE3069283UL, crc ^ OutputXOR);
crc = transferCRCAdd(crc,
4,
"\x83" // Least significant byte first.
"\x92"
"\x06"
"\xE3");
ASSERT_EQ(0xB798B438UL, crc);
ASSERT_EQ(0x48674BC7UL, crc ^ OutputXOR);
}
Loading