Coverage Report

Created: 2025-12-31 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/util/lst.c
Line
Count
Source
1
/*
2
 *   This program is free software; you can redistribute it and/or modify
3
 *   it under the terms of the GNU General Public License as published by
4
 *   the Free Software Foundation; either version 2 of the License, or
5
 *   (at your option) any later version.
6
 *
7
 *   This program is distributed in the hope that it will be useful,
8
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 *   GNU General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU General Public License
13
 *   along with this program; if not, write to the Free Software
14
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15
 */
16
17
/** Functions for a Leftmost Skeleton Tree
18
 *
19
 * @file src/lib/util/lst.c
20
 *
21
 * @copyright 2021 Network RADIUS SAS (legal@networkradius.com)
22
 */
23
RCSID("$Id: a173f46202faf836251a07bf0b96b006549f37bb $")
24
25
#include <freeradius-devel/util/lst.h>
26
#include <freeradius-devel/util/misc.h>
27
#include <freeradius-devel/util/rand.h>
28
#include <freeradius-devel/util/strerror.h>
29
30
/*
31
 * Leftmost Skeleton Trees are defined in "Stronger Quickheaps" (Gonzalo Navarro,
32
 * Rodrigo Paredes, Patricio V. Poblete, and Peter Sanders) International Journal
33
 * of Foundations of Computer Science, November 2011. As the title suggests, it
34
 * is inspired by quickheaps, and indeed the underlying representation looks
35
 * like a quickheap.
36
 *
37
 * heap/priority queue operations are defined in the paper in terms of LST
38
 * operations.
39
 */
40
41
/*
42
 * The LST as defined in the paper has a fixed size set at creation.
43
 * Here, as with quickheaps, but we want to allow for expansion...
44
 * though given that, as the paper shows, the expected stack depth
45
 * is proportion to the log of the number of items in the LST, expanding
46
 * the pivot stack may be a rare event.
47
 */
48
0
#define INITIAL_CAPACITY  2048
49
50
0
#define is_power_of_2(_n) ((_n) && (((_n) & ((_n) - 1)) == 0))
51
52
typedef unsigned int stack_index_t;
53
54
typedef struct {
55
  stack_index_t depth;    //!< The current stack depth.
56
  unsigned int  size;   //!< The current stack size (number of frames)
57
  fr_lst_index_t  *data;    //!< Array of indices of the pivots (sometimes called roots)
58
} pivot_stack_t;
59
60
struct fr_lst_s {
61
  unsigned int  capacity; //!< Number of elements that will fit
62
  fr_lst_index_t  idx;    //!< Starting index, initially zero
63
  unsigned int  num_elements; //!< Number of elements in the LST
64
  size_t    offset;   //!< Offset of heap index in element structure.
65
  void    **p;    //!< Array of elements.
66
  pivot_stack_t s;    //!< Stack of pivots, always with depth >= 1.
67
  fr_fast_rand_t  rand_ctx; //!< Seed for random choices.
68
  char const  *type;    //!< Type of elements.
69
  fr_cmp_t  cmp;    //!< Comparator function.
70
};
71
72
static inline fr_lst_index_t stack_item(pivot_stack_t const *s, stack_index_t idx) CC_HINT(always_inline, nonnull);
73
static inline stack_index_t lst_length(fr_lst_t const *lst, stack_index_t stack_index) CC_HINT(always_inline, nonnull);
74
75
static inline CC_HINT(always_inline, nonnull) void *index_addr(fr_lst_t const *lst, void *data)
76
0
{
77
0
  return ((uint8_t *)data) + (lst)->offset;
78
0
}
79
80
/*
81
 * Concerning item_index() and item_index_set():
82
 * To let zero be the value *as stored in an item* that indicates not being in an LST,
83
 * we add one to the real index when storing it and subtract one when retrieving it.
84
 *
85
 * This lets the LST functions use item indices in [0, lst->capacity), important for
86
 * 1. the circular array, which allows an important optimization for fr_lst_pop()
87
 * 2. quick reduction of indices
88
 *
89
 * fr_item_insert() needs to see the value actually stored, hence raw_item_index().
90
 */
