Skip to content

Commit 7ad777f

Browse files
Improve test coverage and fix typos (#74)
1 parent 70e6565 commit 7ad777f

8 files changed

Lines changed: 1189 additions & 18 deletions

File tree

libudpard/udpard.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ typedef unsigned char byte_t; ///< For compatibility with platforms where byte s
5353
/// The number of most recent transfers to keep in the history for duplicate rejection.
5454
/// Should be a power of two to allow replacement of modulo operation with a bitwise AND.
5555
///
56-
/// Implementation node: we used to store bitmap windows instead of a full list of recent transfer-IDs, but they
56+
/// Implementation note: we used to store bitmap windows instead of a full list of recent transfer-IDs, but they
5757
/// were found to offer no advantage except in the perfect scenario of non-restarting senders, and an increased
5858
/// implementation complexity (more branches, more lines of code), so they were replaced with a simple list.
5959
/// The list works equally well given a non-contiguous transfer-ID stream, unlike the bitmap, thus more robust.
@@ -1584,7 +1584,7 @@ static void rx_session_eject(rx_session_t* const self, udpard_rx_t* const rx, rx
15841584
rx_slot_destroy(slot_ref, self->port->memory.fragment, self->port->memory.slot);
15851585
}
15861586

1587-
/// Finds an existing in-progress slot with the specified transfer-ID, or allocates a new one. Returns NULL of OOM.
1587+
/// Finds an existing in-progress slot with the specified transfer-ID, or allocates a new one. Returns NULL on OOM.
15881588
/// We return a pointer to pointer to allow the caller to NULL out the slot on destruction.
15891589
static rx_slot_t** rx_session_get_slot(rx_session_t* const self, const udpard_us_t ts, const uint64_t transfer_id)
15901590
{
@@ -1705,8 +1705,8 @@ static void rx_port_accept_stateful(udpard_rx_t* const rx,
17051705
}
17061706
}
17071707

1708-
/// The stateless strategy accepts only single-frame transfers and does not maintain any session state.
1709-
/// It could be trivially extended to fallback to UNORDERED when multi-frame transfers are detected.
1708+
/// The stateless strategy accepts transfers that fit in the first frame after extent truncation.
1709+
/// It does not maintain any session state.
17101710
static void rx_port_accept_stateless(udpard_rx_t* const rx,
17111711
udpard_rx_port_t* const port,
17121712
const udpard_us_t timestamp,
@@ -1715,6 +1715,8 @@ static void rx_port_accept_stateless(udpard_rx_t* const rx,
17151715
const udpard_deleter_t payload_deleter,
17161716
const uint_fast8_t iface_index)
17171717
{
1718+
// Stateless subscriptions only care about the prefix up to the configured extent.
1719+
// If the first frame already covers that much payload, the rest of the transfer is ignored.
17181720
const size_t required_size = smaller(port->extent, frame->meta.transfer_payload_size);
17191721
const bool full_transfer = (frame->base.offset == 0) && (frame->base.payload.size >= required_size);
17201722
if (full_transfer) {

libudpard/udpard.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -661,9 +661,9 @@ bool udpard_rx_port_new(udpard_rx_port_t* const self,
661661
const udpard_rx_mem_resources_t memory,
662662
const udpard_rx_port_vtable_t* const vtable);
663663

664-
/// A specialization of udpard_rx_port_new() for scalable stateless subscriptions, where only single-frame transfers
665-
/// are accepted, and no attempt at deduplication is made. This is useful for the heartbeat topic mostly, and perhaps
666-
/// other topics with a great number of publishers and/or very high traffic.
664+
/// A specialization of udpard_rx_port_new() for scalable stateless subscriptions, where only the prefix up to the
665+
/// configured extent is accepted from the first frame, and no attempt at deduplication is made. This is useful for
666+
/// the heartbeat topic mostly, and perhaps other topics with a great number of publishers and/or very high traffic.
667667
bool udpard_rx_port_new_stateless(udpard_rx_port_t* const self,
668668
const size_t extent,
669669
const udpard_rx_mem_resources_t memory,

tests/src/test_e2e_api.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ void test_subject_roundtrip()
118118
TEST_ASSERT_TRUE(udpard_rx_port_new(&port, 1024U, rx_mem, &rx_vtable));
119119

120120
// Send one multi-frame transfer over two interfaces.
121-
std::vector<uint8_t> payload(300U);
121+
std::vector<uint8_t> payload(600U);
122122
for (std::size_t i = 0; i < payload.size(); i++) {
123123
payload[i] = static_cast<uint8_t>(i);
124124
}
@@ -133,7 +133,7 @@ void test_subject_roundtrip()
133133
make_scattered(payload.data(), payload.size()),
134134
nullptr));
135135
udpard_tx_poll(&tx, 1001, UDPARD_IFACE_BITMAP_ALL);
136-
TEST_ASSERT_TRUE(!frames.empty());
136+
TEST_ASSERT_TRUE(frames.size() > 1U);
137137

138138
// Deliver the first interface copy only.
139139
for (const auto& frame : frames) {

tests/src/test_e2e_edge.cpp

Lines changed: 155 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ void test_out_of_order_multiframe_reassembly()
199199
TEST_ASSERT_TRUE(udpard_rx_port_new(&port, 4096U, rx_mem, &rx_vtable));
200200

201201
// Send a payload that spans multiple frames.
202-
std::vector<std::uint8_t> payload(280U);
202+
std::vector<std::uint8_t> payload(600U);
203203
for (std::size_t i = 0; i < payload.size(); i++) {
204204
payload[i] = static_cast<std::uint8_t>(i ^ 0x5AU);
205205
}
@@ -214,7 +214,7 @@ void test_out_of_order_multiframe_reassembly()
214214
make_scattered(payload.data(), payload.size()),
215215
nullptr));
216216
udpard_tx_poll(&tx, 1001, UDPARD_IFACE_BITMAP_ALL);
217-
TEST_ASSERT_TRUE(!frames.empty());
217+
TEST_ASSERT_TRUE(frames.size() > 1U);
218218

219219
// Deliver frames in reverse order to exercise out-of-order reassembly.
220220
std::reverse(frames.begin(), frames.end());
@@ -243,6 +243,155 @@ void test_out_of_order_multiframe_reassembly()
243243
instrumented_allocator_reset(&rx_alloc_fragment);
244244
}
245245

246+
void test_stateless_single_frame_acceptance()
247+
{
248+
seed_prng();
249+
250+
// Configure TX and RX.
251+
instrumented_allocator_t tx_alloc_transfer{};
252+
instrumented_allocator_t tx_alloc_payload{};
253+
instrumented_allocator_t rx_alloc_session{};
254+
instrumented_allocator_t rx_alloc_fragment{};
255+
instrumented_allocator_new(&tx_alloc_transfer);
256+
instrumented_allocator_new(&tx_alloc_payload);
257+
instrumented_allocator_new(&rx_alloc_session);
258+
instrumented_allocator_new(&rx_alloc_fragment);
259+
260+
udpard_tx_t tx{};
261+
std::vector<CapturedFrame> frames;
262+
TEST_ASSERT_TRUE(udpard_tx_new(
263+
&tx, 0x1234123412341234ULL, 777U, 8U, make_tx_mem(tx_alloc_transfer, tx_alloc_payload), &tx_vtable));
264+
tx.mtu[0] = 128U;
265+
tx.mtu[1] = 128U;
266+
tx.mtu[2] = 128U;
267+
tx.user = &frames;
268+
269+
const auto rx_mem = make_rx_mem(rx_alloc_session, rx_alloc_fragment);
270+
const udpard_deleter_t del = instrumented_allocator_make_deleter(&rx_alloc_fragment);
271+
udpard_rx_t rx{};
272+
udpard_rx_port_t port{};
273+
RxState state{};
274+
udpard_rx_new(&rx);
275+
rx.user = &state;
276+
TEST_ASSERT_TRUE(udpard_rx_port_new_stateless(&port, 1U, rx_mem, &rx_vtable));
277+
278+
// Send and deliver one single-frame transfer.
279+
const std::vector<std::uint8_t> payload{ 0x10U, 0x20U, 0x30U, 0x40U };
280+
TEST_ASSERT_TRUE(udpard_tx_push(&tx,
281+
100U,
282+
10000U,
283+
1U,
284+
udpard_prio_nominal,
285+
88U,
286+
udpard_make_subject_endpoint(66U),
287+
make_scattered(payload.data(), payload.size()),
288+
nullptr));
289+
udpard_tx_poll(&tx, 101U, UDPARD_IFACE_BITMAP_ALL);
290+
TEST_ASSERT_EQUAL_size_t(1U, frames.size());
291+
292+
deliver(frames.front(), rx_mem.fragment, del, &rx, &port, 200U);
293+
udpard_rx_poll(&rx, 201U);
294+
TEST_ASSERT_EQUAL_size_t(1U, state.count);
295+
TEST_ASSERT_EQUAL_size_t(payload.size(), state.payload.size());
296+
TEST_ASSERT_EQUAL_MEMORY(payload.data(), state.payload.data(), payload.size());
297+
TEST_ASSERT_EQUAL_UINT64(0U, rx.errors_transfer_malformed);
298+
299+
// Release all resources.
300+
udpard_rx_port_free(&rx, &port);
301+
udpard_tx_free(&tx);
302+
TEST_ASSERT_EQUAL_size_t(0U, tx_alloc_transfer.allocated_fragments);
303+
TEST_ASSERT_EQUAL_size_t(0U, tx_alloc_payload.allocated_fragments);
304+
TEST_ASSERT_EQUAL_size_t(0U, rx_alloc_session.allocated_fragments);
305+
TEST_ASSERT_EQUAL_size_t(0U, rx_alloc_fragment.allocated_fragments);
306+
instrumented_allocator_reset(&tx_alloc_transfer);
307+
instrumented_allocator_reset(&tx_alloc_payload);
308+
instrumented_allocator_reset(&rx_alloc_session);
309+
instrumented_allocator_reset(&rx_alloc_fragment);
310+
}
311+
312+
void test_stateless_multiframe_first_frame_handling(const std::size_t extent, const bool expect_accept)
313+
{
314+
seed_prng();
315+
316+
// Configure TX and RX.
317+
instrumented_allocator_t tx_alloc_transfer{};
318+
instrumented_allocator_t tx_alloc_payload{};
319+
instrumented_allocator_t rx_alloc_session{};
320+
instrumented_allocator_t rx_alloc_fragment{};
321+
instrumented_allocator_new(&tx_alloc_transfer);
322+
instrumented_allocator_new(&tx_alloc_payload);
323+
instrumented_allocator_new(&rx_alloc_session);
324+
instrumented_allocator_new(&rx_alloc_fragment);
325+
326+
udpard_tx_t tx{};
327+
std::vector<CapturedFrame> frames;
328+
TEST_ASSERT_TRUE(udpard_tx_new(
329+
&tx, 0x5555666677778888ULL, 999U, 16U, make_tx_mem(tx_alloc_transfer, tx_alloc_payload), &tx_vtable));
330+
tx.mtu[0] = 128U;
331+
tx.mtu[1] = 128U;
332+
tx.mtu[2] = 128U;
333+
tx.user = &frames;
334+
335+
const auto rx_mem = make_rx_mem(rx_alloc_session, rx_alloc_fragment);
336+
const udpard_deleter_t del = instrumented_allocator_make_deleter(&rx_alloc_fragment);
337+
udpard_rx_t rx{};
338+
udpard_rx_port_t port{};
339+
RxState state{};
340+
udpard_rx_new(&rx);
341+
rx.user = &state;
342+
TEST_ASSERT_TRUE(udpard_rx_port_new_stateless(&port, extent, rx_mem, &rx_vtable));
343+
344+
// Emit a transfer that is guaranteed to span multiple frames.
345+
std::vector<std::uint8_t> payload(600U);
346+
for (std::size_t i = 0; i < payload.size(); i++) {
347+
payload[i] = static_cast<std::uint8_t>(i);
348+
}
349+
TEST_ASSERT_TRUE(udpard_tx_push(&tx,
350+
1000U,
351+
100000U,
352+
1U,
353+
udpard_prio_nominal,
354+
99U,
355+
udpard_make_subject_endpoint(67U),
356+
make_scattered(payload.data(), payload.size()),
357+
nullptr));
358+
udpard_tx_poll(&tx, 1001U, UDPARD_IFACE_BITMAP_ALL);
359+
TEST_ASSERT_TRUE(frames.size() > 1U);
360+
361+
// Deliver only the first frame. Stateless mode may accept it if the configured extent is already covered.
362+
deliver(frames.front(), rx_mem.fragment, del, &rx, &port, 2000U);
363+
udpard_rx_poll(&rx, 2001U);
364+
if (expect_accept) {
365+
TEST_ASSERT_EQUAL_size_t(1U, state.count);
366+
TEST_ASSERT_EQUAL_UINT64(0U, rx.errors_transfer_malformed);
367+
TEST_ASSERT_EQUAL_size_t(payload.size(), state.payload_size_wire);
368+
TEST_ASSERT_GREATER_OR_EQUAL_size_t(std::min(extent, payload.size()), state.payload.size());
369+
TEST_ASSERT_LESS_THAN_size_t(payload.size(), state.payload.size());
370+
TEST_ASSERT_EQUAL_MEMORY(payload.data(), state.payload.data(), state.payload.size());
371+
} else {
372+
TEST_ASSERT_EQUAL_size_t(0U, state.count);
373+
TEST_ASSERT_EQUAL_UINT64(1U, rx.errors_transfer_malformed);
374+
}
375+
376+
// Release all resources.
377+
udpard_rx_port_free(&rx, &port);
378+
udpard_tx_free(&tx);
379+
TEST_ASSERT_EQUAL_size_t(0U, tx_alloc_transfer.allocated_fragments);
380+
TEST_ASSERT_EQUAL_size_t(0U, tx_alloc_payload.allocated_fragments);
381+
TEST_ASSERT_EQUAL_size_t(0U, rx_alloc_session.allocated_fragments);
382+
TEST_ASSERT_EQUAL_size_t(0U, rx_alloc_fragment.allocated_fragments);
383+
instrumented_allocator_reset(&tx_alloc_transfer);
384+
instrumented_allocator_reset(&tx_alloc_payload);
385+
instrumented_allocator_reset(&rx_alloc_session);
386+
instrumented_allocator_reset(&rx_alloc_fragment);
387+
}
388+
389+
void test_stateless_multiframe_truncation_small_extent() { test_stateless_multiframe_first_frame_handling(10U, true); }
390+
391+
void test_stateless_multiframe_truncation_zero_extent() { test_stateless_multiframe_first_frame_handling(0U, true); }
392+
393+
void test_stateless_multiframe_rejection_large_extent() { test_stateless_multiframe_first_frame_handling(600U, false); }
394+
246395
} // namespace
247396

248397
void setUp() {}
@@ -253,5 +402,9 @@ int main()
253402
UNITY_BEGIN();
254403
RUN_TEST(test_zero_payload_transfer);
255404
RUN_TEST(test_out_of_order_multiframe_reassembly);
405+
RUN_TEST(test_stateless_single_frame_acceptance);
406+
RUN_TEST(test_stateless_multiframe_truncation_small_extent);
407+
RUN_TEST(test_stateless_multiframe_truncation_zero_extent);
408+
RUN_TEST(test_stateless_multiframe_rejection_large_extent);
256409
return UNITY_END();
257410
}

0 commit comments

Comments
 (0)