| /* | |
| * Copyright 2022-2025 The OpenSSL Project Authors. All Rights Reserved. | |
| * | |
| * Licensed under the Apache License 2.0 (the "License"). You may not use | |
| * this file except in compliance with the License. You can obtain a copy | |
| * in the file LICENSE in the source distribution or at | |
| * https://www.openssl.org/source/license.html | |
| */ | |
| #include <openssl/crypto.h> | |
| #include <openssl/err.h> | |
| #include <assert.h> | |
| #include "internal/priority_queue.h" | |
| #include "internal/safe_math.h" | |
| #include "internal/numbers.h" | |
| OSSL_SAFE_MATH_UNSIGNED(size_t, size_t) | |
| /* | |
| * Fundamental operations: | |
| * Binary Heap Fibonacci Heap | |
| * Get smallest O(1) O(1) | |
| * Delete any O(log n) O(log n) average but worst O(n) | |
| * Insert O(log n) O(1) | |
| * | |
| * Not supported: | |
| * Merge two structures O(log n) O(1) | |
| * Decrease key O(log n) O(1) | |
| * Increase key O(log n) ? | |
| * | |
| * The Fibonacci heap is quite a bit more complicated to implement and has | |
| * larger overhead in practice. We favour the binary heap here. A multi-way | |
| * (ternary or quaternary) heap might elicit a performance advantage via better | |
| * cache access patterns. | |
| */ | |
| struct pq_heap_st { | |
| void *data; /* User supplied data pointer */ | |
| size_t index; /* Constant index in elements[] */ | |
| }; | |
| struct pq_elem_st { | |
| size_t posn; /* Current index in heap[] or link in free list */ | |
| #ifndef NDEBUG | |
| int used; /* Debug flag indicating that this is in use */ | |
| #endif | |
| }; | |
| struct ossl_pqueue_st { | |
| struct pq_heap_st *heap; | |
| struct pq_elem_st *elements; | |
| int (*compare)(const void *, const void *); | |
| size_t htop; /* Highest used heap element */ | |
| size_t hmax; /* Allocated heap & element space */ | |
| size_t freelist; /* Index into elements[], start of free element list */ | |
| }; | |
| /* | |
| * The initial and maximum number of elements in the heap. | |
| */ | |
| static const size_t min_nodes = 8; | |
| 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)); | |
| #ifndef NDEBUG | |
| /* Some basic sanity checking of the data structure */ | |
| #define ASSERT_USED(pq, idx) \ | |
| assert(pq->elements[pq->heap[idx].index].used); \ | |
| assert(pq->elements[pq->heap[idx].index].posn == idx) | |
| #define ASSERT_ELEM_USED(pq, elem) \ | |
| assert(pq->elements[elem].used) | |
| #else | |
| #define ASSERT_USED(pq, idx) | |
| #define ASSERT_ELEM_USED(pq, elem) | |
| #endif | |
| /* | |
| * Calculate the array growth based on the target size. | |
| * | |
| * The growth factor is a rational number and is defined by a numerator | |
| * and a denominator. According to Andrew Koenig in his paper "Why Are | |
| * Vectors Efficient?" from JOOP 11(5) 1998, this factor should be less | |
| * than the golden ratio (1.618...). | |
| * | |
| * We use an expansion factor of 8 / 5 = 1.6 | |
| */ | |
| static ossl_inline size_t compute_pqueue_growth(size_t target, size_t current) | |
| { | |
| int err = 0; | |
| while (current < target) { | |
| if (current >= max_nodes) | |
| return 0; | |
| current = safe_muldiv_size_t(current, 8, 5, &err); | |
| if (err) | |
| return 0; | |
| if (current >= max_nodes) | |
| current = max_nodes; | |
| } | |
| return current; | |
| } | |
| static ossl_inline void pqueue_swap_elem(OSSL_PQUEUE *pq, size_t i, size_t j) | |
| { | |
| struct pq_heap_st *h = pq->heap, t_h; | |
| struct pq_elem_st *e = pq->elements; | |
| ASSERT_USED(pq, i); | |
| ASSERT_USED(pq, j); | |
| t_h = h[i]; | |
| h[i] = h[j]; | |
| h[j] = t_h; | |
| e[h[i].index].posn = i; | |
| e[h[j].index].posn = j; | |
| } | |
| static ossl_inline void pqueue_move_elem(OSSL_PQUEUE *pq, size_t from, size_t to) | |
| { | |
| struct pq_heap_st *h = pq->heap; | |
| struct pq_elem_st *e = pq->elements; | |
| ASSERT_USED(pq, from); | |
| h[to] = h[from]; | |
| e[h[to].index].posn = to; | |
| } | |
| /* | |
| * Force the specified element to the front of the heap. This breaks | |
| * the heap partial ordering pre-condition. | |
| */ | |
| static ossl_inline void pqueue_force_bottom(OSSL_PQUEUE *pq, size_t n) | |
| { | |
| ASSERT_USED(pq, n); | |
| while (n > 0) { | |
| const size_t p = (n - 1) / 2; | |
| ASSERT_USED(pq, p); | |
| pqueue_swap_elem(pq, n, p); | |
| n = p; | |
| } | |
| } | |
| /* | |
| * Move an element down to its correct position to restore the partial | |
| * order pre-condition. | |
| */ | |
| static ossl_inline void pqueue_move_down(OSSL_PQUEUE *pq, size_t n) | |
| { | |
| struct pq_heap_st *h = pq->heap; | |
| ASSERT_USED(pq, n); | |
| while (n > 0) { | |
| const size_t p = (n - 1) / 2; | |
| ASSERT_USED(pq, p); | |
| if (pq->compare(h[n].data, h[p].data) >= 0) | |
| break; | |
| pqueue_swap_elem(pq, n, p); | |
| n = p; | |
| } | |
| } | |
| /* | |
| * Move an element up to its correct position to restore the partial | |
| * order pre-condition. | |
| */ | |
| static ossl_inline void pqueue_move_up(OSSL_PQUEUE *pq, size_t n) | |
| { | |
| struct pq_heap_st *h = pq->heap; | |
| size_t p = n * 2 + 1; | |
| ASSERT_USED(pq, n); | |
| if (pq->htop > p + 1) { | |
| ASSERT_USED(pq, p); | |
| ASSERT_USED(pq, p + 1); | |
| if (pq->compare(h[p].data, h[p + 1].data) > 0) | |
| p++; | |
| } | |
| while (pq->htop > p && pq->compare(h[p].data, h[n].data) < 0) { | |
| ASSERT_USED(pq, p); | |
| pqueue_swap_elem(pq, n, p); | |
| n = p; | |
| p = n * 2 + 1; | |
| if (pq->htop > p + 1) { | |
| ASSERT_USED(pq, p + 1); | |
| if (pq->compare(h[p].data, h[p + 1].data) > 0) | |
| p++; | |
| } | |
| } | |
| } | |
| int ossl_pqueue_push(OSSL_PQUEUE *pq, void *data, size_t *elem) | |
| { | |
| size_t n, m; | |
| if (!ossl_pqueue_reserve(pq, 1)) | |
| return 0; | |
| n = pq->htop++; | |
| m = pq->freelist; | |
| pq->freelist = pq->elements[m].posn; | |
| pq->heap[n].data = data; | |
| pq->heap[n].index = m; | |
| pq->elements[m].posn = n; | |
| #ifndef NDEBUG | |
| pq->elements[m].used = 1; | |
| #endif | |
| pqueue_move_down(pq, n); | |
| if (elem != NULL) | |
| *elem = m; | |
| return 1; | |
| } | |
| void *ossl_pqueue_peek(const OSSL_PQUEUE *pq) | |
| { | |
| if (pq->htop > 0) { | |
| ASSERT_USED(pq, 0); | |
| return pq->heap->data; | |
| } | |
| return NULL; | |
| } | |
| void *ossl_pqueue_pop(OSSL_PQUEUE *pq) | |
| { | |
| void *res; | |
| size_t elem; | |
| if (pq == NULL || pq->htop == 0) | |
| return NULL; | |
| ASSERT_USED(pq, 0); | |
| res = pq->heap->data; | |
| elem = pq->heap->index; | |
| if (--pq->htop != 0) { | |
| pqueue_move_elem(pq, pq->htop, 0); | |
| pqueue_move_up(pq, 0); | |
| } | |
| pq->elements[elem].posn = pq->freelist; | |
| pq->freelist = elem; | |
| #ifndef NDEBUG | |
| pq->elements[elem].used = 0; | |
| #endif | |
| return res; | |
| } | |
| void *ossl_pqueue_remove(OSSL_PQUEUE *pq, size_t elem) | |
| { | |
| size_t n; | |
| if (pq == NULL || elem >= pq->hmax || pq->htop == 0) | |
| return 0; | |
| ASSERT_ELEM_USED(pq, elem); | |
| n = pq->elements[elem].posn; | |
| ASSERT_USED(pq, n); | |
| if (n == pq->htop - 1) { | |
| pq->elements[elem].posn = pq->freelist; | |
| pq->freelist = elem; | |
| #ifndef NDEBUG | |
| pq->elements[elem].used = 0; | |
| #endif | |
| return pq->heap[--pq->htop].data; | |
| } | |
| if (n > 0) | |
| pqueue_force_bottom(pq, n); | |
| return ossl_pqueue_pop(pq); | |
| } | |
| static void pqueue_add_freelist(OSSL_PQUEUE *pq, size_t from) | |
| { | |
| struct pq_elem_st *e = pq->elements; | |
| size_t i; | |
| #ifndef NDEBUG | |
| for (i = from; i < pq->hmax; i++) | |
| e[i].used = 0; | |
| #endif | |
| e[from].posn = pq->freelist; | |
| for (i = from + 1; i < pq->hmax; i++) | |
| e[i].posn = i - 1; | |
| pq->freelist = pq->hmax - 1; | |
| } | |
| int ossl_pqueue_reserve(OSSL_PQUEUE *pq, size_t n) | |
| { | |
| size_t new_max, cur_max; | |
| struct pq_heap_st *h; | |
| struct pq_elem_st *e; | |
| if (pq == NULL) | |
| return 0; | |
| cur_max = pq->hmax; | |
| if (pq->htop + n < cur_max) | |
| return 1; | |
| new_max = compute_pqueue_growth(n + cur_max, cur_max); | |
| if (new_max == 0) { | |
| ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); | |
| return 0; | |
| } | |
| h = OPENSSL_realloc_array(pq->heap, new_max, sizeof(*pq->heap)); | |
| if (h == NULL) | |
| return 0; | |
| pq->heap = h; | |
| e = OPENSSL_realloc_array(pq->elements, new_max, sizeof(*pq->elements)); | |
| if (e == NULL) | |
| return 0; | |
| pq->elements = e; | |
| pq->hmax = new_max; | |
| pqueue_add_freelist(pq, cur_max); | |
| return 1; | |
| } | |
| OSSL_PQUEUE *ossl_pqueue_new(int (*compare)(const void *, const void *)) | |
| { | |
| OSSL_PQUEUE *pq; | |
| if (compare == NULL) | |
| return NULL; | |
| pq = OPENSSL_malloc(sizeof(*pq)); | |
| if (pq == NULL) | |
| return NULL; | |
| pq->compare = compare; | |
| pq->hmax = min_nodes; | |
| pq->htop = 0; | |
| pq->freelist = 0; | |
| pq->heap = OPENSSL_malloc_array(min_nodes, sizeof(*pq->heap)); | |
| pq->elements = OPENSSL_malloc_array(min_nodes, sizeof(*pq->elements)); | |
| if (pq->heap == NULL || pq->elements == NULL) { | |
| ossl_pqueue_free(pq); | |
| return NULL; | |
| } | |
| pqueue_add_freelist(pq, 0); | |
| return pq; | |
| } | |
| void ossl_pqueue_free(OSSL_PQUEUE *pq) | |
| { | |
| if (pq != NULL) { | |
| OPENSSL_free(pq->heap); | |
| OPENSSL_free(pq->elements); | |
| OPENSSL_free(pq); | |
| } | |
| } | |
| void ossl_pqueue_pop_free(OSSL_PQUEUE *pq, void (*freefunc)(void *)) | |
| { | |
| size_t i; | |
| if (pq != NULL) { | |
| for (i = 0; i < pq->htop; i++) | |
| (*freefunc)(pq->heap[i].data); | |
| ossl_pqueue_free(pq); | |
| } | |
| } | |
| size_t ossl_pqueue_num(const OSSL_PQUEUE *pq) | |
| { | |
| return pq != NULL ? pq->htop : 0; | |
| } |