91
static inline CC_HINT(always_inline, nonnull) fr_lst_index_t raw_item_index(fr_lst_t const *lst, void *data)
92
0
{
93
0
  return *(fr_lst_index_t *)index_addr(lst, data);
94
0
}
95
96
static inline CC_HINT(always_inline, nonnull) fr_lst_index_t item_index(fr_lst_t const *lst, void *data)
97
0
{
98
0
  return  raw_item_index(lst, data) - 1;
99
0
}
100
101
static inline CC_HINT(always_inline, nonnull) void item_index_set(fr_lst_t *lst, void *data, fr_lst_index_t idx)
102
0
{
103
0
  (*(fr_lst_index_t *)index_addr(lst, data)) = idx + 1;
104
0
}
105
106
static inline CC_HINT(always_inline, nonnull) fr_lst_index_t index_reduce(fr_lst_t const *lst, fr_lst_index_t idx)
107
0
{
108
0
  return idx & ((lst)->capacity - 1);
109
0
}
110
111
static inline CC_HINT(always_inline, nonnull)
112
bool is_equivalent(fr_lst_t const *lst, fr_lst_index_t idx1, fr_lst_index_t idx2)
113
0
{
114
0
  return (index_reduce(lst, idx1 - idx2) == 0);
115
0
}
116
117
static inline CC_HINT(always_inline, nonnull) void item_set(fr_lst_t *lst, fr_lst_index_t idx, void *data)
118
0
{
119
0
  lst->p[index_reduce(lst, idx)] = data;
120
0
}
121
122
static inline CC_HINT(always_inline, nonnull) void *item(fr_lst_t const *lst, fr_lst_index_t idx)
123
0
{
124
0
  return (lst->p[index_reduce(lst, idx)]);
125
0
}
126
127
static inline CC_HINT(always_inline, nonnull) void *pivot_item(fr_lst_t const *lst, stack_index_t idx)
128
0
{
129
0
  return item(lst, stack_item(&lst->s, idx));
130
0
}
131
132
/*
133
 * The paper defines randomized priority queue operations appropriately for the
134
 * sum type definition the authors use for LSTs, which are used to implement the
135
 * RPQ operations. This code, however, deals with the internal representation,
136
 * including the root/pivot stack, which must change as the LST changes. Also, an
137
 * insertion or deletion may shift the position of any number of buckets or change
138
 * the number of buckets.
139
 *
140
 * So... for those operations, we will pass in the pointer to the LST, but
141
 * internally, we'll represent it and its subtrees with an (LST pointer, stack index)
142
 * pair. The index is that of the least pivot greater than or equal to all items in
143
 * the subtree (considering the "fictitious" pivot greater than anything, so (lst, 0)
144
 * represents the entire tree.
145
 *
146
 * The fictitious pivot at the bottom of the stack isn't actually in the array,
147
 * so don't try to refer to what's there.
148
 *
149
 * The index is visible for the size and length functions, since they need
150
 * to know the subtree they're working on.
151
 */
152
static inline CC_HINT(always_inline, nonnull) bool is_bucket(fr_lst_t const *lst, stack_index_t idx)
153
0
{
154
0
  return lst_length(lst, idx) == 1;
155
0
}
156
157
static bool stack_expand(fr_lst_t *lst, pivot_stack_t *s)
158
0
{
159
0
  fr_lst_index_t  *n;
160
0
  unsigned int  n_size;
161
162
0
#ifndef NDEBUG
163
  /*
164
   *  This likely can't happen, we just include
165
   *  the guard to keep static analysis happy.
166
   */
167
0
  if (unlikely(s->size > (UINT_MAX - s->size))) {
168
0
    if (s->size == UINT_MAX) {
169
0
      fr_strerror_const("lst stack is full");
170
0
      return false;
171
0
    } else {
172
0
      n_size = UINT_MAX;
173
0
    }
174
0
  } else {
175
0
#endif
176
0
    n_size = s->size * 2;
177
0
#ifndef NDEBUG
178
0
  }
179
0
#endif
180
181
0
  n = talloc_realloc(lst, s->data, fr_lst_index_t, n_size);
182
0
  if (unlikely(!n)) {
183
0
    fr_strerror_printf("Failed expanding lst stack to %u elements (%zu bytes)",
184
0
           n_size, n_size * sizeof(fr_lst_index_t));
185
0
    return false;
186
0
  }
187
188
0
  s->size = n_size;
189
0
  s->data = n;
190
0
  return true;
191
0
}
192
193
static inline CC_HINT(always_inline, nonnull) int stack_push(fr_lst_t *lst, pivot_stack_t *s, fr_lst_index_t pivot)
194
0
{
195
0
  if (unlikely(s->depth == s->size && !stack_expand(lst, s))) return -1;
196
197
0
  s->data[s->depth++] = pivot;
198
0
  return 0;
199
0
}
200
201
static inline CC_HINT(always_inline, nonnull) void stack_pop(pivot_stack_t *s, unsigned int n)
202
0
{
203
0
  s->depth -= n;
204
0
}
205
206
static inline CC_HINT(always_inline, nonnull) stack_index_t stack_depth(pivot_stack_t const *s)
207
0
{
208
0
  return s->depth;
209
0
}
210
211
static inline fr_lst_index_t stack_item(pivot_stack_t const *s, stack_index_t idx)
212
0
{
213
0
  return s->data[idx];
214
0
}
215
216
static inline CC_HINT(always_inline, nonnull)
217
void stack_set(pivot_stack_t *s, stack_index_t idx, fr_lst_index_t new_value)
218
0
{
219
0
  s->data[idx] = new_value;
220
0
}
221
222
fr_lst_t *_fr_lst_alloc(TALLOC_CTX *ctx, fr_cmp_t cmp, char const *type, size_t offset, fr_lst_index_t init)
223
0
{
224
0
  fr_lst_t  *lst;
225
0
  pivot_stack_t *s;
226
0
  unsigned int  initial_stack_capacity;
227
228
0
  if (!init) {
229
0
    init = INITIAL_CAPACITY;
230
0
  } else if (!is_power_of_2(init)) {
231
0
    init = 1 << fr_high_bit_pos(init);
232
0
  }
233
234
0
  for (initial_stack_capacity = 1; (1U << initial_stack_capacity) < init; initial_stack_capacity++) ;
235
236
  /*
237
   *  Pre-allocate stack memory as it is
238
   *  unlikely to need to grow in practice.
239
   *
240
   *  We don't pre-allocate the array of elements
241
   *  If we pre-allocated the array of elements
242
   *  we'd end up wasting that memory as soon as
243
   *  we needed to expand the array.
244
   *
245
   *  Pre-allocating three chunks appears to be
246
   *  the optimum.
247
   */
248
0
  lst = talloc_zero_pooled_object(ctx, fr_lst_t, 3, (initial_stack_capacity * sizeof(fr_lst_index_t)));
249
0
  if (unlikely(!lst)) return NULL;
250
251
0
  lst->capacity = init;
252
0
  lst->p = talloc_array(lst, void *, lst->capacity);
253
0
  if (unlikely(!lst->p)) {
254
0
  cleanup:
255
0
    talloc_free(lst);
256
0
    return NULL;
257
0
  }
258
259
  /*
260
   *  Allocate the initial stack
261
   */
262
0
  s = &lst->s;
263
0
  s->data = talloc_array(lst, fr_lst_index_t, initial_stack_capacity);
264
0
  if (unlikely(!s->data)) goto cleanup;
265
0
  s->depth = 0;
266
0
  s->size = initial_stack_capacity;
267
268
  /* Initially the LST is empty and we start at the beginning of the array */
269
0
  stack_push(lst, &lst->s, 0);
270
271
0
  lst->idx = 0;
272
273
  /* Prepare for random choices */
274
0
  lst->rand_ctx.a = fr_rand();
275
0
  lst->rand_ctx.b = fr_rand();
276
277
0
  lst->type = type;
278
0
  lst->cmp = cmp;
279
0
  lst->offset = offset;
280
281
0
  return lst;
282
0
}
283
284
/** The length function for LSTs (how many buckets it contains)
285
 *
286
 */
