Skip to content

Commit 04f644e

Browse files
extend the test suite
1 parent 70e6565 commit 04f644e

7 files changed

Lines changed: 800 additions & 12 deletions

File tree

libudpard/udpard.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

tests/src/test_intrusive_misc.c

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,33 @@
44
/// SPDX-License-Identifier: MIT
55

66
#include <udpard.c> // NOLINT(bugprone-suspicious-include)
7+
#include "helpers.h"
78
#include <unity.h>
9+
#include <string.h>
10+
11+
// Allocates one standalone fragment for intrusive fragment-tree checks.
12+
static udpard_fragment_t* make_fragment(const udpard_mem_t fragment_memory,
13+
const udpard_mem_t payload_memory,
14+
const udpard_deleter_t payload_deleter,
15+
const size_t offset,
16+
const void* const data,
17+
const size_t size)
18+
{
19+
udpard_fragment_t* const out = mem_res_alloc(fragment_memory, sizeof(udpard_fragment_t));
20+
TEST_ASSERT_NOT_NULL(out);
21+
void* payload = NULL;
22+
if (size > 0U) {
23+
payload = mem_res_alloc(payload_memory, size);
24+
TEST_ASSERT_NOT_NULL(payload);
25+
(void)memcpy(payload, data, size);
26+
}
27+
mem_zero(sizeof(*out), out);
28+
out->offset = offset;
29+
out->view = (udpard_bytes_t){ .size = size, .data = payload };
30+
out->origin = (udpard_bytes_mut_t){ .size = size, .data = payload };
31+
out->payload_deleter = payload_deleter;
32+
return out;
33+
}
834

935
static void test_crc_streamed(void)
1036
{
@@ -151,6 +177,69 @@ static void test_list(void)
151177
TEST_ASSERT_NULL(list.tail);
152178
}
153179

180+
static void test_misc_helpers(void)
181+
{
182+
instrumented_allocator_t alloc_frag = { 0 };
183+
instrumented_allocator_t alloc_payload = { 0 };
184+
instrumented_allocator_new(&alloc_frag);
185+
instrumented_allocator_new(&alloc_payload);
186+
const udpard_mem_t mem_frag = instrumented_allocator_make_resource(&alloc_frag);
187+
const udpard_mem_t mem_payload = instrumented_allocator_make_resource(&alloc_payload);
188+
const udpard_deleter_t del_payload = instrumented_allocator_make_deleter(&alloc_payload);
189+
190+
// Check trivial helper branches directly.
191+
udpard_udpip_ep_t endpoints[UDPARD_IFACE_COUNT_MAX] = { 0 };
192+
endpoints[1] = (udpard_udpip_ep_t){ .ip = 0x0A000001U, .port = 9999U };
193+
TEST_ASSERT_TRUE(mem_same(mem_frag, mem_frag));
194+
TEST_ASSERT_FALSE(mem_same(mem_frag, mem_payload));
195+
TEST_ASSERT_EQUAL_UINT16(0U, valid_ep_bitmap(NULL));
196+
TEST_ASSERT_EQUAL_UINT16((uint16_t)(1U << 1U), valid_ep_bitmap(endpoints));
197+
mem_free_payload(del_payload, (udpard_bytes_mut_t){ 0 });
198+
199+
// Exercise memory-resource validation failures.
200+
const udpard_mem_vtable_t missing_alloc = { .base = { .free = instrumented_allocator_free }, .alloc = NULL };
201+
const udpard_mem_vtable_t missing_free = { .base = { .free = NULL }, .alloc = instrumented_allocator_alloc };
202+
TEST_ASSERT_FALSE(mem_validate((udpard_mem_t){ 0 }));
203+
TEST_ASSERT_FALSE(mem_validate((udpard_mem_t){ .vtable = &missing_alloc, .context = &alloc_payload }));
204+
TEST_ASSERT_FALSE(mem_validate((udpard_mem_t){ .vtable = &missing_free, .context = &alloc_payload }));
205+
TEST_ASSERT_TRUE(mem_validate(mem_payload));
206+
207+
// Read across an empty fragment to cover the scattered-reader fast path.
208+
const udpard_bytes_scattered_t tail = { .bytes = { .size = 2U, .data = "CD" }, .next = NULL };
209+
const udpard_bytes_scattered_t mid = { .bytes = { .size = 0U, .data = "" }, .next = &tail };
210+
const udpard_bytes_scattered_t head = { .bytes = { .size = 2U, .data = "AB" }, .next = &mid };
211+
bytes_scattered_reader_t rdr = { .cursor = &head, .position = 0U };
212+
char out[4] = { 0 };
213+
TEST_ASSERT_EQUAL_size_t(4U, bytes_scattered_size(head));
214+
bytes_scattered_read(&rdr, sizeof(out), out);
215+
TEST_ASSERT_EQUAL_MEMORY("ABCD", out, sizeof(out));
216+
217+
// Compare fragment ends explicitly.
218+
udpard_fragment_t probe = { 0 };
219+
probe.offset = 5U;
220+
probe.view.size = 3U;
221+
size_t key = 7U;
222+
TEST_ASSERT_EQUAL_INT32(-1, cavl_compare_fragment_end(&key, &probe.index_offset));
223+
key = 8U;
224+
TEST_ASSERT_EQUAL_INT32(0, cavl_compare_fragment_end(&key, &probe.index_offset));
225+
key = 9U;
226+
TEST_ASSERT_EQUAL_INT32(+1, cavl_compare_fragment_end(&key, &probe.index_offset));
227+
228+
// Free a small tree starting from a child to cover descent and ascent.
229+
udpard_fragment_t* const root = make_fragment(mem_frag, mem_payload, del_payload, 2U, "BB", 2U);
230+
udpard_fragment_t* const left = make_fragment(mem_frag, mem_payload, del_payload, 0U, "AA", 2U);
231+
udpard_fragment_t* const rght = make_fragment(mem_frag, mem_payload, del_payload, 4U, "CC", 2U);
232+
root->index_offset.lr[0] = &left->index_offset;
233+
root->index_offset.lr[1] = &rght->index_offset;
234+
left->index_offset.up = &root->index_offset;
235+
rght->index_offset.up = &root->index_offset;
236+
udpard_fragment_free_all(left, udpard_make_deleter(mem_frag));
237+
TEST_ASSERT_EQUAL_size_t(0U, alloc_frag.allocated_fragments);
238+
TEST_ASSERT_EQUAL_size_t(0U, alloc_payload.allocated_fragments);
239+
instrumented_allocator_reset(&alloc_frag);
240+
instrumented_allocator_reset(&alloc_payload);
241+
}
242+
154243
void setUp(void) {}
155244

156245
void tearDown(void) {}
@@ -160,5 +249,6 @@ int main(void)
160249
UNITY_BEGIN();
161250
RUN_TEST(test_crc_streamed);
162251
RUN_TEST(test_list);
252+
RUN_TEST(test_misc_helpers);
163253
return UNITY_END();
164254
}

0 commit comments

Comments
 (0)