Home | History | Annotate | Line # | Download | only in quic
      1      1.1  christos /*
      2      1.1  christos  * Copyright 2022-2023 The OpenSSL Project Authors. All Rights Reserved.
      3      1.1  christos  *
      4      1.1  christos  * Licensed under the Apache License 2.0 (the "License").  You may not use
      5      1.1  christos  * this file except in compliance with the License.  You can obtain a copy
      6      1.1  christos  * in the file LICENSE in the source distribution or at
      7      1.1  christos  * https://www.openssl.org/source/license.html
      8      1.1  christos  */
      9      1.1  christos 
     10      1.1  christos #include "internal/uint_set.h"
     11      1.1  christos #include "internal/common.h"
     12      1.1  christos #include <assert.h>
     13      1.1  christos 
     14      1.1  christos /*
     15      1.1  christos  * uint64_t Integer Sets
     16      1.1  christos  * =====================
     17      1.1  christos  *
     18      1.1  christos  * This data structure supports the following operations:
     19      1.1  christos  *
     20      1.1  christos  *   Insert Range: Adds an inclusive range of integers [start, end]
     21      1.1  christos  *                 to the set. Equivalent to Insert for each number
     22      1.1  christos  *                 in the range.
     23      1.1  christos  *
     24      1.1  christos  *   Remove Range: Removes an inclusive range of integers [start, end]
     25      1.1  christos  *                 from the set. Not all of the range need already be in
     26      1.1  christos  *                 the set, but any part of the range in the set is removed.
     27      1.1  christos  *
     28      1.1  christos  *   Query:        Is an integer in the data structure?
     29      1.1  christos  *
     30      1.1  christos  * The data structure can be iterated.
     31      1.1  christos  *
     32      1.1  christos  * For greater efficiency in tracking large numbers of contiguous integers, we
     33      1.1  christos  * track integer ranges rather than individual integers. The data structure
     34      1.1  christos  * manages a list of integer ranges [[start, end]...]. Internally this is
     35      1.1  christos  * implemented as a doubly linked sorted list of range structures, which are
     36      1.1  christos  * automatically split and merged as necessary.
     37      1.1  christos  *
     38      1.1  christos  * This data structure requires O(n) traversal of the list for insertion,
     39      1.1  christos  * removal and query when we are not adding/removing ranges which are near the
     40      1.1  christos  * beginning or end of the set of ranges. For the applications for which this
     41      1.1  christos  * data structure is used (e.g. QUIC PN tracking for ACK generation), it is
     42      1.1  christos  * expected that the number of integer ranges needed at any given time will
     43      1.1  christos  * generally be small and that most operations will be close to the beginning or
     44      1.1  christos  * end of the range.
     45      1.1  christos  *
     46      1.1  christos  * Invariant: The data structure is always sorted in ascending order by value.
     47      1.1  christos  *
     48      1.1  christos  * Invariant: No two adjacent ranges ever 'border' one another (have no
     49      1.1  christos  *            numerical gap between them) as the data structure always ensures
     50      1.1  christos  *            such ranges are merged.
     51      1.1  christos  *
     52      1.1  christos  * Invariant: No two ranges ever overlap.
     53      1.1  christos  *
     54      1.1  christos  * Invariant: No range [a, b] ever has a > b.
     55      1.1  christos  *
     56      1.1  christos  * Invariant: Since ranges are represented using inclusive bounds, no range
     57      1.1  christos  *            item inside the data structure can represent a span of zero
     58      1.1  christos  *            integers.
     59      1.1  christos  */
     60      1.1  christos void ossl_uint_set_init(UINT_SET *s)
     61      1.1  christos {
     62      1.1  christos     ossl_list_uint_set_init(s);
     63      1.1  christos }
     64      1.1  christos 
     65      1.1  christos void ossl_uint_set_destroy(UINT_SET *s)
     66      1.1  christos {
     67      1.1  christos     UINT_SET_ITEM *x, *xnext;
     68      1.1  christos 
     69      1.1  christos     for (x = ossl_list_uint_set_head(s); x != NULL; x = xnext) {
     70      1.1  christos         xnext = ossl_list_uint_set_next(x);
     71      1.1  christos         OPENSSL_free(x);
     72      1.1  christos     }
     73      1.1  christos }
     74      1.1  christos 
     75      1.1  christos /* Possible merge of x, prev(x) */
     76      1.1  christos static void uint_set_merge_adjacent(UINT_SET *s, UINT_SET_ITEM *x)
     77      1.1  christos {
     78      1.1  christos     UINT_SET_ITEM *xprev = ossl_list_uint_set_prev(x);
     79      1.1  christos 
     80      1.1  christos     if (xprev == NULL)
     81      1.1  christos         return;
     82      1.1  christos 
     83      1.1  christos     if (x->range.start - 1 != xprev->range.end)
     84      1.1  christos         return;
     85      1.1  christos 
     86      1.1  christos     x->range.start = xprev->range.start;
     87      1.1  christos     ossl_list_uint_set_remove(s, xprev);
     88      1.1  christos     OPENSSL_free(xprev);
     89      1.1  christos }
     90      1.1  christos 
     91      1.1  christos static uint64_t u64_min(uint64_t x, uint64_t y)
     92      1.1  christos {
     93      1.1  christos     return x < y ? x : y;
     94      1.1  christos }
     95      1.1  christos 
     96      1.1  christos static uint64_t u64_max(uint64_t x, uint64_t y)
     97      1.1  christos {
     98      1.1  christos     return x > y ? x : y;
     99      1.1  christos }
    100      1.1  christos 
    101      1.1  christos /*
    102      1.1  christos  * Returns 1 if there exists an integer x which falls within both ranges a and
    103      1.1  christos  * b.
    104      1.1  christos  */
    105      1.1  christos static int uint_range_overlaps(const UINT_RANGE *a,
    106  1.1.1.2  christos     const UINT_RANGE *b)
    107      1.1  christos {
    108      1.1  christos     return u64_min(a->end, b->end)
    109      1.1  christos         >= u64_max(a->start, b->start);
    110      1.1  christos }
    111      1.1  christos 
    112      1.1  christos static UINT_SET_ITEM *create_set_item(uint64_t start, uint64_t end)
    113      1.1  christos {
    114      1.1  christos     UINT_SET_ITEM *x = OPENSSL_malloc(sizeof(UINT_SET_ITEM));
    115      1.1  christos 
    116      1.1  christos     if (x == NULL)
    117      1.1  christos         return NULL;
    118      1.1  christos 
    119      1.1  christos     ossl_list_uint_set_init_elem(x);
    120      1.1  christos     x->range.start = start;
    121  1.1.1.2  christos     x->range.end = end;
    122      1.1  christos     return x;
    123      1.1  christos }
    124      1.1  christos 
    125      1.1  christos int ossl_uint_set_insert(UINT_SET *s, const UINT_RANGE *range)
    126      1.1  christos {
    127      1.1  christos     UINT_SET_ITEM *x, *xnext, *z, *zprev, *f;
    128      1.1  christos     uint64_t start = range->start, end = range->end;
    129      1.1  christos 
    130      1.1  christos     if (!ossl_assert(start <= end))
    131      1.1  christos         return 0;
    132      1.1  christos 
    133      1.1  christos     if (ossl_list_uint_set_is_empty(s)) {
    134      1.1  christos         /* Nothing in the set yet, so just add this range. */
    135      1.1  christos         x = create_set_item(start, end);
    136      1.1  christos         if (x == NULL)
    137      1.1  christos             return 0;
    138      1.1  christos         ossl_list_uint_set_insert_head(s, x);
    139      1.1  christos         return 1;
    140      1.1  christos     }
    141      1.1  christos 
    142      1.1  christos     z = ossl_list_uint_set_tail(s);
    143      1.1  christos     if (start > z->range.end) {
    144      1.1  christos         /*
    145      1.1  christos          * Range is after the latest range in the set, so append.
    146      1.1  christos          *
    147      1.1  christos          * Note: The case where the range is before the earliest range in the
    148      1.1  christos          * set is handled as a degenerate case of the final case below. See
    149      1.1  christos          * optimization note (*) below.
    150      1.1  christos          */
    151      1.1  christos         if (z->range.end + 1 == start) {
    152      1.1  christos             z->range.end = end;
    153      1.1  christos             return 1;
    154      1.1  christos         }
    155      1.1  christos 
    156      1.1  christos         x = create_set_item(start, end);
    157      1.1  christos         if (x == NULL)
    158      1.1  christos             return 0;
    159      1.1  christos         ossl_list_uint_set_insert_tail(s, x);
    160      1.1  christos         return 1;
    161      1.1  christos     }
    162      1.1  christos 
    163      1.1  christos     f = ossl_list_uint_set_head(s);
    164      1.1  christos     if (start <= f->range.start && end >= z->range.end) {
    165      1.1  christos         /*
    166      1.1  christos          * New range dwarfs all ranges in our set.
    167      1.1  christos          *
    168      1.1  christos          * Free everything except the first range in the set, which we scavenge
    169      1.1  christos          * and reuse.
    170      1.1  christos          */
    171      1.1  christos         x = ossl_list_uint_set_head(s);
    172      1.1  christos         x->range.start = start;
    173      1.1  christos         x->range.end = end;
    174      1.1  christos         for (x = ossl_list_uint_set_next(x); x != NULL; x = xnext) {
    175      1.1  christos             xnext = ossl_list_uint_set_next(x);
    176      1.1  christos             ossl_list_uint_set_remove(s, x);
    177      1.1  christos         }
    178      1.1  christos         return 1;
    179      1.1  christos     }
    180      1.1  christos 
    181      1.1  christos     /*
    182      1.1  christos      * Walk backwards since we will most often be inserting at the end. As an
    183      1.1  christos      * optimization, test the head node first and skip iterating over the
    184      1.1  christos      * entire list if we are inserting at the start. The assumption is that
    185      1.1  christos      * insertion at the start and end of the space will be the most common
    186      1.1  christos      * operations. (*)
    187      1.1  christos      */
    188      1.1  christos     z = end < f->range.start ? f : z;
    189      1.1  christos 
    190      1.1  christos     for (; z != NULL; z = zprev) {
    191      1.1  christos         zprev = ossl_list_uint_set_prev(z);
    192      1.1  christos 
    193      1.1  christos         /* An existing range dwarfs our new range (optimisation). */
    194      1.1  christos         if (z->range.start <= start && z->range.end >= end)
    195      1.1  christos             return 1;
    196      1.1  christos 
    197      1.1  christos         if (uint_range_overlaps(&z->range, range)) {
    198      1.1  christos             /*
    199      1.1  christos              * Our new range overlaps an existing range, or possibly several
    200      1.1  christos              * existing ranges.
    201      1.1  christos              */
    202      1.1  christos             UINT_SET_ITEM *ovend = z;
    203      1.1  christos 
    204      1.1  christos             ovend->range.end = u64_max(end, z->range.end);
    205      1.1  christos 
    206      1.1  christos             /* Get earliest overlapping range. */
    207      1.1  christos             while (zprev != NULL && uint_range_overlaps(&zprev->range, range)) {
    208      1.1  christos                 z = zprev;
    209      1.1  christos                 zprev = ossl_list_uint_set_prev(z);
    210      1.1  christos             }
    211      1.1  christos 
    212      1.1  christos             ovend->range.start = u64_min(start, z->range.start);
    213      1.1  christos 
    214      1.1  christos             /* Replace sequence of nodes z..ovend with updated ovend only. */
    215      1.1  christos             while (z != ovend) {
    216      1.1  christos                 z = ossl_list_uint_set_next(x = z);
    217      1.1  christos                 ossl_list_uint_set_remove(s, x);
    218      1.1  christos                 OPENSSL_free(x);
    219      1.1  christos             }
    220      1.1  christos             break;
    221      1.1  christos         } else if (end < z->range.start
    222  1.1.1.2  christos             && (zprev == NULL || start > zprev->range.end)) {
    223      1.1  christos             if (z->range.start == end + 1) {
    224      1.1  christos                 /* We can extend the following range backwards. */
    225      1.1  christos                 z->range.start = start;
    226      1.1  christos 
    227      1.1  christos                 /*
    228      1.1  christos                  * If this closes a gap we now need to merge
    229      1.1  christos                  * consecutive nodes.
    230      1.1  christos                  */
    231      1.1  christos                 uint_set_merge_adjacent(s, z);
    232      1.1  christos             } else if (zprev != NULL && zprev->range.end + 1 == start) {
    233      1.1  christos                 /* We can extend the preceding range forwards. */
    234      1.1  christos                 zprev->range.end = end;
    235      1.1  christos 
    236      1.1  christos                 /*
    237      1.1  christos                  * If this closes a gap we now need to merge
    238      1.1  christos                  * consecutive nodes.
    239      1.1  christos                  */
    240      1.1  christos                 uint_set_merge_adjacent(s, z);
    241      1.1  christos             } else {
    242      1.1  christos                 /*
    243      1.1  christos                  * The new interval is between intervals without overlapping or
    244      1.1  christos                  * touching them, so insert between, preserving sort.
    245      1.1  christos                  */
    246      1.1  christos                 x = create_set_item(start, end);
    247      1.1  christos                 if (x == NULL)
    248      1.1  christos                     return 0;
    249      1.1  christos                 ossl_list_uint_set_insert_before(s, z, x);
    250      1.1  christos             }
    251      1.1  christos             break;
    252      1.1  christos         }
    253      1.1  christos     }
    254      1.1  christos 
    255      1.1  christos     return 1;
    256      1.1  christos }
    257      1.1  christos 
    258      1.1  christos int ossl_uint_set_remove(UINT_SET *s, const UINT_RANGE *range)
    259      1.1  christos {
    260      1.1  christos     UINT_SET_ITEM *z, *zprev, *y;
    261      1.1  christos     uint64_t start = range->start, end = range->end;
    262      1.1  christos 
    263      1.1  christos     if (!ossl_assert(start <= end))
    264      1.1  christos         return 0;
    265      1.1  christos 
    266      1.1  christos     /* Walk backwards since we will most often be removing at the end. */
    267      1.1  christos     for (z = ossl_list_uint_set_tail(s); z != NULL; z = zprev) {
    268      1.1  christos         zprev = ossl_list_uint_set_prev(z);
    269      1.1  christos 
    270      1.1  christos         if (start > z->range.end)
    271      1.1  christos             /* No overlapping ranges can exist beyond this point, so stop. */
    272      1.1  christos             break;
    273      1.1  christos 
    274      1.1  christos         if (start <= z->range.start && end >= z->range.end) {
    275      1.1  christos             /*
    276      1.1  christos              * The range being removed dwarfs this range, so it should be
    277      1.1  christos              * removed.
    278      1.1  christos              */
    279      1.1  christos             ossl_list_uint_set_remove(s, z);
    280      1.1  christos             OPENSSL_free(z);
    281      1.1  christos         } else if (start <= z->range.start && end >= z->range.start) {
    282      1.1  christos             /*
    283      1.1  christos              * The range being removed includes start of this range, but does
    284      1.1  christos              * not cover the entire range (as this would be caught by the case
    285      1.1  christos              * above). Shorten the range.
    286      1.1  christos              */
    287      1.1  christos             assert(end < z->range.end);
    288      1.1  christos             z->range.start = end + 1;
    289      1.1  christos         } else if (end >= z->range.end) {
    290      1.1  christos             /*
    291      1.1  christos              * The range being removed includes the end of this range, but does
    292      1.1  christos              * not cover the entire range (as this would be caught by the case
    293      1.1  christos              * above). Shorten the range. We can also stop iterating.
    294      1.1  christos              */
    295      1.1  christos             assert(start > z->range.start);
    296      1.1  christos             assert(start > 0);
    297      1.1  christos             z->range.end = start - 1;
    298      1.1  christos             break;
    299      1.1  christos         } else if (start > z->range.start && end < z->range.end) {
    300      1.1  christos             /*
    301      1.1  christos              * The range being removed falls entirely in this range, so cut it
    302      1.1  christos              * into two. Cases where a zero-length range would be created are
    303      1.1  christos              * handled by the above cases.
    304      1.1  christos              */
    305      1.1  christos             y = create_set_item(end + 1, z->range.end);
    306      1.1  christos             ossl_list_uint_set_insert_after(s, z, y);
    307      1.1  christos             z->range.end = start - 1;
    308      1.1  christos             break;
    309      1.1  christos         } else {
    310      1.1  christos             /* Assert no partial overlap; all cases should be covered above. */
    311      1.1  christos             assert(!uint_range_overlaps(&z->range, range));
    312      1.1  christos         }
    313      1.1  christos     }
    314      1.1  christos 
    315      1.1  christos     return 1;
    316      1.1  christos }
    317      1.1  christos 
    318      1.1  christos int ossl_uint_set_query(const UINT_SET *s, uint64_t v)
    319      1.1  christos {
    320      1.1  christos     UINT_SET_ITEM *x;
    321      1.1  christos 
    322      1.1  christos     if (ossl_list_uint_set_is_empty(s))
    323      1.1  christos         return 0;
    324      1.1  christos 
    325      1.1  christos     for (x = ossl_list_uint_set_tail(s); x != NULL; x = ossl_list_uint_set_prev(x))
    326      1.1  christos         if (x->range.start <= v && x->range.end >= v)
    327      1.1  christos             return 1;
    328      1.1  christos         else if (x->range.end < v)
    329      1.1  christos             return 0;
    330      1.1  christos 
    331      1.1  christos     return 0;
    332      1.1  christos }
    333