287
static inline stack_index_t lst_length(fr_lst_t const *lst, stack_index_t stack_index)
288
0
{
289
0
  return stack_depth(&lst->s) - stack_index;
290
0
}
291
292
/** The size function for LSTs (number of items a (sub)tree contains)
293
 *
294
 */
295
static CC_HINT(nonnull) fr_lst_index_t lst_size(fr_lst_t *lst, stack_index_t stack_index)
296
0
{
297
0
  fr_lst_index_t  reduced_right, reduced_idx;
298
299
0
  if (stack_index == 0) return lst->num_elements;
300
301
0
  reduced_right = index_reduce(lst, stack_item(&lst->s, stack_index));
302
0
  reduced_idx = index_reduce(lst, lst->idx);
303
304
0
  if (reduced_idx <= reduced_right) return reduced_right - reduced_idx; /* No wraparound--easy. */
305
306
0
  return (lst->capacity - reduced_idx) + reduced_right;
307
0
}
308
309
/** Flatten an LST, i.e. turn it into the base-case one bucket [sub]tree
310
 *
311
 * NOTE: so doing leaves the passed stack_index valid--we just add
312
 * everything once in the left subtree to it.
313
 */
314
static inline CC_HINT(always_inline, nonnull) void lst_flatten(fr_lst_t *lst, stack_index_t stack_index)
315
0
{
316
0
  stack_pop(&lst->s, stack_depth(&lst->s) - stack_index);
317
0
}
318
319
/** Move data to a specific location in an LST's array.
320
 *
321
 * The caller must have made sure the location is available and exists
322
 * in said array.
323
 */
324
static inline CC_HINT(always_inline, nonnull) void lst_move(fr_lst_t *lst, fr_lst_index_t location, void *data)
325
0
{
326
0
  item_set(lst, location, data);
327
0
  item_index_set(lst, data, index_reduce(lst, location));
328
0
}
329
330
/** Add data to the bucket of a specified (sub)tree.
331
 *
332
 */
