@@ -27,6 +27,17 @@ static udpard_fragment_t* fragment_at(udpard_tree_t* const root, uint32_t index)
2727 return NULL ;
2828}
2929
30+ static bool fragment_equals (udpard_fragment_t * const frag ,
31+ const size_t offset ,
32+ const size_t size ,
33+ const void * const payload )
34+ {
35+ if ((frag == NULL ) || (frag -> offset != offset ) || (frag -> view .size != size )) {
36+ return false;
37+ }
38+ return (size == 0U ) || (memcmp (frag -> view .data , payload , size ) == 0 );
39+ }
40+
3041/// Allocates the payload on the heap, emulating normal frame reception.
3142static rx_frame_base_t make_frame_base (const udpard_mem_resource_t mem ,
3243 const size_t offset ,
@@ -434,6 +445,302 @@ static void test_rx_fragment_tree_update_a(void)
434445 }
435446 instrumented_allocator_reset (& alloc_frag );
436447 instrumented_allocator_reset (& alloc_payload );
448+
449+ // Multi-frame reassembly test with defragmentation: "abcdefghijklmnopqrst". Split with various MTU:
450+ //
451+ // MTU 4: abcd efgh ijkl mnop qrst
452+ // 0 4 8 12 16
453+ //
454+ // MTU 5: abcde fghij klmno pqrst
455+ // 0 5 10 15
456+ //
457+ // MTU 11: abcdefghijk lmnopqrst
458+ // 0 11
459+ //
460+ // Offset helper:
461+ // abcdefghijklmnopqrst
462+ // 01234567890123456789
463+ // 00000000001111111111
464+ {
465+ udpard_tree_t * root = NULL ;
466+ size_t cov = 0 ;
467+ rx_fragment_tree_update_result_t res = rx_fragment_tree_not_done ;
468+
469+ // Add fragment.
470+ res = rx_fragment_tree_update (& root , //
471+ mem_frag ,
472+ del_payload ,
473+ make_frame_base (mem_payload , 0 , 5 , "abcde" ),
474+ 21 ,
475+ 21 ,
476+ & cov );
477+ TEST_ASSERT_EQUAL (rx_fragment_tree_not_done , res );
478+ TEST_ASSERT_EQUAL_size_t (5 , cov );
479+ TEST_ASSERT_NOT_NULL (root );
480+ TEST_ASSERT (fragment_equals (fragment_at (root , 0 ), 0 , 5 , "abcde" ));
481+ TEST_ASSERT_NULL (fragment_at (root , 1 ));
482+
483+ // Add fragment. Rejected because contained by existing.
484+ res = rx_fragment_tree_update (& root , //
485+ mem_frag ,
486+ del_payload ,
487+ make_frame_base (mem_payload , 0 , 4 , "abcd" ),
488+ 21 ,
489+ 21 ,
490+ & cov );
491+ TEST_ASSERT_EQUAL (rx_fragment_tree_not_done , res );
492+ TEST_ASSERT_EQUAL_size_t (5 , cov );
493+ TEST_ASSERT_NOT_NULL (root );
494+ TEST_ASSERT (fragment_equals (fragment_at (root , 0 ), 0 , 5 , "abcde" ));
495+ TEST_ASSERT_NULL (fragment_at (root , 1 ));
496+
497+ // Add 2 fragments. They cover new ground with a gap but they are small, to be replaced later.
498+ // Resulting state:
499+ // 0 |abcde |
500+ // 1 | ijkl |
501+ // 2 | mnop |
502+ res = rx_fragment_tree_update (& root , //
503+ mem_frag ,
504+ del_payload ,
505+ make_frame_base (mem_payload , 8 , 4 , "ijkl" ),
506+ 21 ,
507+ 21 ,
508+ & cov );
509+ TEST_ASSERT_EQUAL (rx_fragment_tree_not_done , res );
510+ res = rx_fragment_tree_update (& root , //
511+ mem_frag ,
512+ del_payload ,
513+ make_frame_base (mem_payload , 12 , 4 , "mnop" ),
514+ 21 ,
515+ 21 ,
516+ & cov );
517+ TEST_ASSERT_EQUAL (rx_fragment_tree_not_done , res );
518+ TEST_ASSERT_EQUAL_size_t (5 , cov ); // not extended due to a gap
519+ TEST_ASSERT_NOT_NULL (root );
520+ TEST_ASSERT (fragment_equals (fragment_at (root , 0 ), 0 , 5 , "abcde" ));
521+ TEST_ASSERT (fragment_equals (fragment_at (root , 1 ), 8 , 4 , "ijkl" ));
522+ TEST_ASSERT (fragment_equals (fragment_at (root , 2 ), 12 , 4 , "mnop" ));
523+ TEST_ASSERT_NULL (fragment_at (root , 3 ));
524+ TEST_ASSERT_EQUAL_size_t (3 , alloc_frag .allocated_fragments );
525+ TEST_ASSERT_EQUAL_size_t (3 , alloc_payload .allocated_fragments );
526+
527+ // Add another fragment that doesn't add any new information but is accepted anyway because it is larger.
528+ // This may enable defragmentation in the future.
529+ // Resulting state:
530+ // 0 |abcde |
531+ // 1 | ijkl |
532+ // 2 | klmno |
533+ // 3 | mnop |
534+ res = rx_fragment_tree_update (& root , //
535+ mem_frag ,
536+ del_payload ,
537+ make_frame_base (mem_payload , 10 , 5 , "klmno" ),
538+ 21 ,
539+ 21 ,
540+ & cov );
541+ TEST_ASSERT_EQUAL (rx_fragment_tree_not_done , res );
542+ TEST_ASSERT_EQUAL_size_t (5 , cov ); // not extended due to a gap
543+ TEST_ASSERT_NOT_NULL (root );
544+ TEST_ASSERT (fragment_equals (fragment_at (root , 0 ), 0 , 5 , "abcde" ));
545+ TEST_ASSERT (fragment_equals (fragment_at (root , 1 ), 8 , 4 , "ijkl" ));
546+ TEST_ASSERT (fragment_equals (fragment_at (root , 2 ), 10 , 5 , "klmno" ));
547+ TEST_ASSERT (fragment_equals (fragment_at (root , 3 ), 12 , 4 , "mnop" ));
548+ TEST_ASSERT_NULL (fragment_at (root , 4 ));
549+ TEST_ASSERT_EQUAL_size_t (4 , alloc_frag .allocated_fragments );
550+ TEST_ASSERT_EQUAL_size_t (4 , alloc_payload .allocated_fragments );
551+
552+ // Add another fragment that bridges the gap and allows removing ijkl.
553+ // Resulting state:
554+ // 0 |abcde |
555+ // 1 | fghij | replaces the old 1
556+ // 2 | klmno |
557+ // 3 | mnop | kept because it has 'p'
558+ res = rx_fragment_tree_update (& root , //
559+ mem_frag ,
560+ del_payload ,
561+ make_frame_base (mem_payload , 5 , 5 , "fghij" ),
562+ 21 ,
563+ 21 ,
564+ & cov );
565+ TEST_ASSERT_EQUAL (rx_fragment_tree_not_done , res );
566+ TEST_ASSERT_EQUAL_size_t (16 , cov ); // jumps to the end because the gap is covered
567+ TEST_ASSERT_NOT_NULL (root );
568+ TEST_ASSERT (fragment_equals (fragment_at (root , 0 ), 0 , 5 , "abcde" ));
569+ TEST_ASSERT (fragment_equals (fragment_at (root , 1 ), 5 , 5 , "fghij" ));
570+ TEST_ASSERT (fragment_equals (fragment_at (root , 2 ), 10 , 5 , "klmno" ));
571+ TEST_ASSERT (fragment_equals (fragment_at (root , 3 ), 12 , 4 , "mnop" ));
572+ TEST_ASSERT_NULL (fragment_at (root , 4 ));
573+ TEST_ASSERT_EQUAL_size_t (4 , alloc_frag .allocated_fragments );
574+ TEST_ASSERT_EQUAL_size_t (4 , alloc_payload .allocated_fragments );
575+
576+ // Add the last smallest fragment. The transfer is not detected as complete because it is set to 21 bytes.
577+ // Resulting state:
578+ // 0 |abcde |
579+ // 1 | fghij | replaces the old 1
580+ // 2 | klmno |
581+ // 3 | mnop | kept because it has 'p'
582+ // 4 | qrst|
583+ res = rx_fragment_tree_update (& root , //
584+ mem_frag ,
585+ del_payload ,
586+ make_frame_base (mem_payload , 16 , 4 , "qrst" ),
587+ 21 ,
588+ 21 ,
589+ & cov );
590+ TEST_ASSERT_EQUAL (rx_fragment_tree_not_done , res );
591+ TEST_ASSERT_EQUAL_size_t (20 , cov ); // updated
592+ TEST_ASSERT_NOT_NULL (root );
593+ TEST_ASSERT (fragment_equals (fragment_at (root , 0 ), 0 , 5 , "abcde" ));
594+ TEST_ASSERT (fragment_equals (fragment_at (root , 1 ), 5 , 5 , "fghij" ));
595+ TEST_ASSERT (fragment_equals (fragment_at (root , 2 ), 10 , 5 , "klmno" ));
596+ TEST_ASSERT (fragment_equals (fragment_at (root , 3 ), 12 , 4 , "mnop" ));
597+ TEST_ASSERT (fragment_equals (fragment_at (root , 4 ), 16 , 4 , "qrst" ));
598+ TEST_ASSERT_NULL (fragment_at (root , 5 ));
599+ TEST_ASSERT_EQUAL_size_t (5 , alloc_frag .allocated_fragments );
600+ TEST_ASSERT_EQUAL_size_t (5 , alloc_payload .allocated_fragments );
601+
602+ // Send redundant fragments. State unchanged.
603+ res = rx_fragment_tree_update (& root , //
604+ mem_frag ,
605+ del_payload ,
606+ make_frame_base (mem_payload , 4 , 4 , "efgh" ),
607+ 21 ,
608+ 21 ,
609+ & cov );
610+ TEST_ASSERT_EQUAL (rx_fragment_tree_not_done , res );
611+ res = rx_fragment_tree_update (& root , //
612+ mem_frag ,
613+ del_payload ,
614+ make_frame_base (mem_payload , 5 , 5 , "fghij" ),
615+ 21 ,
616+ 21 ,
617+ & cov );
618+ TEST_ASSERT_EQUAL (rx_fragment_tree_not_done , res );
619+ res = rx_fragment_tree_update (& root , //
620+ mem_frag ,
621+ del_payload ,
622+ make_frame_base (mem_payload , 0 , 5 , "abcde" ),
623+ 21 ,
624+ 21 ,
625+ & cov );
626+ TEST_ASSERT_EQUAL (rx_fragment_tree_not_done , res );
627+ TEST_ASSERT_EQUAL_size_t (20 , cov ); // no change
628+ TEST_ASSERT_NOT_NULL (root );
629+ TEST_ASSERT (fragment_equals (fragment_at (root , 0 ), 0 , 5 , "abcde" ));
630+ TEST_ASSERT (fragment_equals (fragment_at (root , 1 ), 5 , 5 , "fghij" ));
631+ TEST_ASSERT (fragment_equals (fragment_at (root , 2 ), 10 , 5 , "klmno" ));
632+ TEST_ASSERT (fragment_equals (fragment_at (root , 3 ), 12 , 4 , "mnop" ));
633+ TEST_ASSERT (fragment_equals (fragment_at (root , 4 ), 16 , 4 , "qrst" ));
634+ TEST_ASSERT_NULL (fragment_at (root , 5 ));
635+ TEST_ASSERT_EQUAL_size_t (5 , alloc_frag .allocated_fragments );
636+ TEST_ASSERT_EQUAL_size_t (5 , alloc_payload .allocated_fragments );
637+
638+ // Add the first max-MTU fragment. Replaces the smaller initial fragments.
639+ // Resulting state:
640+ // 0 |abcdefghijk | replaces 0 and 1
641+ // 1 | klmno | kept because it has 'lmno'
642+ // 2 | mnop | kept because it has 'p'
643+ // 3 | qrst|
644+ res = rx_fragment_tree_update (& root , //
645+ mem_frag ,
646+ del_payload ,
647+ make_frame_base (mem_payload , 0 , 11 , "abcdefghijk" ),
648+ 21 ,
649+ 21 ,
650+ & cov );
651+ TEST_ASSERT_EQUAL (rx_fragment_tree_not_done , res );
652+ TEST_ASSERT_EQUAL_size_t (20 , cov );
653+ TEST_ASSERT_NOT_NULL (root );
654+ TEST_ASSERT (fragment_equals (fragment_at (root , 0 ), 0 , 11 , "abcdefghijk" ));
655+ TEST_ASSERT (fragment_equals (fragment_at (root , 1 ), 10 , 5 , "klmno" ));
656+ TEST_ASSERT (fragment_equals (fragment_at (root , 2 ), 12 , 4 , "mnop" ));
657+ TEST_ASSERT (fragment_equals (fragment_at (root , 3 ), 16 , 4 , "qrst" ));
658+ TEST_ASSERT_NULL (fragment_at (root , 4 ));
659+ TEST_ASSERT_EQUAL_size_t (4 , alloc_frag .allocated_fragments );
660+ TEST_ASSERT_EQUAL_size_t (4 , alloc_payload .allocated_fragments );
661+
662+ // Add the last MTU 5 fragment. Replaces the last two MTU 4 fragments.
663+ // Resulting state:
664+ // 0 |abcdefghijk |
665+ // 1 | klmno | kept because it has 'lmno'
666+ // 2 | pqrst|
667+ res = rx_fragment_tree_update (& root , //
668+ mem_frag ,
669+ del_payload ,
670+ make_frame_base (mem_payload , 15 , 5 , "pqrst" ),
671+ 21 ,
672+ 21 ,
673+ & cov );
674+ TEST_ASSERT_EQUAL (rx_fragment_tree_not_done , res );
675+ TEST_ASSERT_EQUAL_size_t (20 , cov );
676+ TEST_ASSERT_NOT_NULL (root );
677+ TEST_ASSERT (fragment_equals (fragment_at (root , 0 ), 0 , 11 , "abcdefghijk" ));
678+ TEST_ASSERT (fragment_equals (fragment_at (root , 1 ), 10 , 5 , "klmno" ));
679+ TEST_ASSERT (fragment_equals (fragment_at (root , 2 ), 15 , 5 , "pqrst" ));
680+ TEST_ASSERT_NULL (fragment_at (root , 3 ));
681+ TEST_ASSERT_EQUAL_size_t (3 , alloc_frag .allocated_fragments );
682+ TEST_ASSERT_EQUAL_size_t (3 , alloc_payload .allocated_fragments );
683+
684+ // Add the last max-MTU fragment. Replaces the last two fragments.
685+ // Resulting state:
686+ // 0 |abcdefghijk |
687+ // 1 | lmnopqrst|
688+ res = rx_fragment_tree_update (& root , //
689+ mem_frag ,
690+ del_payload ,
691+ make_frame_base (mem_payload , 11 , 9 , "lmnopqrst" ),
692+ 21 ,
693+ 21 ,
694+ & cov );
695+ TEST_ASSERT_EQUAL (rx_fragment_tree_not_done , res );
696+ TEST_ASSERT_EQUAL_size_t (20 , cov );
697+ TEST_ASSERT_NOT_NULL (root );
698+ TEST_ASSERT (fragment_equals (fragment_at (root , 0 ), 0 , 11 , "abcdefghijk" ));
699+ TEST_ASSERT (fragment_equals (fragment_at (root , 1 ), 11 , 9 , "lmnopqrst" ));
700+ TEST_ASSERT_NULL (fragment_at (root , 2 ));
701+ TEST_ASSERT_EQUAL_size_t (2 , alloc_frag .allocated_fragments );
702+ TEST_ASSERT_EQUAL_size_t (2 , alloc_payload .allocated_fragments );
703+
704+ // Replace everything with a single huge fragment.
705+ res = rx_fragment_tree_update (& root , //
706+ mem_frag ,
707+ del_payload ,
708+ make_frame_base (mem_payload , 0 , 20 , "abcdefghijklmnopqrst" ),
709+ 21 ,
710+ 21 ,
711+ & cov );
712+ TEST_ASSERT_EQUAL (rx_fragment_tree_not_done , res );
713+ TEST_ASSERT_EQUAL_size_t (20 , cov );
714+ TEST_ASSERT_NOT_NULL (root );
715+ TEST_ASSERT (fragment_equals (fragment_at (root , 0 ), 0 , 20 , "abcdefghijklmnopqrst" ));
716+ TEST_ASSERT_NULL (fragment_at (root , 1 ));
717+ TEST_ASSERT_EQUAL_size_t (1 , alloc_frag .allocated_fragments );
718+ TEST_ASSERT_EQUAL_size_t (1 , alloc_payload .allocated_fragments );
719+
720+ // One tiny boi will complete the transfer.
721+ res = rx_fragment_tree_update (& root , //
722+ mem_frag ,
723+ del_payload ,
724+ make_frame_base (mem_payload , 19 , 2 , "t-" ),
725+ 21 ,
726+ 21 ,
727+ & cov );
728+ TEST_ASSERT_EQUAL (rx_fragment_tree_done , res );
729+ TEST_ASSERT_EQUAL_size_t (21 , cov );
730+ TEST_ASSERT_NOT_NULL (root );
731+ TEST_ASSERT (fragment_equals (fragment_at (root , 0 ), 0 , 20 , "abcdefghijklmnopqrst" ));
732+ TEST_ASSERT (fragment_equals (fragment_at (root , 1 ), 19 , 2 , "t-" ));
733+ TEST_ASSERT_NULL (fragment_at (root , 2 ));
734+ TEST_ASSERT_EQUAL_size_t (2 , alloc_frag .allocated_fragments );
735+ TEST_ASSERT_EQUAL_size_t (2 , alloc_payload .allocated_fragments );
736+
737+ // Cleanup.
738+ udpard_fragment_free_all ((udpard_fragment_t * )root , mem_frag );
739+ TEST_ASSERT_EQUAL_size_t (0 , alloc_frag .allocated_fragments );
740+ TEST_ASSERT_EQUAL_size_t (0 , alloc_payload .allocated_fragments );
741+ }
742+ instrumented_allocator_reset (& alloc_frag );
743+ instrumented_allocator_reset (& alloc_payload );
437744}
438745
439746static void test_rx_transfer_id_forward_distance (void )
0 commit comments