Skip to content

Commit a90d0c6

Browse files
udpard_fragment_gather
1 parent 2e9e4da commit a90d0c6

File tree

3 files changed

+151
-12
lines changed

3 files changed

+151
-12
lines changed

libudpard/udpard.c

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -146,21 +146,47 @@ void udpard_fragment_free_all(udpard_fragment_t* const frag, const udpard_mem_re
146146

147147
udpard_fragment_t* udpard_fragment_seek(udpard_fragment_t* any_frag, const size_t offset)
148148
{
149-
while (any_frag->index_offset.up != NULL) { // Only if the given node is not already the root.
150-
any_frag = (udpard_fragment_t*)any_frag->index_offset.up;
151-
}
152-
if (offset == 0) { // Common fast path.
153-
return (udpard_fragment_t*)cavl2_min(&any_frag->index_offset);
154-
}
155-
udpard_fragment_t* const f =
156-
(udpard_fragment_t*)cavl2_predecessor(&any_frag->index_offset, &offset, &cavl_compare_fragment_offset);
157-
if ((f != NULL) && ((f->offset + f->view.size) > offset)) {
158-
UDPARD_ASSERT(f->offset <= offset);
159-
return f;
149+
if (any_frag != NULL) {
150+
while (any_frag->index_offset.up != NULL) { // Only if the given node is not already the root.
151+
any_frag = (udpard_fragment_t*)any_frag->index_offset.up;
152+
}
153+
if (offset == 0) { // Common fast path.
154+
return (udpard_fragment_t*)cavl2_min(&any_frag->index_offset);
155+
}
156+
udpard_fragment_t* const f =
157+
(udpard_fragment_t*)cavl2_predecessor(&any_frag->index_offset, &offset, &cavl_compare_fragment_offset);
158+
if ((f != NULL) && ((f->offset + f->view.size) > offset)) {
159+
UDPARD_ASSERT(f->offset <= offset);
160+
return f;
161+
}
160162
}
161163
return NULL;
162164
}
163165

166+
size_t udpard_fragment_gather(const udpard_fragment_t* any_frag,
167+
const size_t destination_size_bytes,
168+
void* const destination)
169+
{
170+
size_t offset = 0;
171+
if ((any_frag != NULL) && (destination != NULL)) {
172+
while (any_frag->index_offset.up != NULL) { // Locate the root.
173+
any_frag = (const udpard_fragment_t*)any_frag->index_offset.up;
174+
}
175+
for (const udpard_fragment_t* frag =
176+
(const udpard_fragment_t*)cavl2_min((udpard_tree_t*)&any_frag->index_offset);
177+
(frag != NULL) && (offset < destination_size_bytes);
178+
frag = (const udpard_fragment_t*)cavl2_next_greater((udpard_tree_t*)&frag->index_offset)) {
179+
UDPARD_ASSERT(frag->offset == offset);
180+
UDPARD_ASSERT(frag->view.data != NULL);
181+
const size_t to_copy = smaller(frag->view.size, destination_size_bytes - offset);
182+
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
183+
(void)memcpy((byte_t*)destination + offset, frag->view.data, to_copy);
184+
offset += to_copy;
185+
}
186+
}
187+
return offset;
188+
}
189+
164190
// --------------------------------------------- CRC ---------------------------------------------
165191

166192
#define CRC_INITIAL 0xFFFFFFFFUL

libudpard/udpard.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,12 +278,21 @@ typedef struct udpard_fragment_t
278278
void udpard_fragment_free_all(udpard_fragment_t* const frag, const udpard_mem_resource_t fragment_memory_resource);
279279

280280
/// Given any fragment in a transfer, returns the fragment that contains the given payload offset.
281-
/// Returns NULL if the offset points beyond the stored payload.
281+
/// Returns NULL if the offset points beyond the stored payload, or if any_frag is NULL.
282282
/// This is also the idiomatic way to find the head of the fragment list when invoked with offset zero.
283283
/// This function accepts any node in the fragment tree, not necessarily the head or the root, and
284284
/// has a logarithmic complexity in the number of fragments, which makes it very efficient.
285285
udpard_fragment_t* udpard_fragment_seek(udpard_fragment_t* any_frag, const size_t offset);
286286

287+
/// Given any fragment in a transfer, copies the entire transfer payload into the specified contiguous buffer.
288+
/// If the total size of all fragments combined exceeds the size of the destination buffer,
289+
/// copying will stop early after the buffer is filled, thus truncating the fragmented data short.
290+
/// The function has no effect and returns zero if the destination buffer is NULL.
291+
/// Returns the number of bytes copied into the contiguous destination buffer.
292+
size_t udpard_fragment_gather(const udpard_fragment_t* any_frag,
293+
const size_t destination_size_bytes,
294+
void* const destination);
295+
287296
// =====================================================================================================================
288297
// ================================================= TX PIPELINE =================================================
289298
// =====================================================================================================================

tests/src/test_public.cpp

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,109 @@ void test_udpard_fragment_seek()
154154
TEST_ASSERT_EQUAL_size_t(0, alloc_payload.allocated_fragments);
155155
}
156156

157+
void test_udpard_fragment_gather()
158+
{
159+
instrumented_allocator_t alloc_frag{};
160+
instrumented_allocator_new(&alloc_frag);
161+
const udpard_mem_resource_t mem_frag = instrumented_allocator_make_resource(&alloc_frag);
162+
163+
instrumented_allocator_t alloc_payload{};
164+
instrumented_allocator_new(&alloc_payload);
165+
const udpard_mem_resource_t mem_payload = instrumented_allocator_make_resource(&alloc_payload);
166+
const udpard_mem_deleter_t del_payload = instrumented_allocator_make_deleter(&alloc_payload);
167+
168+
// Test 1: NULL fragment returns 0.
169+
char buf[100]; // NOLINT(*-avoid-c-arrays)
170+
TEST_ASSERT_EQUAL_size_t(0, udpard_fragment_gather(nullptr, sizeof(buf), static_cast<void*>(buf)));
171+
172+
// Test 2: NULL destination returns 0.
173+
udpard_fragment_t* single = make_test_fragment(mem_frag, mem_payload, del_payload, 0, 5, "hello");
174+
TEST_ASSERT_NOT_NULL(single);
175+
single->index_offset.up = nullptr;
176+
single->index_offset.lr[0] = nullptr;
177+
single->index_offset.lr[1] = nullptr;
178+
single->index_offset.bf = 0;
179+
TEST_ASSERT_EQUAL_size_t(0, udpard_fragment_gather(single, sizeof(buf), nullptr));
180+
181+
// Test 3: Single fragment - gather all.
182+
(void) std::memset(static_cast<void*>(buf), 0, sizeof(buf));
183+
TEST_ASSERT_EQUAL_size_t(5, udpard_fragment_gather(single, sizeof(buf), static_cast<void*>(buf)));
184+
TEST_ASSERT_EQUAL_MEMORY("hello", buf, 5);
185+
186+
// Test 4: Single fragment - truncation (destination smaller than fragment).
187+
(void) std::memset(static_cast<void*>(buf), 0, sizeof(buf));
188+
TEST_ASSERT_EQUAL_size_t(3, udpard_fragment_gather(single, 3, static_cast<void*>(buf)));
189+
TEST_ASSERT_EQUAL_MEMORY("hel", buf, 3);
190+
191+
// Cleanup single fragment.
192+
mem_payload.free(mem_payload.user, single->origin.size, single->origin.data);
193+
mem_frag.free(mem_frag.user, sizeof(udpard_fragment_t), single);
194+
195+
// Test 5: Multiple fragments forming a tree.
196+
// Create tree: root at offset 5 ("MID"), left at offset 0 ("ABCDE"), right at offset 10 ("WXYZ")
197+
// Total payload when gathered: "ABCDE" + "MID" + "WXYZ" = "ABCDEMIDWXYZ" (12 bytes)
198+
udpard_fragment_t* root = make_test_fragment(mem_frag, mem_payload, del_payload, 5, 3, "MID");
199+
udpard_fragment_t* left = make_test_fragment(mem_frag, mem_payload, del_payload, 0, 5, "ABCDE");
200+
udpard_fragment_t* right = make_test_fragment(mem_frag, mem_payload, del_payload, 10, 4, "WXYZ");
201+
TEST_ASSERT_NOT_NULL(root);
202+
TEST_ASSERT_NOT_NULL(left);
203+
TEST_ASSERT_NOT_NULL(right);
204+
205+
// Build tree structure.
206+
root->index_offset.up = nullptr;
207+
root->index_offset.lr[0] = &left->index_offset;
208+
root->index_offset.lr[1] = &right->index_offset;
209+
root->index_offset.bf = 0;
210+
211+
left->index_offset.up = &root->index_offset;
212+
left->index_offset.lr[0] = nullptr;
213+
left->index_offset.lr[1] = nullptr;
214+
left->index_offset.bf = 0;
215+
216+
right->index_offset.up = &root->index_offset;
217+
right->index_offset.lr[0] = nullptr;
218+
right->index_offset.lr[1] = nullptr;
219+
right->index_offset.bf = 0;
220+
221+
// Gather from root - should collect all fragments in order.
222+
(void) std::memset(static_cast<void*>(buf), 0, sizeof(buf));
223+
TEST_ASSERT_EQUAL_size_t(12, udpard_fragment_gather(root, sizeof(buf), static_cast<void*>(buf)));
224+
TEST_ASSERT_EQUAL_MEMORY("ABCDEMIDWXYZ", buf, 12);
225+
226+
// Gather from left child - should still collect all fragments (traverses to root first).
227+
(void) std::memset(static_cast<void*>(buf), 0, sizeof(buf));
228+
TEST_ASSERT_EQUAL_size_t(12, udpard_fragment_gather(left, sizeof(buf), static_cast<void*>(buf)));
229+
TEST_ASSERT_EQUAL_MEMORY("ABCDEMIDWXYZ", buf, 12);
230+
231+
// Gather from right child - should still collect all fragments.
232+
(void) std::memset(static_cast<void*>(buf), 0, sizeof(buf));
233+
TEST_ASSERT_EQUAL_size_t(12, udpard_fragment_gather(right, sizeof(buf), static_cast<void*>(buf)));
234+
TEST_ASSERT_EQUAL_MEMORY("ABCDEMIDWXYZ", buf, 12);
235+
236+
// Test 6: Truncation with multiple fragments - buffer smaller than total.
237+
(void) std::memset(static_cast<void*>(buf), 0, sizeof(buf));
238+
TEST_ASSERT_EQUAL_size_t(7, udpard_fragment_gather(root, 7, static_cast<void*>(buf)));
239+
TEST_ASSERT_EQUAL_MEMORY("ABCDEMI", buf, 7);
240+
241+
// Test 7: Truncation mid-fragment.
242+
(void) std::memset(static_cast<void*>(buf), 0, sizeof(buf));
243+
TEST_ASSERT_EQUAL_size_t(3, udpard_fragment_gather(root, 3, static_cast<void*>(buf)));
244+
TEST_ASSERT_EQUAL_MEMORY("ABC", buf, 3);
245+
246+
// Test 8: Zero-size destination.
247+
TEST_ASSERT_EQUAL_size_t(0, udpard_fragment_gather(root, 0, static_cast<void*>(buf)));
248+
249+
// Cleanup.
250+
mem_payload.free(mem_payload.user, left->origin.size, left->origin.data);
251+
mem_frag.free(mem_frag.user, sizeof(udpard_fragment_t), left);
252+
mem_payload.free(mem_payload.user, root->origin.size, root->origin.data);
253+
mem_frag.free(mem_frag.user, sizeof(udpard_fragment_t), root);
254+
mem_payload.free(mem_payload.user, right->origin.size, right->origin.data);
255+
mem_frag.free(mem_frag.user, sizeof(udpard_fragment_t), right);
256+
TEST_ASSERT_EQUAL_size_t(0, alloc_frag.allocated_fragments);
257+
TEST_ASSERT_EQUAL_size_t(0, alloc_payload.allocated_fragments);
258+
}
259+
157260
} // namespace
158261

159262
extern "C" void setUp() {}
@@ -164,5 +267,6 @@ int main()
164267
{
165268
UNITY_BEGIN();
166269
RUN_TEST(test_udpard_fragment_seek);
270+
RUN_TEST(test_udpard_fragment_gather);
167271
return UNITY_END();
168272
}

0 commit comments

Comments
 (0)