333
static void bucket_add(fr_lst_t *lst, stack_index_t stack_index, void *data)
334
0
{
335
0
  fr_lst_index_t  new_space;
336
0
  stack_index_t ridx;
337
338
  /*
339
   * For each bucket to the right, starting from the top,
340
   * make a space available at the top and move the bottom item
341
   * into it. Since ordering within a bucket doesn't matter, we
342
   * can do that, minimizing moving and index adjustment.
343
   *
344
   * The fictitious pivot doesn't correspond to an actual value,
345
   * so we save pivot moving for the end of the loop.
346
   */
347
0
  for (ridx = 0; ridx < stack_index; ridx++) {
348
0
    fr_lst_index_t  prev_pivot_index = stack_item(&lst->s, ridx + 1);
349
0
    bool    empty_bucket;
350
351
0
    new_space = stack_item(&lst->s, ridx);
352
0
    empty_bucket = (new_space - prev_pivot_index) == 1;
353
0
    stack_set(&lst->s, ridx, new_space + 1);
354
355
0
    if (!empty_bucket) lst_move(lst, new_space, item(lst, prev_pivot_index + 1));
356
357
    /* move the pivot up, leaving space for the next bucket */
358
0
    lst_move(lst, prev_pivot_index + 1, item(lst, prev_pivot_index));
359
0
  }
360
361
  /*
362
   * If the bucket isn't the leftmost, the above loop has made space
363
   * available where the pivot used to be.
364
   * If it is the leftmost, the loop wasn't executed, but the fictitious
365
   * pivot isn't there, which is just as good.
366
   */
367
0
  new_space = stack_item(&lst->s, stack_index);
368
0
  stack_set(&lst->s, stack_index, new_space + 1);
369
0
  lst_move(lst, new_space, data);
370
371
0
  lst->num_elements++;
372
0
}
373
374
/** Reduce pivot stack indices based on their difference from lst->idx, and then reduce lst->idx
375
 *
376
 */
377
static void lst_indices_reduce(fr_lst_t *lst)
378
0
{
379
0
  fr_lst_index_t  reduced_idx = index_reduce(lst, lst->idx);
380
0
  stack_index_t depth = stack_depth(&lst->s), i;
381
382
0
  for (i = 0; i < depth; i++) stack_set(&lst->s, i, reduced_idx + stack_item(&lst->s, i) - lst->idx);
383
384
0
  lst->idx = reduced_idx;
385
0
}
386
387
/** Make more space available in an LST
388
 *
389
 * The LST paper only mentions this option in passing, pointing out that it's O(n); the only
390
 * constructor in the paper lets you hand it an array of items to initially insert
391
 * in the LST, so elements will have to be removed to make room for more (though it's
392
 * easy to see how one could specify extra space).
393
 *
394
 * Were it not for the circular array optimization, it would be talloc_realloc() and done;
395
 * it works or it doesn't. (That's still O(n), since it may require copying the data.)
396
 *
397
 * With the circular array optimization, if lst->idx refers to something other than the
398
 * beginning of the array, you have to move the elements preceding it to beginning of the
399
 * newly-available space so it's still contiguous, and keep pivot stack entries consistent
400
 * with the positions of the elements.
401
 */
402
static bool lst_expand(fr_lst_t *lst)
403
0
{
404
0
  void    **n;
405
0
  unsigned int  old_capacity = lst->capacity, n_capacity;
406
0
  fr_lst_index_t  i;
407
408
0
  if (unlikely(old_capacity > (UINT_MAX - old_capacity))) {
409
0
    if (old_capacity == UINT_MAX) {
410
0
      fr_strerror_const("lst is full");
411
0
      return false;
412
0
    } else {
413
0
      n_capacity = UINT_MAX;
414
0
    }
415
0
  } else {
416
0
    n_capacity = old_capacity * 2;
417
0
  }
418
419
0
  n = talloc_realloc(lst, lst->p, void *, n_capacity);
420
0
  if (unlikely(!n)) {
421
0
    fr_strerror_printf("Failed expanding lst to %u elements (%zu bytes)",
422
0
           n_capacity, n_capacity * sizeof(void *));
423
0
    return false;
424
0
  }
425
426
0
  lst->p = n;
427
0
  lst->capacity = n_capacity;
428
429
0
  lst_indices_reduce(lst);
430
431
0
  for (i = 0; i < lst->idx; i++) {
432
0
    void    *to_be_moved = item(lst, i);
433
0
    fr_lst_index_t  new_index = item_index(lst, to_be_moved) + old_capacity;
434
435
0
    lst_move(lst, new_index, to_be_moved);
436
0
  }
437
438
0
  return true;
439
0
}
440
441
static inline CC_HINT(always_inline, nonnull) fr_lst_index_t bucket_lwb(fr_lst_t const *lst, stack_index_t stack_index)
442
0
{
443
0
  if (is_bucket(lst, stack_index)) return lst->idx;
444
445
0
  return stack_item(&lst->s, stack_index + 1) + 1;
446
0
}
447
448
/*
449
 * Note: buckets can be empty,
450
 */
451
static inline CC_HINT(always_inline, nonnull) fr_lst_index_t bucket_upb(fr_lst_t const *lst, stack_index_t stack_index)
452
0
{
453
0
  return stack_item(&lst->s, stack_index) - 1;
454
0
}
455
456
/*
457
 * Partition an LST
458
 * It's only called for trees that are a single nonempty bucket;
459
 * if it's a subtree, it is thus necessarily the leftmost.
460
 */
