@@ -114,6 +114,27 @@ static tx_transfer_t* find_transfer_by_id(udpard_tx_t* const tx, const uint64_t
114114 return ((tr != NULL ) && (tr -> transfer_id == transfer_id )) ? tr : NULL ;
115115}
116116
117+ // Counts transfers by transfer-ID and kind.
118+ static size_t count_transfers_by_id_and_kind (udpard_tx_t * const tx , const uint64_t transfer_id , const frame_kind_t kind )
119+ {
120+ if (tx == NULL ) {
121+ return 0 ;
122+ }
123+ size_t count = 0 ;
124+ const tx_key_transfer_id_t key = { .transfer_id = transfer_id , .seq_no = 0 };
125+ for (tx_transfer_t * tr =
126+ CAVL2_TO_OWNER (cavl2_lower_bound (tx -> index_transfer_id , & key , & tx_cavl_compare_transfer_id ),
127+ tx_transfer_t ,
128+ index_transfer_id );
129+ (tr != NULL ) && (tr -> transfer_id == transfer_id );
130+ tr = CAVL2_TO_OWNER (cavl2_next_greater (& tr -> index_transfer_id ), tx_transfer_t , index_transfer_id )) {
131+ if (tr -> kind == kind ) {
132+ count ++ ;
133+ }
134+ }
135+ return count ;
136+ }
137+
117138static void test_bytes_scattered_read (void )
118139{
119140 // Skips empty fragments and spans boundaries.
@@ -509,6 +530,70 @@ static void test_tx_ack_and_scheduler(void)
509530 TEST_ASSERT_NOT_NULL (find_transfer_by_id (& tx_be , 43 ));
510531 udpard_tx_free (& tx_be );
511532
533+ // ACK acceptance skips colliding P2P transfers from other remotes.
534+ udpard_tx_t tx_coll_rx = { 0 };
535+ TEST_ASSERT_TRUE (udpard_tx_new (
536+ & tx_coll_rx ,
537+ 10U ,
538+ 1U ,
539+ 8U ,
540+ mem ,
541+ & (udpard_tx_vtable_t ){ .eject_subject = eject_subject_with_flag , .eject_p2p = eject_p2p_with_flag }));
542+ udpard_rx_t rx_coll = { .tx = & tx_coll_rx };
543+ feedback_state_t fb_a = { 0 };
544+ feedback_state_t fb_b = { 0 };
545+ const uint64_t coll_id = 55 ;
546+ // Insert first colliding transfer.
547+ tx_transfer_t * tr_a = mem_alloc (mem .transfer , sizeof (tx_transfer_t ));
548+ mem_zero (sizeof (* tr_a ), tr_a );
549+ tr_a -> kind = frame_msg_reliable ;
550+ tr_a -> is_p2p = true;
551+ tr_a -> transfer_id = coll_id ;
552+ tr_a -> seq_no = 1 ;
553+ tr_a -> deadline = 10 ;
554+ tr_a -> priority = udpard_prio_fast ;
555+ tr_a -> p2p_remote .uid = 1001 ;
556+ tr_a -> user = make_user_context (& fb_a );
557+ tr_a -> feedback = record_feedback ;
558+ cavl2_find_or_insert (
559+ & tx_coll_rx .index_deadline , tr_a , tx_cavl_compare_deadline , & tr_a -> index_deadline , cavl2_trivial_factory );
560+ cavl2_find_or_insert (& tx_coll_rx .index_transfer_id ,
561+ & (tx_key_transfer_id_t ){ .transfer_id = tr_a -> transfer_id , .seq_no = tr_a -> seq_no },
562+ tx_cavl_compare_transfer_id ,
563+ & tr_a -> index_transfer_id ,
564+ cavl2_trivial_factory );
565+ enlist_head (& tx_coll_rx .agewise , & tr_a -> agewise );
566+ // Insert second colliding transfer with different remote UID.
567+ tx_transfer_t * tr_b = mem_alloc (mem .transfer , sizeof (tx_transfer_t ));
568+ mem_zero (sizeof (* tr_b ), tr_b );
569+ tr_b -> kind = frame_msg_reliable ;
570+ tr_b -> is_p2p = true;
571+ tr_b -> transfer_id = coll_id ;
572+ tr_b -> seq_no = 2 ;
573+ tr_b -> deadline = 10 ;
574+ tr_b -> priority = udpard_prio_fast ;
575+ tr_b -> p2p_remote .uid = 1002 ;
576+ tr_b -> user = make_user_context (& fb_b );
577+ tr_b -> feedback = record_feedback ;
578+ cavl2_find_or_insert (
579+ & tx_coll_rx .index_deadline , tr_b , tx_cavl_compare_deadline , & tr_b -> index_deadline , cavl2_trivial_factory );
580+ cavl2_find_or_insert (& tx_coll_rx .index_transfer_id ,
581+ & (tx_key_transfer_id_t ){ .transfer_id = tr_b -> transfer_id , .seq_no = tr_b -> seq_no },
582+ tx_cavl_compare_transfer_id ,
583+ & tr_b -> index_transfer_id ,
584+ cavl2_trivial_factory );
585+ enlist_head (& tx_coll_rx .agewise , & tr_b -> agewise );
586+ // Accept ack for the second transfer only.
587+ tx_receive_ack (& rx_coll , tr_b -> p2p_remote .uid , coll_id );
588+ TEST_ASSERT_EQUAL_size_t (0 , fb_a .count );
589+ TEST_ASSERT_EQUAL_size_t (1 , fb_b .count );
590+ TEST_ASSERT_EQUAL_size_t (1 , count_transfers_by_id_and_kind (& tx_coll_rx , coll_id , frame_msg_reliable ));
591+ // Accept ack for the first transfer.
592+ tx_receive_ack (& rx_coll , tr_a -> p2p_remote .uid , coll_id );
593+ TEST_ASSERT_EQUAL_size_t (1 , fb_a .count );
594+ TEST_ASSERT_EQUAL_size_t (0 , count_transfers_by_id_and_kind (& tx_coll_rx , coll_id , frame_msg_reliable ));
595+ udpard_tx_free (& tx_coll_rx );
596+
512597 // Ack suppressed when coverage not improved.
513598 udpard_tx_t tx2 = { 0 };
514599 TEST_ASSERT_TRUE (udpard_tx_new (
@@ -559,6 +644,33 @@ static void test_tx_ack_and_scheduler(void)
559644 TEST_ASSERT_NOT_EQUAL (0U , udpard_tx_pending_ifaces (& tx3 ));
560645 udpard_tx_free (& tx3 );
561646
647+ // Ack emission ignores colliding non-ack transfers.
648+ udpard_tx_t tx_coll_ack = { 0 };
649+ TEST_ASSERT_TRUE (udpard_tx_new (
650+ & tx_coll_ack ,
651+ 12U ,
652+ 3U ,
653+ 4U ,
654+ mem ,
655+ & (udpard_tx_vtable_t ){ .eject_subject = eject_subject_with_flag , .eject_p2p = eject_p2p_with_flag }));
656+ rx .tx = & tx_coll_ack ;
657+ rx .errors_ack_tx = 0 ;
658+ TEST_ASSERT_TRUE (udpard_tx_push (& tx_coll_ack ,
659+ 0 ,
660+ 1000 ,
661+ iface_bitmap_01 ,
662+ udpard_prio_fast ,
663+ 60 ,
664+ make_scattered (NULL , 0 ),
665+ record_feedback ,
666+ make_user_context (& fstate )));
667+ TEST_ASSERT_EQUAL_size_t (1 , count_transfers_by_id_and_kind (& tx_coll_ack , 60 , frame_msg_reliable ));
668+ tx_send_ack (& rx , 0 , udpard_prio_fast , 60 , (udpard_remote_t ){ .uid = 77 , .endpoints = { make_ep (7 ) } });
669+ TEST_ASSERT_EQUAL_UINT64 (0 , rx .errors_ack_tx );
670+ TEST_ASSERT_EQUAL_size_t (1 , count_transfers_by_id_and_kind (& tx_coll_ack , 60 , frame_msg_reliable ));
671+ TEST_ASSERT_EQUAL_size_t (1 , count_transfers_by_id_and_kind (& tx_coll_ack , 60 , frame_ack ));
672+ udpard_tx_free (& tx_coll_ack );
673+
562674 // Ack push failure with TX present.
563675 udpard_tx_mem_resources_t fail_mem = { .transfer = { .vtable = & mem_vtable_noop_alloc , .context = NULL } };
564676 for (size_t i = 0 ; i < UDPARD_IFACE_COUNT_MAX ; i ++ ) {
@@ -825,6 +937,61 @@ static void test_tx_cancel(void)
825937 TEST_ASSERT_TRUE (udpard_tx_cancel (& tx , 201 , false));
826938 TEST_ASSERT_EQUAL_size_t (0 , tx .enqueued_frames_count );
827939
940+ // Collisions cancel all reliable transfers with the same ID.
941+ fstate .count = 0 ;
942+ const uint64_t coll_id = 300 ;
943+ TEST_ASSERT_TRUE (udpard_tx_push (& tx ,
944+ 0 ,
945+ 100 ,
946+ iface_bitmap_1 ,
947+ udpard_prio_fast ,
948+ coll_id ,
949+ make_scattered (NULL , 0 ),
950+ record_feedback ,
951+ make_user_context (& fstate )));
952+ TEST_ASSERT_TRUE (udpard_tx_push (& tx ,
953+ 0 ,
954+ 100 ,
955+ iface_bitmap_1 ,
956+ udpard_prio_fast ,
957+ coll_id ,
958+ make_scattered (NULL , 0 ),
959+ record_feedback ,
960+ make_user_context (& fstate )));
961+ TEST_ASSERT_EQUAL_size_t (2 , count_transfers_by_id_and_kind (& tx , coll_id , frame_msg_reliable ));
962+ TEST_ASSERT_TRUE (udpard_tx_cancel (& tx , coll_id , true));
963+ TEST_ASSERT_EQUAL_size_t (0 , count_transfers_by_id_and_kind (& tx , coll_id , frame_msg_reliable ));
964+ TEST_ASSERT_EQUAL_size_t (2 , fstate .count );
965+
966+ // Best-effort collisions do not cancel reliable transfers.
967+ fstate .count = 0 ;
968+ const uint64_t coll_id2 = 301 ;
969+ TEST_ASSERT_TRUE (udpard_tx_push (& tx ,
970+ 0 ,
971+ 100 ,
972+ iface_bitmap_1 ,
973+ udpard_prio_fast ,
974+ coll_id2 ,
975+ make_scattered (NULL , 0 ),
976+ record_feedback ,
977+ make_user_context (& fstate )));
978+ TEST_ASSERT_TRUE (udpard_tx_push (& tx ,
979+ 0 ,
980+ 100 ,
981+ iface_bitmap_1 ,
982+ udpard_prio_fast ,
983+ coll_id2 ,
984+ make_scattered (NULL , 0 ),
985+ NULL ,
986+ UDPARD_USER_CONTEXT_NULL ));
987+ TEST_ASSERT_EQUAL_size_t (1 , count_transfers_by_id_and_kind (& tx , coll_id2 , frame_msg_reliable ));
988+ TEST_ASSERT_EQUAL_size_t (1 , count_transfers_by_id_and_kind (& tx , coll_id2 , frame_msg_best ));
989+ TEST_ASSERT_TRUE (udpard_tx_cancel (& tx , coll_id2 , false));
990+ TEST_ASSERT_EQUAL_size_t (1 , count_transfers_by_id_and_kind (& tx , coll_id2 , frame_msg_reliable ));
991+ TEST_ASSERT_EQUAL_size_t (0 , count_transfers_by_id_and_kind (& tx , coll_id2 , frame_msg_best ));
992+ TEST_ASSERT_TRUE (udpard_tx_cancel (& tx , coll_id2 , true));
993+ TEST_ASSERT_EQUAL_size_t (0 , count_transfers_by_id_and_kind (& tx , coll_id2 , frame_msg_reliable ));
994+
828995 udpard_tx_free (& tx );
829996 instrumented_allocator_reset (& alloc );
830997}
0 commit comments