@@ -19,6 +19,12 @@ typedef struct
1919 udpard_tx_feedback_t last ;
2020} feedback_state_t ;
2121
22+ typedef struct
23+ {
24+ size_t count ;
25+ udpard_us_t when [8 ];
26+ } eject_log_t ;
27+
2228static void noop_free (void * const user , const size_t size , void * const pointer )
2329{
2430 (void )user ;
@@ -38,6 +44,16 @@ static bool eject_with_flag(udpard_tx_t* const tx, const udpard_tx_ejection_t ej
3844 return true;
3945}
4046
47+ // Records ejection timestamps for later inspection.
48+ static bool eject_with_log (udpard_tx_t * const tx , const udpard_tx_ejection_t ejection )
49+ {
50+ eject_log_t * const st = (eject_log_t * )tx -> user ;
51+ if ((st != NULL ) && (st -> count < (sizeof (st -> when ) / sizeof (st -> when [0 ])))) {
52+ st -> when [st -> count ++ ] = ejection .now ;
53+ }
54+ return true;
55+ }
56+
4157// Records feedback into the provided state via user_transfer_reference.
4258static void record_feedback (udpard_tx_t * const tx , const udpard_tx_feedback_t fb )
4359{
@@ -451,6 +467,107 @@ static void test_tx_ack_and_scheduler(void)
451467 instrumented_allocator_reset (& alloc );
452468}
453469
470+ static void test_tx_stage_if (void )
471+ {
472+ // Exercises retransmission gating near deadline.
473+ udpard_tx_t tx = { 0 };
474+ tx .ack_baseline_timeout = 10 ;
475+
476+ tx_transfer_t tr ;
477+ mem_zero (sizeof (tr ), & tr );
478+ tr .priority = udpard_prio_nominal ;
479+ tr .deadline = 1000 ;
480+ tr .staged_until = 100 ;
481+
482+ udpard_us_t expected = tr .staged_until ;
483+
484+ tx_stage_if (& tx , & tr );
485+ expected += tx_ack_timeout (tx .ack_baseline_timeout , tr .priority , 0 );
486+ TEST_ASSERT_EQUAL_UINT8 (1 , tr .epoch );
487+ TEST_ASSERT_EQUAL (expected , tr .staged_until );
488+ TEST_ASSERT_NOT_NULL (tx .index_staged );
489+ cavl2_remove (& tx .index_staged , & tr .index_staged );
490+
491+ tx_stage_if (& tx , & tr );
492+ expected += tx_ack_timeout (tx .ack_baseline_timeout , tr .priority , 1 );
493+ TEST_ASSERT_EQUAL_UINT8 (2 , tr .epoch );
494+ TEST_ASSERT_EQUAL (expected , tr .staged_until );
495+ TEST_ASSERT_NOT_NULL (tx .index_staged );
496+ cavl2_remove (& tx .index_staged , & tr .index_staged );
497+
498+ tx_stage_if (& tx , & tr );
499+ expected += tx_ack_timeout (tx .ack_baseline_timeout , tr .priority , 2 );
500+ TEST_ASSERT_EQUAL_UINT8 (3 , tr .epoch );
501+ TEST_ASSERT_EQUAL (expected , tr .staged_until );
502+ TEST_ASSERT_NULL (tx .index_staged );
503+ }
504+
505+ static void test_tx_stage_if_via_tx_push (void )
506+ {
507+ // Tracks retransmission times via the scheduler.
508+ instrumented_allocator_t alloc = { 0 };
509+ instrumented_allocator_new (& alloc );
510+ udpard_tx_mem_resources_t mem = { .transfer = instrumented_allocator_make_resource (& alloc ) };
511+ for (size_t i = 0 ; i < UDPARD_IFACE_COUNT_MAX ; i ++ ) {
512+ mem .payload [i ] = instrumented_allocator_make_resource (& alloc );
513+ }
514+
515+ udpard_tx_t tx = { 0 };
516+ eject_log_t log = { 0 };
517+ feedback_state_t fb = { 0 };
518+ udpard_tx_vtable_t vt = { .eject = eject_with_log };
519+ TEST_ASSERT_TRUE (udpard_tx_new (& tx , 30U , 1U , 4U , mem , & vt ));
520+ tx .user = & log ;
521+ tx .ack_baseline_timeout = 10 ;
522+ udpard_udpip_ep_t dest [UDPARD_IFACE_COUNT_MAX ] = { make_ep (1 ), { 0 } };
523+
524+ TEST_ASSERT_GREATER_THAN_UINT32 (
525+ 0 , udpard_tx_push (& tx , 0 , 500 , udpard_prio_nominal , 77 , dest , 1 , make_scattered (NULL , 0 ), record_feedback , & fb ));
526+
527+ udpard_tx_poll (& tx , 0 , UDPARD_IFACE_MASK_ALL );
528+ udpard_tx_poll (& tx , 160 , UDPARD_IFACE_MASK_ALL );
529+ udpard_tx_poll (& tx , 400 , UDPARD_IFACE_MASK_ALL );
530+
531+ TEST_ASSERT_EQUAL_size_t (2 , log .count );
532+ TEST_ASSERT_EQUAL (0 , log .when [0 ]);
533+ TEST_ASSERT_EQUAL (160 , log .when [1 ]);
534+ TEST_ASSERT_NULL (tx .index_staged );
535+ udpard_tx_free (& tx );
536+ instrumented_allocator_reset (& alloc );
537+ }
538+
539+ static void test_tx_stage_if_short_deadline (void )
540+ {
541+ // Ensures retransmission is skipped when deadline is too close.
542+ instrumented_allocator_t alloc = { 0 };
543+ instrumented_allocator_new (& alloc );
544+ udpard_tx_mem_resources_t mem = { .transfer = instrumented_allocator_make_resource (& alloc ) };
545+ for (size_t i = 0 ; i < UDPARD_IFACE_COUNT_MAX ; i ++ ) {
546+ mem .payload [i ] = instrumented_allocator_make_resource (& alloc );
547+ }
548+
549+ udpard_tx_t tx = { 0 };
550+ eject_log_t log = { 0 };
551+ feedback_state_t fb = { 0 };
552+ udpard_tx_vtable_t vt = { .eject = eject_with_log };
553+ TEST_ASSERT_TRUE (udpard_tx_new (& tx , 31U , 1U , 4U , mem , & vt ));
554+ tx .user = & log ;
555+ tx .ack_baseline_timeout = 10 ;
556+ udpard_udpip_ep_t dest [UDPARD_IFACE_COUNT_MAX ] = { make_ep (1 ), { 0 } };
557+
558+ TEST_ASSERT_GREATER_THAN_UINT32 (
559+ 0 , udpard_tx_push (& tx , 0 , 50 , udpard_prio_nominal , 78 , dest , 1 , make_scattered (NULL , 0 ), record_feedback , & fb ));
560+
561+ udpard_tx_poll (& tx , 0 , UDPARD_IFACE_MASK_ALL );
562+ udpard_tx_poll (& tx , 30 , UDPARD_IFACE_MASK_ALL );
563+ udpard_tx_poll (& tx , 60 , UDPARD_IFACE_MASK_ALL );
564+
565+ TEST_ASSERT_EQUAL_size_t (1 , log .count );
566+ TEST_ASSERT_EQUAL (0 , log .when [0 ]);
567+ udpard_tx_free (& tx );
568+ instrumented_allocator_reset (& alloc );
569+ }
570+
454571// Cancels transfers and reports outcome.
455572static void test_tx_cancel (void )
456573{
@@ -618,6 +735,9 @@ int main(void)
618735 RUN_TEST (test_tx_validation_and_free );
619736 RUN_TEST (test_tx_comparators_and_feedback );
620737 RUN_TEST (test_tx_spool_and_queue_errors );
738+ RUN_TEST (test_tx_stage_if );
739+ RUN_TEST (test_tx_stage_if_via_tx_push );
740+ RUN_TEST (test_tx_stage_if_short_deadline );
621741 RUN_TEST (test_tx_cancel );
622742 RUN_TEST (test_tx_spool_deduplication );
623743 RUN_TEST (test_tx_ack_and_scheduler );
0 commit comments