461
static void partition(fr_lst_t *lst, stack_index_t stack_index)
462
0
{
463
0
  fr_lst_index_t  low = bucket_lwb(lst, stack_index);
464
0
  fr_lst_index_t  high = bucket_upb(lst, stack_index);
465
0
  fr_lst_index_t  l, h;
466
0
  fr_lst_index_t  pivot_index;
467
0
  void    *pivot;
468
0
  void    *temp;
469
470
  /*
471
   * Hoare partition doesn't do the trivial case, so catch it here.
472
   */
473
0
  if (is_equivalent(lst, low, high)) {
474
0
    stack_push(lst, &lst->s, low);
475
0
    return;
476
0
  }
477
478
0
  pivot_index = low + (fr_fast_rand(&lst->rand_ctx) % (high + 1 - low));
479
0
  pivot = item(lst, pivot_index);
480
481
0
  if (pivot_index != low) {
482
0
    lst_move(lst, pivot_index, item(lst, low));
483
0
    lst_move(lst, low, pivot);
484
0
  }
485
486
  /*
487
   * Hoare partition; on the avaerage, it does a third the swaps of
488
   * Lomuto.
489
   */
490
0
  l = low - 1;
491
0
  h = high + 1;
492
0
  for (;;) {
493
0
    while (lst->cmp(item(lst, --h), pivot) > 0) ;
494
0
    while (lst->cmp(item(lst, ++l), pivot) < 0) ;
495
0
    if (l >= h) break;
496
0
    temp = item(lst, l);
497
0
    lst_move(lst, l, item(lst, h));
498
0
    lst_move(lst, h, temp);
499
0
  }
500
501
  /*
502
   * Hoare partition doesn't guarantee the pivot sits at location h
503
   * the way Lomuto does and LST needs, so first get its location...
504
   */
505
0
  pivot_index = item_index(lst, pivot);
506
0
  if (pivot_index >= index_reduce(lst, low)) {
507
0
    pivot_index = low + pivot_index - index_reduce(lst, low);
508
0
  } else {
509
0
    pivot_index = high - (index_reduce(lst, high) - pivot_index);
510
0
  }
511
512
  /*
513
   * ...and then move it if need be.
514
   */
515
0
  if (pivot_index < h) {
516
0
    lst_move(lst, pivot_index, item(lst, h));
517
0
    lst_move(lst, h, pivot);
518
0
  }
519
0
  if (pivot_index > h) {
520
0
    h++;
521
0
    lst_move(lst, pivot_index, item(lst, h));
522
0
    lst_move(lst, h, pivot);
523
0
  }
524
525
0
  stack_push(lst, &lst->s, h);
526
0
}
527
528
/*
529
 * Delete an item from a bucket in an LST
530
 */
531
static void bucket_delete(fr_lst_t *lst, stack_index_t stack_index, void *data)
532
0
{
533
0
  fr_lst_index_t  location = item_index(lst, data);
534
0
  fr_lst_index_t  top;
535
536
0
  if (is_equivalent(lst, location, lst->idx)) {
537
0
    lst->idx++;
538
0
    if (is_equivalent(lst, lst->idx, 0)) lst_indices_reduce(lst);
539
0
  } else {
540
0
    for (;;) {
541
0
      top = bucket_upb(lst, stack_index);
542
0
      if (!is_equivalent(lst, location, top)) lst_move(lst, location, item(lst, top));
543
0
      stack_set(&lst->s, stack_index, top);
544
0
      if (stack_index == 0) break;
545
0
      lst_move(lst, top, item(lst, top + 1));
546
0
      stack_index--;
547
0
      location = top + 1;
548
0
    }
549
0
  }
550
551
0
  lst->num_elements--;
552
0
  item_index_set(lst, data, -1);
553
0
}
554
555
/*
556
 * We precede each function that does the real work with a Pythonish
557
 * (but colon-free) version of the pseudocode from the paper.
558
 *
559
 * clang, in version 13, will have a way to force tail call optimization
560
 * with a "musttail" attribute. gcc has -f-foptimize-sibling-calls, but
561
 * it works only with -O[23s]. For now, -O2 will assure TCO. In its absence,
562
 * the recursion depth is bounded by the number of pivot stack entries, aka
563
 * the "length" of the LST, which has an expected value proportional to
564
 * log(number of nodes).
565
 *
566
 * NOTE: inlining a recursive function is not advisable, so no
567
 * always_inline here.
568
 */
569
570
/*
571
 * ExtractMin(LST T ) // assumes s(T ) > 0
572
 *  If T = bucket(B) Then
573
 *    Partition(T ) // O(|B|)
574
 *  Let T = tree(r, L, B )
575
 *  If s(L) = 0 Then
576
 *    Flatten T into bucket(B ) // O(1)
577
 *    Remove r from bucket B // O(1)
578
 *    Return r
579
 *  Else
580
 *    Return ExtractMin(L)
581
 */
