@@ -743,6 +743,198 @@ static void test_rx_fragment_tree_update_a(void)
743743 instrumented_allocator_reset (& alloc_payload );
744744}
745745
746+ /// Exhaustive test for rx_fragment_tree_update with random fragmentation patterns.
747+ /// Tests a fixed payload split into every possible non-empty substring,
748+ /// fed in random order with possible duplicates, and verifies correct completion detection.
749+ static void test_rx_fragment_tree_update_exhaustive (void )
750+ {
751+ instrumented_allocator_t alloc_frag = { 0 };
752+ instrumented_allocator_new (& alloc_frag );
753+ const udpard_mem_resource_t mem_frag = instrumented_allocator_make_resource (& alloc_frag );
754+
755+ instrumented_allocator_t alloc_payload = { 0 };
756+ instrumented_allocator_new (& alloc_payload );
757+ const udpard_mem_resource_t mem_payload = instrumented_allocator_make_resource (& alloc_payload );
758+ const udpard_mem_deleter_t del_payload = instrumented_allocator_make_deleter (& alloc_payload );
759+
760+ const char payload [] = "0123456789" ;
761+ const size_t payload_length = strlen (payload );
762+
763+ // Generate all possible non-empty substrings (offset, length pairs).
764+ // For a string of length N, there are N*(N+1)/2 possible substrings.
765+ typedef struct
766+ {
767+ size_t offset ;
768+ size_t length ;
769+ } substring_t ;
770+
771+ const size_t max_substrings = (payload_length * (payload_length + 1 )) / 2 ;
772+ substring_t substrings [max_substrings ];
773+ size_t substring_count = 0 ;
774+
775+ for (size_t offset = 0 ; offset < payload_length ; offset ++ ) {
776+ for (size_t length = 1 ; length <= (payload_length - offset ); length ++ ) {
777+ substrings [substring_count ].offset = offset ;
778+ substrings [substring_count ].length = length ;
779+ substring_count ++ ;
780+ }
781+ }
782+ TEST_ASSERT_EQUAL_size_t (max_substrings , substring_count );
783+
784+ // Run multiple randomized test iterations to explore different orderings.
785+ // We use fewer iterations to keep test time reasonable (~10 seconds).
786+ const size_t num_iterations = 10000 ;
787+
788+ for (size_t iteration = 0 ; iteration < num_iterations ; iteration ++ ) {
789+ udpard_tree_t * root = NULL ;
790+ size_t cov = 0 ;
791+
792+ // Create a randomized schedule of fragments to feed.
793+ // We'll randomly select which substrings to use and in what order.
794+ // Some may be duplicated, some may be omitted initially.
795+
796+ // Track which bytes have been covered by submitted fragments.
797+ bool byte_covered [10 ] = { false };
798+ bool transfer_complete = false;
799+
800+ // Shuffle the substring indices to get a random order.
801+ size_t schedule [substring_count ];
802+ for (size_t i = 0 ; i < substring_count ; i ++ ) {
803+ schedule [i ] = i ;
804+ }
805+
806+ // Fisher-Yates shuffle
807+ for (size_t i = substring_count - 1 ; i > 0 ; i -- ) {
808+ const size_t j = (size_t )(rand () % (int )(i + 1 ));
809+ const size_t tmp = schedule [i ];
810+ schedule [i ] = schedule [j ];
811+ schedule [j ] = tmp ;
812+ }
813+
814+ // Feed fragments in the shuffled order.
815+ // We stop after we've seen every byte at least once.
816+ for (size_t sched_idx = 0 ; sched_idx < substring_count ; sched_idx ++ ) {
817+ const substring_t sub = substrings [schedule [sched_idx ]];
818+
819+ // Allocate and copy the substring payload.
820+ char * const frag_data = mem_payload .alloc (mem_payload .user , sub .length );
821+ memcpy (frag_data , payload + sub .offset , sub .length );
822+
823+ const rx_frame_base_t frame = { .offset = sub .offset ,
824+ .payload = { .data = frag_data , .size = sub .length },
825+ .origin = { .data = frag_data , .size = sub .length } };
826+
827+ const rx_fragment_tree_update_result_t res =
828+ rx_fragment_tree_update (& root , mem_frag , del_payload , frame , payload_length , payload_length , & cov );
829+
830+ // Update our tracking of covered bytes.
831+ for (size_t i = 0 ; i < sub .length ; i ++ ) {
832+ byte_covered [sub .offset + i ] = true;
833+ }
834+
835+ // Check if all bytes are covered.
836+ bool all_covered = true;
837+ for (size_t i = 0 ; i < payload_length ; i ++ ) {
838+ if (!byte_covered [i ]) {
839+ all_covered = false;
840+ break ;
841+ }
842+ }
843+
844+ if (all_covered ) {
845+ // The function should report completion exactly when all bytes are covered.
846+ TEST_ASSERT_EQUAL (rx_fragment_tree_done , res );
847+ transfer_complete = true;
848+ break ; // Stop feeding more fragments once complete.
849+ }
850+ // The function should NOT report completion if not all bytes are covered.
851+ TEST_ASSERT_EQUAL (rx_fragment_tree_not_done , res );
852+ }
853+
854+ // Verify that we did achieve completion (our shuffled order should have covered everything).
855+ TEST_ASSERT_TRUE (transfer_complete );
856+
857+ // Verify coverage matches the payload length.
858+ TEST_ASSERT_EQUAL_size_t (payload_length , cov );
859+
860+ // Cleanup for this iteration.
861+ udpard_fragment_free_all ((udpard_fragment_t * )root , mem_frag );
862+ TEST_ASSERT_EQUAL_size_t (0 , alloc_frag .allocated_fragments );
863+ TEST_ASSERT_EQUAL_size_t (0 , alloc_payload .allocated_fragments );
864+ }
865+ TEST_ASSERT_EQUAL_size_t (0 , alloc_frag .allocated_fragments );
866+ TEST_ASSERT_EQUAL_size_t (0 , alloc_payload .allocated_fragments );
867+
868+ // Test with duplicates: feed the same fragments multiple times.
869+ for (size_t iteration = 0 ; iteration < 100 ; iteration ++ ) {
870+ udpard_tree_t * root = NULL ;
871+ size_t cov = 0 ;
872+
873+ bool byte_covered [10 ] = { false };
874+ bool transfer_complete = false;
875+
876+ // Create a schedule with duplicates.
877+ const size_t schedule_length = substring_count * 3 ; // 3x duplication factor
878+ size_t schedule [schedule_length ];
879+ for (size_t i = 0 ; i < schedule_length ; i ++ ) {
880+ schedule [i ] = (size_t )(rand () % (int )substring_count );
881+ }
882+
883+ // Feed fragments with duplicates.
884+ for (size_t sched_idx = 0 ; sched_idx < schedule_length ; sched_idx ++ ) {
885+ const substring_t sub = substrings [schedule [sched_idx ]];
886+
887+ char * const frag_data = mem_payload .alloc (mem_payload .user , sub .length );
888+ memcpy (frag_data , payload + sub .offset , sub .length );
889+
890+ const rx_frame_base_t frame = { .offset = sub .offset ,
891+ .payload = { .data = frag_data , .size = sub .length },
892+ .origin = { .data = frag_data , .size = sub .length } };
893+
894+ const rx_fragment_tree_update_result_t res =
895+ rx_fragment_tree_update (& root , mem_frag , del_payload , frame , payload_length , payload_length , & cov );
896+
897+ // Update tracking.
898+ for (size_t i = 0 ; i < sub .length ; i ++ ) {
899+ byte_covered [sub .offset + i ] = true;
900+ }
901+
902+ // Check completion.
903+ bool all_covered = true;
904+ for (size_t i = 0 ; i < payload_length ; i ++ ) {
905+ if (!byte_covered [i ]) {
906+ all_covered = false;
907+ break ;
908+ }
909+ }
910+
911+ if (all_covered && !transfer_complete ) {
912+ // The first time we achieve full coverage, the function should report done.
913+ TEST_ASSERT_EQUAL (rx_fragment_tree_done , res );
914+ transfer_complete = true;
915+ // Continue feeding duplicates to ensure no issues.
916+ } else if (transfer_complete ) {
917+ // Once complete, subsequent fragments may return either done or not_done,
918+ // depending on whether they are accepted or rejected by the heuristic.
919+ // The important thing is that coverage remains at payload_length.
920+ TEST_ASSERT_NOT_EQUAL (rx_fragment_tree_oom , res );
921+ TEST_ASSERT_EQUAL_size_t (payload_length , cov );
922+ } else {
923+ TEST_ASSERT_EQUAL (rx_fragment_tree_not_done , res );
924+ }
925+ }
926+
927+ TEST_ASSERT_TRUE (transfer_complete );
928+ TEST_ASSERT_EQUAL_size_t (payload_length , cov );
929+
930+ udpard_fragment_free_all ((udpard_fragment_t * )root , mem_frag );
931+ TEST_ASSERT_EQUAL_size_t (0 , alloc_frag .allocated_fragments );
932+ TEST_ASSERT_EQUAL_size_t (0 , alloc_payload .allocated_fragments );
933+ }
934+ TEST_ASSERT_EQUAL_size_t (0 , alloc_frag .allocated_fragments );
935+ TEST_ASSERT_EQUAL_size_t (0 , alloc_payload .allocated_fragments );
936+ }
937+
746938static void test_rx_transfer_id_forward_distance (void )
747939{
748940 // Test 1: Same value (distance is 0)
@@ -1026,6 +1218,7 @@ int main(void)
10261218{
10271219 UNITY_BEGIN ();
10281220 RUN_TEST (test_rx_fragment_tree_update_a );
1221+ RUN_TEST (test_rx_fragment_tree_update_exhaustive );
10291222 RUN_TEST (test_rx_transfer_id_forward_distance );
10301223 RUN_TEST (test_rx_transfer_id_window_slide );
10311224 RUN_TEST (test_rx_transfer_id_window_manip );
0 commit comments