Home | History | Annotate | Line # | Download | only in ssl
      1 /*
      2  * Copyright 2022-2024 The OpenSSL Project Authors. All Rights Reserved.
      3  *
      4  * Licensed under the Apache License 2.0 (the "License").  You may not use
      5  * this file except in compliance with the License.  You can obtain a copy
      6  * in the file LICENSE in the source distribution or at
      7  * https://www.openssl.org/source/license.html
      8  */
      9 
     10 #include <openssl/crypto.h>
     11 #include <openssl/err.h>
     12 #include <assert.h>
     13 #include "internal/priority_queue.h"
     14 #include "internal/safe_math.h"
     15 #include "internal/numbers.h"
     16 
     17 OSSL_SAFE_MATH_UNSIGNED(size_t, size_t)
     18 
     19 /*
     20  * Fundamental operations:
     21  *                        Binary Heap   Fibonacci Heap
     22  *  Get smallest            O(1)          O(1)
     23  *  Delete any              O(log n)      O(log n) average but worst O(n)
     24  *  Insert                  O(log n)      O(1)
     25  *
     26  * Not supported:
     27  *  Merge two structures    O(log n)      O(1)
     28  *  Decrease key            O(log n)      O(1)
     29  *  Increase key            O(log n)      ?
     30  *
     31  * The Fibonacci heap is quite a bit more complicated to implement and has
     32  * larger overhead in practice.  We favour the binary heap here.  A multi-way
     33  * (ternary or quaternary) heap might elicit a performance advantage via better
     34  * cache access patterns.
     35  */
     36 
     37 struct pq_heap_st {
     38     void *data; /* User supplied data pointer */
     39     size_t index; /* Constant index in elements[] */
     40 };
     41 
     42 struct pq_elem_st {
     43     size_t posn; /* Current index in heap[] or link in free list */
     44 #ifndef NDEBUG
     45     int used; /* Debug flag indicating that this is in use */
     46 #endif
     47 };
     48 
     49 struct ossl_pqueue_st {
     50     struct pq_heap_st *heap;
     51     struct pq_elem_st *elements;
     52     int (*compare)(const void *, const void *);
     53     size_t htop; /* Highest used heap element */
     54     size_t hmax; /* Allocated heap & element space */
     55     size_t freelist; /* Index into elements[], start of free element list */
     56 };
     57 
     58 /*
     59  * The initial and maximum number of elements in the heap.
     60  */
     61 static const size_t min_nodes = 8;
     62 static const size_t max_nodes = SIZE_MAX / (sizeof(struct pq_heap_st) > sizeof(struct pq_elem_st) ? sizeof(struct pq_heap_st) : sizeof(struct pq_elem_st));
     63 
     64 #ifndef NDEBUG
     65 /* Some basic sanity checking of the data structure */
     66 #define ASSERT_USED(pq, idx)                        \
     67     assert(pq->elements[pq->heap[idx].index].used); \
     68     assert(pq->elements[pq->heap[idx].index].posn == idx)
     69 #define ASSERT_ELEM_USED(pq, elem) \
     70     assert(pq->elements[elem].used)
     71 #else
     72 #define ASSERT_USED(pq, idx)
     73 #define ASSERT_ELEM_USED(pq, elem)
     74 #endif
     75 
     76 /*
     77  * Calculate the array growth based on the target size.
     78  *
     79  * The growth factor is a rational number and is defined by a numerator
     80  * and a denominator.  According to Andrew Koenig in his paper "Why Are
     81  * Vectors Efficient?" from JOOP 11(5) 1998, this factor should be less
     82  * than the golden ratio (1.618...).
     83  *
     84  * We use an expansion factor of 8 / 5 = 1.6
     85  */
     86 static ossl_inline size_t compute_pqueue_growth(size_t target, size_t current)
     87 {
     88     int err = 0;
     89 
     90     while (current < target) {
     91         if (current >= max_nodes)
     92             return 0;
     93 
     94         current = safe_muldiv_size_t(current, 8, 5, &err);
     95         if (err)
     96             return 0;
     97         if (current >= max_nodes)
     98             current = max_nodes;
     99     }
    100     return current;
    101 }
    102 
    103 static ossl_inline void pqueue_swap_elem(OSSL_PQUEUE *pq, size_t i, size_t j)
    104 {
    105     struct pq_heap_st *h = pq->heap, t_h;
    106     struct pq_elem_st *e = pq->elements;
    107 
    108     ASSERT_USED(pq, i);
    109     ASSERT_USED(pq, j);
    110 
    111     t_h = h[i];
    112     h[i] = h[j];
    113     h[j] = t_h;
    114 
    115     e[h[i].index].posn = i;
    116     e[h[j].index].posn = j;
    117 }
    118 
    119 static ossl_inline void pqueue_move_elem(OSSL_PQUEUE *pq, size_t from, size_t to)
    120 {
    121     struct pq_heap_st *h = pq->heap;
    122     struct pq_elem_st *e = pq->elements;
    123 
    124     ASSERT_USED(pq, from);
    125 
    126     h[to] = h[from];
    127     e[h[to].index].posn = to;
    128 }
    129 
    130 /*
    131  * Force the specified element to the front of the heap.  This breaks
    132  * the heap partial ordering pre-condition.
    133  */
    134 static ossl_inline void pqueue_force_bottom(OSSL_PQUEUE *pq, size_t n)
    135 {
    136     ASSERT_USED(pq, n);
    137     while (n > 0) {
    138         const size_t p = (n - 1) / 2;
    139 
    140         ASSERT_USED(pq, p);
    141         pqueue_swap_elem(pq, n, p);
    142         n = p;
    143     }
    144 }
    145 
    146 /*
    147  * Move an element down to its correct position to restore the partial
    148  * order pre-condition.
    149  */
    150 static ossl_inline void pqueue_move_down(OSSL_PQUEUE *pq, size_t n)
    151 {
    152     struct pq_heap_st *h = pq->heap;
    153 
    154     ASSERT_USED(pq, n);
    155     while (n > 0) {
    156         const size_t p = (n - 1) / 2;
    157 
    158         ASSERT_USED(pq, p);
    159         if (pq->compare(h[n].data, h[p].data) >= 0)
    160             break;
    161         pqueue_swap_elem(pq, n, p);
    162         n = p;
    163     }
    164 }
    165 
    166 /*
    167  * Move an element up to its correct position to restore the partial
    168  * order pre-condition.
    169  */
    170 static ossl_inline void pqueue_move_up(OSSL_PQUEUE *pq, size_t n)
    171 {
    172     struct pq_heap_st *h = pq->heap;
    173     size_t p = n * 2 + 1;
    174 
    175     ASSERT_USED(pq, n);
    176     if (pq->htop > p + 1) {
    177         ASSERT_USED(pq, p);
    178         ASSERT_USED(pq, p + 1);
    179         if (pq->compare(h[p].data, h[p + 1].data) > 0)
    180             p++;
    181     }
    182     while (pq->htop > p && pq->compare(h[p].data, h[n].data) < 0) {
    183         ASSERT_USED(pq, p);
    184         pqueue_swap_elem(pq, n, p);
    185         n = p;
    186         p = n * 2 + 1;
    187         if (pq->htop > p + 1) {
    188             ASSERT_USED(pq, p + 1);
    189             if (pq->compare(h[p].data, h[p + 1].data) > 0)
    190                 p++;
    191         }
    192     }
    193 }
    194 
    195 int ossl_pqueue_push(OSSL_PQUEUE *pq, void *data, size_t *elem)
    196 {
    197     size_t n, m;
    198 
    199     if (!ossl_pqueue_reserve(pq, 1))
    200         return 0;
    201 
    202     n = pq->htop++;
    203     m = pq->freelist;
    204     pq->freelist = pq->elements[m].posn;
    205 
    206     pq->heap[n].data = data;
    207     pq->heap[n].index = m;
    208 
    209     pq->elements[m].posn = n;
    210 #ifndef NDEBUG
    211     pq->elements[m].used = 1;
    212 #endif
    213     pqueue_move_down(pq, n);
    214     if (elem != NULL)
    215         *elem = m;
    216     return 1;
    217 }
    218 
    219 void *ossl_pqueue_peek(const OSSL_PQUEUE *pq)
    220 {
    221     if (pq->htop > 0) {
    222         ASSERT_USED(pq, 0);
    223         return pq->heap->data;
    224     }
    225     return NULL;
    226 }
    227 
    228 void *ossl_pqueue_pop(OSSL_PQUEUE *pq)
    229 {
    230     void *res;
    231     size_t elem;
    232 
    233     if (pq == NULL || pq->htop == 0)
    234         return NULL;
    235 
    236     ASSERT_USED(pq, 0);
    237     res = pq->heap->data;
    238     elem = pq->heap->index;
    239 
    240     if (--pq->htop != 0) {
    241         pqueue_move_elem(pq, pq->htop, 0);
    242         pqueue_move_up(pq, 0);
    243     }
    244 
    245     pq->elements[elem].posn = pq->freelist;
    246     pq->freelist = elem;
    247 #ifndef NDEBUG
    248     pq->elements[elem].used = 0;
    249 #endif
    250     return res;
    251 }
    252 
    253 void *ossl_pqueue_remove(OSSL_PQUEUE *pq, size_t elem)
    254 {
    255     size_t n;
    256 
    257     if (pq == NULL || elem >= pq->hmax || pq->htop == 0)
    258         return 0;
    259 
    260     ASSERT_ELEM_USED(pq, elem);
    261     n = pq->elements[elem].posn;
    262 
    263     ASSERT_USED(pq, n);
    264 
    265     if (n == pq->htop - 1) {
    266         pq->elements[elem].posn = pq->freelist;
    267         pq->freelist = elem;
    268 #ifndef NDEBUG
    269         pq->elements[elem].used = 0;
    270 #endif
    271         return pq->heap[--pq->htop].data;
    272     }
    273     if (n > 0)
    274         pqueue_force_bottom(pq, n);
    275     return ossl_pqueue_pop(pq);
    276 }
    277 
    278 static void pqueue_add_freelist(OSSL_PQUEUE *pq, size_t from)
    279 {
    280     struct pq_elem_st *e = pq->elements;
    281     size_t i;
    282 
    283 #ifndef NDEBUG
    284     for (i = from; i < pq->hmax; i++)
    285         e[i].used = 0;
    286 #endif
    287     e[from].posn = pq->freelist;
    288     for (i = from + 1; i < pq->hmax; i++)
    289         e[i].posn = i - 1;
    290     pq->freelist = pq->hmax - 1;
    291 }
    292 
    293 int ossl_pqueue_reserve(OSSL_PQUEUE *pq, size_t n)
    294 {
    295     size_t new_max, cur_max;
    296     struct pq_heap_st *h;
    297     struct pq_elem_st *e;
    298 
    299     if (pq == NULL)
    300         return 0;
    301     cur_max = pq->hmax;
    302     if (pq->htop + n < cur_max)
    303         return 1;
    304 
    305     new_max = compute_pqueue_growth(n + cur_max, cur_max);
    306     if (new_max == 0) {
    307         ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
    308         return 0;
    309     }
    310 
    311     h = OPENSSL_realloc(pq->heap, new_max * sizeof(*pq->heap));
    312     if (h == NULL)
    313         return 0;
    314     pq->heap = h;
    315 
    316     e = OPENSSL_realloc(pq->elements, new_max * sizeof(*pq->elements));
    317     if (e == NULL)
    318         return 0;
    319     pq->elements = e;
    320 
    321     pq->hmax = new_max;
    322     pqueue_add_freelist(pq, cur_max);
    323     return 1;
    324 }
    325 
    326 OSSL_PQUEUE *ossl_pqueue_new(int (*compare)(const void *, const void *))
    327 {
    328     OSSL_PQUEUE *pq;
    329 
    330     if (compare == NULL)
    331         return NULL;
    332 
    333     pq = OPENSSL_malloc(sizeof(*pq));
    334     if (pq == NULL)
    335         return NULL;
    336     pq->compare = compare;
    337     pq->hmax = min_nodes;
    338     pq->htop = 0;
    339     pq->freelist = 0;
    340     pq->heap = OPENSSL_malloc(sizeof(*pq->heap) * min_nodes);
    341     pq->elements = OPENSSL_malloc(sizeof(*pq->elements) * min_nodes);
    342     if (pq->heap == NULL || pq->elements == NULL) {
    343         ossl_pqueue_free(pq);
    344         return NULL;
    345     }
    346     pqueue_add_freelist(pq, 0);
    347     return pq;
    348 }
    349 
    350 void ossl_pqueue_free(OSSL_PQUEUE *pq)
    351 {
    352     if (pq != NULL) {
    353         OPENSSL_free(pq->heap);
    354         OPENSSL_free(pq->elements);
    355         OPENSSL_free(pq);
    356     }
    357 }
    358 
    359 void ossl_pqueue_pop_free(OSSL_PQUEUE *pq, void (*freefunc)(void *))
    360 {
    361     size_t i;
    362 
    363     if (pq != NULL) {
    364         for (i = 0; i < pq->htop; i++)
    365             (*freefunc)(pq->heap[i].data);
    366         ossl_pqueue_free(pq);
    367     }
    368 }
    369 
    370 size_t ossl_pqueue_num(const OSSL_PQUEUE *pq)
    371 {
    372     return pq != NULL ? pq->htop : 0;
    373 }
    374