582
static inline CC_HINT(nonnull) void *_fr_lst_pop(fr_lst_t *lst, stack_index_t stack_index)
583
0
{
584
0
  if (is_bucket(lst, stack_index)) partition(lst, stack_index);
585
0
  ++stack_index;
586
0
  if (lst_size(lst, stack_index) == 0) {
587
0
    void *min = pivot_item(lst, stack_index);
588
589
0
    lst_flatten(lst, stack_index);
590
0
    bucket_delete(lst, stack_index, min);
591
0
    return min;
592
0
  }
593
0
  return _fr_lst_pop(lst, stack_index);
594
0
}
595
596
/*
597
 * FindMin(LST T ) // assumes s(T ) > 0
598
 *  If T = bucket(B) Then
599
 *    Partition(T ) // O(|B|)
600
 *  Let T = tree(r, L, B )
601
 *  If s(L) = 0 Then
602
 *    Return r
603
 *  Else
604
 *    Return FindMin(L)
605
 */
606
static inline CC_HINT(nonnull) void *_fr_lst_peek(fr_lst_t *lst, stack_index_t stack_index)
607
0
{
608
0
  if (is_bucket(lst, stack_index)) partition(lst, stack_index);
609
0
  ++stack_index;
610
0
  if (lst_size(lst, stack_index) == 0) return pivot_item(lst, stack_index);
611
0
  return _fr_lst_peek(lst, stack_index);
612
0
}
613
614
/*
615
 * Delete(LST T, x ∈ Z)
616
 *  If T = bucket(B) Then
617
 *    Remove x from bucket B // O(depth)
618
 *  Else
619
 *    Let T = tree(r, L, B′)
620
 *    If x < r Then
621
 *      Delete(L, x)
622
 *    Else If x > r Then
623
 *      Remove x from bucket B ′ // O(depth)
624
 *    Else
625
 *      Flatten T into bucket(B′′) // O(1)
626
 *      Remove x from bucket B′′ // O(depth)
627
 */
628
static inline CC_HINT(nonnull) void _fr_lst_extract(fr_lst_t *lst,  stack_index_t stack_index, void *data)
629
0
{
630
0
  int8_t  cmp;
631
632
0
  if (is_bucket(lst, stack_index)) {
633
0
    bucket_delete(lst, stack_index, data);
634
0
    return;
635
0
  }
636
0
  stack_index++;
637
0
  cmp = lst->cmp(data, pivot_item(lst, stack_index));
638
0
  if (cmp < 0) {
639
0
    _fr_lst_extract(lst, stack_index, data);
640
0
  } else if (cmp > 0) {
641
0
    bucket_delete(lst, stack_index - 1, data);
642
0
  } else {
643
0
    lst_flatten(lst, stack_index);
644
0
    bucket_delete(lst, stack_index, data);
645
0
  }
646
0
}
647
648
/*
649
 * Insert(LST T, x ∈ Z)
650
 *  If T = bucket(B) Then
651
 *    Add x to bucket B // O(depth)
652
 *  Else
653
 *    Let T = tree(r, L, B)
654
 *    If random(s(T) + 1) != 1 Then
655
 *      If x < r Then
656
 *        Insert(L, x)
657
 *      Else
658
 *        Add x to bucket B // O(depth)
659
 *    Else
660
 *      Flatten T into bucket(B′) // O(1)
661
 *      Add x to bucket B′ // O(depth)
662
 */
663
static inline CC_HINT(nonnull) void _fr_lst_insert(fr_lst_t *lst, stack_index_t stack_index, void *data)
664
0
{
665
0
#ifndef TALLOC_GET_TYPE_ABORT_NOOP
666
0
  if (lst->type) (void)_talloc_get_type_abort(data, lst->type, __location__);
667
0
#endif
668
669
0
  if (is_bucket(lst, stack_index)) {
670
0
    bucket_add(lst, stack_index, data);
671
0
    return;
672
0
  }
673
0
  stack_index++;
674
0
  if (fr_fast_rand(&lst->rand_ctx) % (lst_size(lst, stack_index) + 1) != 0) {
675
0
    if (lst->cmp(data, pivot_item(lst, stack_index)) < 0) {
676
0
      _fr_lst_insert(lst, stack_index, data);
677
0
    } else {
678
0
      bucket_add(lst, stack_index - 1, data);
679
0
    }
680
0
  } else {
681
0
    lst_flatten(lst, stack_index);
682
0
    bucket_add(lst, stack_index, data);
683
0
  }
684
0
}
685
686
/*
687
 * We represent a (sub)tree with an (lst, stack index) pair, so
688
 * fr_lst_pop(), fr_lst_peek(), and fr_lst_extract() are minimal
689
 * wrappers that
690
 *
691
 * (1) hide our representation from the user and preserve the interface
692
 * (2) check preconditions
693
 */
694
695
void *fr_lst_pop(fr_lst_t *lst)
696
0
{
697
0
  if (unlikely(lst->num_elements == 0)) return NULL;
698
0
  return _fr_lst_pop(lst, 0);
699
0
}
700
701
void *fr_lst_peek(fr_lst_t *lst)
702
0
{
703
0
  if (unlikely(lst->num_elements == 0)) return NULL;
704
0
  return _fr_lst_peek(lst, 0);
705
0
}
706
707
/** Remove an element from an LST
708
 *
709
 * @param[in] lst   the LST to remove an element from
710
 * @param[in] data    the element to remove
711
 * @return
712
 *  - 0 if removal succeeds
713
 *  - -1 if removal fails
714
 */
