Skip to content

Commit 03dcc2f

Browse files
testing wip
1 parent 5a4df9e commit 03dcc2f

File tree

3 files changed

+79
-20
lines changed

3 files changed

+79
-20
lines changed

libudpard/udpard.c

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,12 @@ void udpard_fragment_free_all(udpard_fragment_t* const frag, const udpard_mem_re
111111
}
112112
// Delete this fragment
113113
udpard_fragment_t* const parent = (udpard_fragment_t*)frag->index_offset.up;
114-
parent->index_offset.lr[parent->index_offset.lr[1] == &frag->index_offset] = NULL;
115114
mem_free_payload(frag->payload_deleter, frag->origin);
116115
mem_free(fragment_memory_resource, sizeof(udpard_fragment_t), frag);
117-
// Ascend the tree, tail call
118-
udpard_fragment_free_all(parent, fragment_memory_resource);
116+
if (parent != NULL) {
117+
parent->index_offset.lr[parent->index_offset.lr[1] == (udpard_tree_t*)frag] = NULL;
118+
udpard_fragment_free_all(parent, fragment_memory_resource); // tail call
119+
}
119120
}
120121
}
121122

@@ -721,7 +722,7 @@ void udpard_tx_free(const udpard_tx_mem_resources_t memory, udpard_tx_item_t* co
721722
/// All but the transfer metadata: fields that change from frame to frame within the same transfer.
722723
typedef struct
723724
{
724-
uint32_t offset; ///< Offset of this fragment's payload within the full transfer payload.
725+
size_t offset; ///< Offset of this fragment's payload within the full transfer payload.
725726
udpard_bytes_t payload; ///< Does not include the header, just pure payload.
726727
udpard_bytes_mut_t origin; ///< The entirety of the free-able buffer passed from the application.
727728
} rx_frame_base_t;
@@ -794,13 +795,15 @@ static bool rx_fragment_is_needed(udpard_tree_t* const root,
794795
const size_t extent)
795796
{
796797
const size_t end = smaller3(frag_offset + frag_size, transfer_size, extent);
797-
return (frag_offset < extent) && rx_fragment_tree_has_gap_in_range(root, frag_offset, end);
798+
// We need to special-case zero-size transfers because they are effectively just a single gap at offset zero.
799+
return ((end == 0) && (frag_offset == 0)) ||
800+
((frag_offset < extent) && rx_fragment_tree_has_gap_in_range(root, frag_offset, end));
798801
}
799802

800803
/// Finds the number of contiguous payload bytes received from offset zero after accepting a new fragment.
801804
/// The transfer is considered fully received when covered_prefix >= min(extent, transfer_payload_size).
802805
/// This should be invoked after the fragment tree accepted a new fragment at frag_offset with frag_size.
803-
/// The complexity is logarithmic if updated after every received frame.
806+
/// The complexity is amortized-logarithmic, worst case is linear in the number of frames in the transfer.
804807
static size_t rx_fragment_tree_update_covered_prefix(udpard_tree_t* const root,
805808
const size_t old_prefix,
806809
const size_t frag_offset,
@@ -830,8 +833,8 @@ typedef enum
830833
/// Takes ownership of the frame payload; either a new fragment is inserted or the payload is freed.
831834
static rx_fragment_tree_update_result_t rx_fragment_tree_update(udpard_tree_t** const root,
832835
const udpard_mem_resource_t fragment_memory,
833-
const rx_frame_base_t frame,
834836
const udpard_mem_deleter_t payload_deleter,
837+
const rx_frame_base_t frame,
835838
const size_t transfer_payload_size,
836839
const size_t extent,
837840
size_t* const covered_prefix_io)
@@ -903,11 +906,13 @@ static rx_fragment_tree_update_result_t rx_fragment_tree_update(udpard_tree_t**
903906
mew->payload_deleter = payload_deleter;
904907

905908
// Remove all redundant fragments before inserting the new one.
906-
while ((frag != NULL) && ((frag->offset + frag->view.size) <= v_right)) {
909+
// No need to repeat tree lookup, we just step through the nodes using the next_greater lookup.
910+
while ((frag != NULL) && (frag->offset >= v_left) && ((frag->offset + frag->view.size) <= v_right)) {
911+
udpard_fragment_t* const next = (udpard_fragment_t*)cavl2_next_greater(&frag->index_offset);
907912
cavl2_remove(root, &frag->index_offset);
908913
mem_free_payload(frag->payload_deleter, frag->origin);
909914
mem_free(fragment_memory, sizeof(udpard_fragment_t), frag);
910-
frag = (udpard_fragment_t*)cavl2_lower_bound(*root, &v_left, &rx_cavl_compare_fragment_offset);
915+
frag = next;
911916
}
912917

913918
// Insert the new fragment.

tests/src/helpers.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ static inline void dummy_free(void* const user, const size_t size, void* const p
5757
#define INSTRUMENTED_ALLOCATOR_CANARY_SIZE 1024U
5858
typedef struct
5959
{
60+
/// Each allocator has its own canary, to catch an attempt to free memory allocated by a different allocator.
6061
uint_least8_t canary[INSTRUMENTED_ALLOCATOR_CANARY_SIZE];
6162
/// The limit can be changed at any moment to control the maximum amount of memory that can be allocated.
6263
/// It may be set to a value less than the currently allocated amount.

tests/src/test_intrusive_rx.c

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,35 @@
77
#include "helpers.h"
88
#include <unity.h>
99

10-
typedef struct fragment_tree_match_item_t
10+
static size_t tree_count(udpard_tree_t* const root) // how many make a forest?
1111
{
12-
size_t offset;
13-
const char* data; ///< A null-terminated string; NULL at the end.
14-
} fragment_tree_match_item_t;
12+
size_t count = 0;
13+
for (udpard_tree_t* p = cavl2_min(root); p != NULL; p = cavl2_next_greater(p)) {
14+
count++;
15+
}
16+
return count;
17+
}
1518

16-
static bool fragment_tree_match(udpard_fragment_t* const head, const fragment_tree_match_item_t items[])
19+
static udpard_fragment_t* fragment_at(udpard_tree_t* const root, uint32_t index)
1720
{
18-
if (head == NULL) {
19-
return (items[0].data == NULL);
21+
for (udpard_fragment_t* it = (udpard_fragment_t*)cavl2_min(root); it != NULL;
22+
it = (udpard_fragment_t*)cavl2_next_greater(&it->index_offset)) {
23+
if (index-- == 0U) {
24+
return it;
25+
}
2026
}
21-
const bool match = (head->offset == items[0].offset) &&
22-
(strlen((const char*)head->view.data) == strlen(items[0].data)) &&
23-
(memcmp(head->view.data, items[0].data, head->view.size) == 0);
24-
return match && fragment_tree_match((udpard_fragment_t*)cavl2_next_greater(&head->index_offset), &items[1]);
27+
return NULL;
28+
}
29+
30+
/// Allocates the payload on the heap, emulating normal transfer reception.
31+
/// The payload shall not contain NUL characters.
32+
static rx_frame_base_t make_frame_base(const udpard_mem_resource_t mem, const size_t offset, const char* const payload)
33+
{
34+
const size_t size = (payload != NULL) ? strlen(payload) : 0U;
35+
void* data = mem.alloc(mem.user, size);
36+
return (rx_frame_base_t){ .offset = offset,
37+
.payload = { .data = data, .size = size },
38+
.origin = { .data = data, .size = size } };
2539
}
2640

2741
static void test_rx_fragment_tree_update_a(void)
@@ -34,6 +48,45 @@ static void test_rx_fragment_tree_update_a(void)
3448
instrumented_allocator_new(&alloc_payload);
3549
const udpard_mem_resource_t mem_payload = instrumented_allocator_make_resource(&alloc_payload);
3650
const udpard_mem_deleter_t del_payload = instrumented_allocator_make_deleter(&alloc_payload);
51+
52+
// Empty payload test
53+
{
54+
udpard_tree_t* root = NULL;
55+
size_t cov = 0;
56+
rx_fragment_tree_update_result_t res = rx_fragment_tree_not_done;
57+
//
58+
res = rx_fragment_tree_update(&root, //
59+
mem_frag,
60+
del_payload,
61+
make_frame_base(mem_payload, 0, ""),
62+
0,
63+
0,
64+
&cov);
65+
TEST_ASSERT_EQUAL(rx_fragment_tree_done, res);
66+
TEST_ASSERT_EQUAL_size_t(0, cov);
67+
TEST_ASSERT_NOT_NULL(root);
68+
TEST_ASSERT_EQUAL(1, tree_count(root));
69+
// Check the retained payload.
70+
TEST_ASSERT_EQUAL_size_t(0, fragment_at(root, 0)->offset);
71+
TEST_ASSERT_EQUAL_size_t(0, fragment_at(root, 0)->view.size);
72+
TEST_ASSERT_NULL(fragment_at(root, 1));
73+
// Check the heap.
74+
TEST_ASSERT_EQUAL_size_t(1, alloc_frag.allocated_fragments);
75+
TEST_ASSERT_EQUAL_size_t(0, alloc_payload.allocated_fragments); // bc payload empty
76+
TEST_ASSERT_EQUAL_size_t(1, alloc_frag.count_alloc);
77+
TEST_ASSERT_EQUAL_size_t(1, alloc_payload.count_alloc);
78+
TEST_ASSERT_EQUAL_size_t(0, alloc_frag.count_free);
79+
TEST_ASSERT_EQUAL_size_t(0, alloc_payload.count_free);
80+
// Free the tree (as in freedom). The free tree is free to manifest its own destiny.
81+
udpard_fragment_free_all((udpard_fragment_t*)root, mem_frag);
82+
// Check the heap.
83+
TEST_ASSERT_EQUAL_size_t(0, alloc_frag.allocated_fragments);
84+
TEST_ASSERT_EQUAL_size_t(0, alloc_payload.allocated_fragments);
85+
TEST_ASSERT_EQUAL_size_t(1, alloc_frag.count_alloc);
86+
TEST_ASSERT_EQUAL_size_t(1, alloc_payload.count_alloc);
87+
TEST_ASSERT_EQUAL_size_t(1, alloc_frag.count_free);
88+
TEST_ASSERT_EQUAL_size_t(0, alloc_payload.count_free); // bc payload empty
89+
}
3790
}
3891

3992
static void test_rx_transfer_id_forward_distance(void)

0 commit comments

Comments
 (0)