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

API refinement: make RX pipeline zero-copy as a side effect of the OOO reassembly support #38

Merged
merged 14 commits into from
Jul 22, 2023
Merged
Show file tree
Hide file tree
Changes from 9 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
2 changes: 1 addition & 1 deletion libudpard/_udpard_cavl.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ extern "C" {
// ---------------------------------------- PUBLIC API SECTION ----------------------------------------

/// Modified for use with LibUDPard: expose the Cavl structure via public API as UdpardTreeNode.
typedef UdpardTreeNode Cavl;
typedef struct UdpardTreeNode Cavl;

/// Returns POSITIVE if the search target is GREATER than the provided node, negative if smaller, zero on match (found).
/// Values other than {-1, 0, +1} are not recommended to avoid overflow during the narrowing conversion of the result.
Expand Down
286 changes: 202 additions & 84 deletions libudpard/udpard.c

Large diffs are not rendered by default.

410 changes: 243 additions & 167 deletions libudpard/udpard.h

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,4 @@ gen_test_matrix(test_cavl "src/test_cavl.cpp")
gen_test_matrix(test_tx "${library_dir}/udpard.c;src/test_tx.cpp")
gen_test_matrix(test_intrusive_crc "src/test_intrusive_crc.c")
gen_test_matrix(test_intrusive_tx "src/test_intrusive_tx.c")
gen_test_matrix(test_intrusive_rx "src/test_intrusive_rx.c")
26 changes: 14 additions & 12 deletions tests/src/helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@
extern "C" {
#endif

#define TEST_PANIC(message) \
do \
{ \
fprintf(stderr, "%s:%u: PANIC: %s\n", __FILE__, (unsigned) __LINE__, message); \
fflush(stderr); \
abort(); \
#define TEST_PANIC(message) \
do \
{ \
(void) fprintf(stderr, "%s:%u: PANIC: %s\n", __FILE__, (unsigned) __LINE__, message); \
(void) fflush(stderr); \
abort(); \
} while (0)
#define TEST_PANIC_UNLESS(condition) \
do \
Expand All @@ -38,14 +38,14 @@ extern "C" {
} \
} while (0)

static inline void* dummyAllocatorAllocate(UdpardMemoryResource* const self, const size_t size)
static inline void* dummyAllocatorAllocate(struct UdpardMemoryResource* const self, const size_t size)
{
(void) self;
(void) size;
return NULL;
}

static inline void dummyAllocatorFree(UdpardMemoryResource* const self, const size_t size, void* const pointer)
static inline void dummyAllocatorFree(struct UdpardMemoryResource* const self, const size_t size, void* const pointer)
{
(void) size;
TEST_PANIC_UNLESS(self != NULL);
Expand All @@ -57,8 +57,8 @@ static inline void dummyAllocatorFree(UdpardMemoryResource* const self, const si
#define INSTRUMENTED_ALLOCATOR_CANARY_SIZE 1024U
typedef struct
{
UdpardMemoryResource base;
uint_least8_t canary[INSTRUMENTED_ALLOCATOR_CANARY_SIZE];
struct UdpardMemoryResource base;
uint_least8_t canary[INSTRUMENTED_ALLOCATOR_CANARY_SIZE];
/// The limit can be changed at any moment to control the maximum amount of memory that can be allocated.
/// It may be set to a value less than the currently allocated amount.
size_t limit_bytes;
Expand All @@ -67,7 +67,7 @@ typedef struct
size_t allocated_bytes;
} InstrumentedAllocator;

static inline void* instrumentedAllocatorAllocate(UdpardMemoryResource* const base, const size_t size)
static inline void* instrumentedAllocatorAllocate(struct UdpardMemoryResource* const base, const size_t size)
{
InstrumentedAllocator* const self = (InstrumentedAllocator*) base;
TEST_PANIC_UNLESS(self->base.allocate == &instrumentedAllocatorAllocate);
Expand Down Expand Up @@ -98,7 +98,9 @@ static inline void* instrumentedAllocatorAllocate(UdpardMemoryResource* const ba
return result;
}

static inline void instrumentedAllocatorFree(UdpardMemoryResource* const base, const size_t size, void* const pointer)
static inline void instrumentedAllocatorFree(struct UdpardMemoryResource* const base,
const size_t size,
void* const pointer)
{
InstrumentedAllocator* const self = (InstrumentedAllocator*) base;
TEST_PANIC_UNLESS(self->base.allocate == &instrumentedAllocatorAllocate);
Expand Down
151 changes: 151 additions & 0 deletions tests/src/test_intrusive_rx.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/// 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 <udpard.c> // NOLINT(bugprone-suspicious-include)
#include "helpers.h"
#include <unity.h>

// Generate reference data using PyCyphal:
//
// >>> from pycyphal.transport.udp import UDPFrame
// >>> from pycyphal.transport import Priority, MessageDataSpecifier, ServiceDataSpecifier
// >>> frame = UDPFrame(priority=Priority.FAST, transfer_id=0xbadc0ffee0ddf00d, index=12345, end_of_transfer=False,
// payload=memoryview(b''), source_node_id=2345, destination_node_id=5432,
// data_specifier=MessageDataSpecifier(7654), user_data=0)
// >>> list(frame.compile_header_and_payload()[0])
// [1, 2, 41, 9, 56, 21, 230, 29, 13, 240, 221, 224, 254, 15, 220, 186, 57, 48, 0, 0, 0, 0, 224, 60]
static void testRxParseFrameValidMessage(void)
thirtytwobits marked this conversation as resolved.
Show resolved Hide resolved
{
const byte_t data[] = {1, 2, 41, 9, 255, 255, 230, 29, 13, 240, 221, 224,
254, 15, 220, 186, 57, 48, 0, 0, 0, 0, 30, 179, //
'a', 'b', 'c'};
RxFrame rxf = {0};
TEST_ASSERT(rxParseFrame((struct UdpardConstPayload){.data = data, .size = sizeof(data)}, &rxf));
TEST_ASSERT_EQUAL_UINT64(UdpardPriorityFast, rxf.meta.priority);
TEST_ASSERT_EQUAL_UINT64(2345, rxf.meta.src_node_id);
TEST_ASSERT_EQUAL_UINT64(UDPARD_NODE_ID_UNSET, rxf.meta.dst_node_id);
TEST_ASSERT_EQUAL_UINT64(7654, rxf.meta.data_specifier);
TEST_ASSERT_EQUAL_UINT64(0xbadc0ffee0ddf00d, rxf.meta.transfer_id);
TEST_ASSERT_EQUAL_UINT64(12345, rxf.index);
TEST_ASSERT_FALSE(rxf.end_of_transfer);
TEST_ASSERT_EQUAL_UINT64(3, rxf.payload.size);
TEST_ASSERT_EQUAL_UINT8_ARRAY("abc", rxf.payload.data, 3);
}

static void testRxParseFrameValidRPCService(void)
{
// frame = UDPFrame(priority=Priority.FAST, transfer_id=0xbadc0ffee0ddf00d, index=6654, end_of_transfer=False,
// payload=memoryview(b''), source_node_id=2345, destination_node_id=4567,
// data_specifier=ServiceDataSpecifier(role=ServiceDataSpecifier.Role.REQUEST, service_id=123), user_data=0)
const byte_t data[] = {1, 2, 41, 9, 215, 17, 123, 192, 13, 240, 221, 224,
254, 15, 220, 186, 254, 25, 0, 0, 0, 0, 173, 122, //
'a', 'b', 'c'};
RxFrame rxf = {0};
TEST_ASSERT(rxParseFrame((struct UdpardConstPayload){.data = data, .size = sizeof(data)}, &rxf));
TEST_ASSERT_EQUAL_UINT64(UdpardPriorityFast, rxf.meta.priority);
TEST_ASSERT_EQUAL_UINT64(2345, rxf.meta.src_node_id);
TEST_ASSERT_EQUAL_UINT64(4567, rxf.meta.dst_node_id);
TEST_ASSERT_EQUAL_UINT64(123U | DATA_SPECIFIER_SERVICE_NOT_MESSAGE_MASK |
DATA_SPECIFIER_SERVICE_REQUEST_NOT_RESPONSE_MASK,
rxf.meta.data_specifier);
TEST_ASSERT_EQUAL_UINT64(0xbadc0ffee0ddf00d, rxf.meta.transfer_id);
TEST_ASSERT_EQUAL_UINT64(6654, rxf.index);
TEST_ASSERT_FALSE(rxf.end_of_transfer);
TEST_ASSERT_EQUAL_UINT64(3, rxf.payload.size);
TEST_ASSERT_EQUAL_UINT8_ARRAY("abc", rxf.payload.data, 3);
}

static void testRxParseFrameValidMessageAnonymous(void)
{
const byte_t data[] = {1, 2, 255, 255, 255, 255, 230, 29, 13, 240, 221, 224,
254, 15, 220, 186, 0, 0, 0, 128, 0, 0, 168, 92, //
'a', 'b', 'c'};
RxFrame rxf = {0};
TEST_ASSERT(rxParseFrame((struct UdpardConstPayload){.data = data, .size = sizeof(data)}, &rxf));
TEST_ASSERT_EQUAL_UINT64(UdpardPriorityFast, rxf.meta.priority);
TEST_ASSERT_EQUAL_UINT64(UDPARD_NODE_ID_UNSET, rxf.meta.src_node_id);
TEST_ASSERT_EQUAL_UINT64(UDPARD_NODE_ID_UNSET, rxf.meta.dst_node_id);
TEST_ASSERT_EQUAL_UINT64(7654, rxf.meta.data_specifier);
TEST_ASSERT_EQUAL_UINT64(0xbadc0ffee0ddf00d, rxf.meta.transfer_id);
TEST_ASSERT_EQUAL_UINT64(0, rxf.index);
TEST_ASSERT_TRUE(rxf.end_of_transfer);
TEST_ASSERT_EQUAL_UINT64(3, rxf.payload.size);
TEST_ASSERT_EQUAL_UINT8_ARRAY("abc", rxf.payload.data, 3);
}

static void testRxParseFrameRPCServiceAnonymous(void)
{
const byte_t data[] = {1, 2, 255, 255, 215, 17, 123, 192, 13, 240, 221, 224,
254, 15, 220, 186, 254, 25, 0, 0, 0, 0, 75, 79, //
'a', 'b', 'c'};
RxFrame rxf = {0};
TEST_ASSERT_FALSE(rxParseFrame((struct UdpardConstPayload){.data = data, .size = sizeof(data)}, &rxf));
}

static void testRxParseFrameRPCServiceBroadcast(void)
{
const byte_t data[] = {1, 2, 41, 9, 255, 255, 123, 192, 13, 240, 221, 224,
254, 15, 220, 186, 254, 25, 0, 0, 0, 0, 248, 152, //
'a', 'b', 'c'};
RxFrame rxf = {0};
TEST_ASSERT_FALSE(rxParseFrame((struct UdpardConstPayload){.data = data, .size = sizeof(data)}, &rxf));
}

static void testRxParseFrameAnonymousNonSingleFrame(void)
{ // Invalid anonymous message frame because EOT not set (multi-frame anonymous transfers are not allowed).
const byte_t data[] = {1, 2, 255, 255, 255, 255, 230, 29, 13, 240, 221, 224,
254, 15, 220, 186, 0, 0, 0, 0, 0, 0, 147, 6, //
'a', 'b', 'c'};
RxFrame rxf = {0};
TEST_ASSERT_FALSE(rxParseFrame((struct UdpardConstPayload){.data = data, .size = sizeof(data)}, &rxf));
}

static void testRxParseFrameBadHeaderCRC(void)
{ // Bad header CRC.
const byte_t data[] = {1, 2, 41, 9, 255, 255, 230, 29, 13, 240, 221, 224,
254, 15, 220, 186, 57, 48, 0, 0, 0, 0, 30, 180, //
'a', 'b', 'c'};
RxFrame rxf = {0};
TEST_ASSERT_FALSE(rxParseFrame((struct UdpardConstPayload){.data = data, .size = sizeof(data)}, &rxf));
}

static void testRxParseFrameUnknownHeaderVersion(void)
{
// >>> from pycyphal.transport.commons.crc import CRC16CCITT
// >>> list(CRC16CCITT.new(bytes(
// [0, 2, 41, 9, 56, 21, 230, 29, 13, 240, 221, 224, 254, 15, 220, 186, 57, 48, 0, 0, 0, 0])).value_as_bytes)
const byte_t data[] = {0, 2, 41, 9, 56, 21, 230, 29, 13, 240, 221, 224,
254, 15, 220, 186, 57, 48, 0, 0, 0, 0, 141, 228, //
'a', 'b', 'c'};
RxFrame rxf = {0};
TEST_ASSERT_FALSE(rxParseFrame((struct UdpardConstPayload){.data = data, .size = sizeof(data)}, &rxf));
}

static void testRxParseFrameHeaderWithoutPayload(void)
{
const byte_t data[] = {1, 2, 41, 9, 255, 255, 230, 29, 13, 240, 221, 224,
254, 15, 220, 186, 57, 48, 0, 0, 0, 0, 30, 179};
RxFrame rxf = {0};
TEST_ASSERT_FALSE(rxParseFrame((struct UdpardConstPayload){.data = data, .size = sizeof(data)}, &rxf));
}

void setUp(void) {}

void tearDown(void) {}

int main(void)
{
UNITY_BEGIN();
RUN_TEST(testRxParseFrameValidMessage);
RUN_TEST(testRxParseFrameValidRPCService);
RUN_TEST(testRxParseFrameValidMessageAnonymous);
RUN_TEST(testRxParseFrameRPCServiceAnonymous);
RUN_TEST(testRxParseFrameRPCServiceBroadcast);
RUN_TEST(testRxParseFrameAnonymousNonSingleFrame);
RUN_TEST(testRxParseFrameBadHeaderCRC);
RUN_TEST(testRxParseFrameUnknownHeaderVersion);
RUN_TEST(testRxParseFrameHeaderWithoutPayload);
return UNITY_END();
}
6 changes: 6 additions & 0 deletions tests/src/test_intrusive_tx.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ static const char InterstellarWar[] = "You have not seen what a true inter
static const size_t InterstellarWarSize = sizeof(InterstellarWar) - 1;
static const byte_t InterstellarWarCRC[4] = {102, 217, 109, 188};

// These aliases cannot be defined in the public API section: https://github.com/OpenCyphal-Garage/libudpard/issues/36
typedef struct UdpardConstPayload UdpardConstPayload;
typedef struct UdpardUDPIPEndpoint UdpardUDPIPEndpoint;
typedef struct UdpardTx UdpardTx;
typedef struct UdpardTxItem UdpardTxItem;

typedef struct
{
byte_t data[HEADER_SIZE_BYTES];
Expand Down