715
int fr_lst_extract(fr_lst_t *lst, void *data)
716
0
{
717
0
  if (unlikely(lst->num_elements == 0)) {
718
0
    fr_strerror_const("Tried to extract element from empty LST");
719
0
    return -1;
720
0
  }
721
722
0
  if (unlikely(raw_item_index(lst, data) == 0)) {
723
0
    fr_strerror_const("Tried to extract element not in LST");
724
0
    return -1;
725
0
  }
726
727
0
  _fr_lst_extract(lst, 0, data);
728
0
  return 0;
729
0
}
730
731
int fr_lst_insert(fr_lst_t *lst, void *data)
732
0
{
733
  /*
734
   * Expand if need be. Not in the paper, but we want the capability.
735
   */
736
0
  if (unlikely((lst->num_elements == lst->capacity) && !lst_expand(lst))) return -1;
737
738
  /*
739
   * Don't insert something that looks like it's already in an LST.
740
   */
741
0
  if (unlikely(raw_item_index(lst, data) > 0)) {
742
0
    fr_strerror_const("Node is already in the LST");
743
0
    return -1;
744
0
  }
745
746
0
  _fr_lst_insert(lst, 0, data);
747
0
  return 0;
748
0
}
749
750
unsigned int fr_lst_num_elements(fr_lst_t *lst)
751
0
{
752
0
  return lst->num_elements;
753
0
}
754
755
/** Iterate over entries in LST
756
 *
757
 * @note If the LST is modified, the iterator should be considered invalidated.
758
 *
759
 * @param[in] lst to iterate over.
760
 * @param[in] iter  Pointer to an iterator struct, used to maintain
761
 *      state between calls.
762
 * @return
763
 *  - User data.
764
 *  - NULL if at the end of the list.
765
 */
766
void *fr_lst_iter_init(fr_lst_t *lst, fr_lst_iter_t *iter)
767
0
{
768
0
  if (unlikely(lst->num_elements == 0)) return NULL;
769
770
0
  *iter = lst->idx;
771
0
  return item(lst, *iter);
772
0
}
773
774
/** Get the next entry in an LST
775
 *
776
 * @note If the LST is modified, the iterator should be considered invalidated.
777
 *
778
 * @param[in] lst to iterate over.
779
 * @param[in] iter  Pointer to an iterator struct, used to maintain
780
 *      state between calls.
781
 * @return
782
 *  - User data.
783
 *  - NULL if at the end of the list.
784
 */
