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