785
void *fr_lst_iter_next(fr_lst_t *lst, fr_lst_iter_t *iter)
786
0
{
787
0
  if ((*iter + 1) >= stack_item(&lst->s, 0)) return NULL;
788
0
  *iter += 1;
789
790
0
  return item(lst, *iter);
791
0
}
792
793
#ifndef TALLOC_GET_TYPE_ABORT_NOOP
794
void fr_lst_verify(char const *file, int line, fr_lst_t const *lst)
795
0
{
796
0
  fr_lst_index_t  fake_pivot_index, reduced_fake_pivot_index, reduced_end;
797
0
  stack_index_t depth = stack_depth(&(lst->s));
798
0
  int   bucket_size_sum;
799
0
  bool    pivots_in_order = true;
800
0
  bool    pivot_indices_in_order = true;
801
802
0
  fr_fatal_assert_msg(lst, "CONSISTENCY CHECK FAILED %s[%i]: LST pointer NULL", file, line);
803
0
  talloc_get_type_abort(lst, fr_lst_t);
804
805
  /*
806
   *  There must be at least the fictitious pivot.
807
   */
808
0
  fr_fatal_assert_msg(depth >= 1, "CONSISTENCY CHECK FAILED %s[%i]: LST pivot stack empty", file, line);
809
810
  /*
811
   *  Modulo circularity, idx + the number of elements should be the index
812
   *  of the fictitious pivot.
813
   */
814
0
  fake_pivot_index = stack_item(&(lst->s), 0);
815
0
  reduced_fake_pivot_index = index_reduce(lst, fake_pivot_index);
816
0
  reduced_end = index_reduce(lst, lst->idx + lst->num_elements);
817
0
  fr_fatal_assert_msg(reduced_fake_pivot_index == reduced_end,
818
0
          "CONSISTENCY CHECK FAILED %s[%i]: fictitious pivot doesn't point past last element",
819
0
          file, line);
820
821
  /*
822
   *  Bucket sizes must make sense.
823
   */
824
0
  if (lst->num_elements) {
825
0
    bucket_size_sum = 0;
826
827
0
    for (stack_index_t stack_index = 0; stack_index < depth; stack_index++)  {
828
0
      fr_lst_index_t bucket_size = bucket_upb(lst, stack_index) - bucket_lwb(lst, stack_index) + 1;
829
0
      fr_fatal_assert_msg(bucket_size <= lst->num_elements,
830
0
              "CONSISTENCY CHECK FAILED %s[%i]: bucket %u size %u is invalid",
831
0
              file, line, stack_index, bucket_size);
832
0
      bucket_size_sum += bucket_size;
833
0
    }
834
835
0
    fr_fatal_assert_msg(bucket_size_sum + depth - 1 == lst->num_elements,
836
0
            "CONSISTENCY CHECK FAILED %s[%i]: buckets inconsistent with number of elements",
837
0
            file, line);
838
0
  }
839
840
  /*
841
   *  No elements should be NULL;
842
   *  they should have the correct index stored,
843
   *  and if a type is specified, they should point at something of that type,
844
   */
845
0
  for (fr_lst_index_t i = 0; i < lst->num_elements; i++) {
846
0
    void  *element = item(lst, lst->idx + i);
847
848
0
    fr_fatal_assert_msg(element, "CONSISTENCY CHECK FAILED %s[%i]: null element pointer at %u",
849
0
            file, line, lst->idx + i);
850
0
    fr_fatal_assert_msg(is_equivalent(lst, lst->idx + i, item_index(lst, element)),
851
0
            "CONSISTENCY CHECK FAILED %s[%i]: element %u index mismatch", file, line, i);
852
0
    if (lst->type)  (void) _talloc_get_type_abort(element, lst->type, __location__);
853
0
  }
854
855
  /*
856
   * There's nothing more to check for a one-bucket tree.
857
   */
858
0
  if (is_bucket(lst, 0)) return;
859
860
  /*
861
   * Otherwise, first, pivots from left to right (aside from the fictitious
862
   * one) should be in ascending order.
863
   */
864
0
  for (stack_index_t stack_index = 1; stack_index + 1 < depth; stack_index++) {
865
0
    void  *current_pivot = pivot_item(lst, stack_index);
866
0
    void  *next_pivot = pivot_item(lst, stack_index + 1);
867
868
0
    if (current_pivot && next_pivot && lst->cmp(current_pivot, next_pivot) < 0) pivots_in_order = false;
869
0
  }
870
0
  fr_fatal_assert_msg(pivots_in_order, "CONSISTENCY CHECK FAILED %s[%i]: pivots not in ascending order",
871
0
          file, line);
872
873
  /*
874
   * Next, the stacked pivot indices should decrease as you ascend from
875
   * the bottom of the pivot stack. Here we *do* include the fictitious
876
   * pivot; we're just comparing indices.
877
   */
878
0
  for (stack_index_t stack_index = 0; stack_index + 1 < depth; stack_index++) {
879
0
    fr_lst_index_t current_pivot_index = stack_item(&(lst->s), stack_index);
880
0
    fr_lst_index_t previous_pivot_index = stack_item(&(lst->s), stack_index + 1);
881
882
0
    if (previous_pivot_index >= current_pivot_index) pivot_indices_in_order = false;
883
0
  }
884
0
  fr_fatal_assert_msg(pivot_indices_in_order, "CONSISTENCY CHECK FAILED %s[%i]: pivots indices not in order",
885
0
          file, line);
886
887
  /*
888
   * Finally...
889
   * values in buckets shouldn't "follow" the pivot to the immediate right (if it exists)
890
   * and shouldn't "precede" the pivot to the immediate left (if it exists)
891
   */
892
0
  for (stack_index_t stack_index = 0; stack_index < depth; stack_index++) {
893
0
    fr_lst_index_t  lwb, upb, pivot_index;
894
0
    void    *pivot_item, *element;
895
896
0
    if (stack_index > 0) {
897
0
      lwb = (stack_index + 1 == depth) ? lst->idx : stack_item(&(lst->s), stack_index + 1);
898
0
      pivot_index = upb = stack_item(&(lst->s), stack_index);
899
0
      pivot_item = item(lst, pivot_index);
900
0
      for (fr_lst_index_t index = lwb; index < upb; index++) {
901
0
        element = item(lst, index);
902
0
        fr_fatal_assert_msg(!element || !pivot_item || lst->cmp(element, pivot_item) <= 0,
903
0
                "CONSISTENCY CHECK FAILED %s[%i]: element at %u > pivot at %u",
904
0
                file, line, index, pivot_index);
905
0
      }
906
0
    }
907
0
    if (stack_index + 1 < depth) {
908
0
      upb = stack_item(&(lst->s), stack_index);
909
0
      lwb = pivot_index = stack_item(&(lst->s), stack_index + 1);
910
0
      pivot_item = item(lst, pivot_index);
911
0
      for (fr_lst_index_t index = lwb; index < upb; index++) {
912
0
        element = item(lst, index);
913
0
        fr_fatal_assert_msg(!element || !pivot_item || lst->cmp(pivot_item, element) <= 0,
914
0
                "CONSISTENCY CHECK FAILED %s[%i]: element at %u < pivot at %u",
915
0
                file, line, index, pivot_index);
916
0
      }
917
0
    }
918
0
  }
919
0
}
920
#endif