/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 1998-2002,2010 Luigi Rizzo, Universita` di Pisa4* All rights reserved5*6* Redistribution and use in source and binary forms, with or without7* modification, are permitted provided that the following conditions8* are met:9* 1. Redistributions of source code must retain the above copyright10* notice, this list of conditions and the following disclaimer.11* 2. Redistributions in binary form must reproduce the above copyright12* notice, this list of conditions and the following disclaimer in the13* documentation and/or other materials provided with the distribution.14*15* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND16* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE17* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE18* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE19* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL20* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS21* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)22* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT23* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY24* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF25* SUCH DAMAGE.26*/2728/*29* Binary heap and hash tables, used in dummynet30*/3132#include <sys/param.h>33#ifdef _KERNEL34#include <sys/systm.h>35#include <sys/malloc.h>36#include <sys/kernel.h>37#include <netpfil/ipfw/dn_heap.h>38#ifndef log39#define log(x, arg...)40#endif4142#else /* !_KERNEL */4344#include <stdio.h>45#include <dn_test.h>46#include <strings.h>47#include <stdlib.h>4849#include "dn_heap.h"50#define log(x, arg...) fprintf(stderr, ## arg)51#define panic(x...) fprintf(stderr, ## x), exit(1)52#define MALLOC_DEFINE(a, b, c) volatile int __dummy__ ## a __attribute__((__unused__))53static void *my_malloc(int s) { return malloc(s); }54static void my_free(void *p) { free(p); }55#define malloc(s, t, w) my_malloc(s)56#define free(p, t) my_free(p)57#endif /* !_KERNEL */5859static MALLOC_DEFINE(M_DN_HEAP, "dummynet", "dummynet heap");6061/*62* Heap management functions.63*64* In the heap, first node is element 0. Children of i are 2i+1 and 2i+2.65* Some macros help finding parent/children so we can optimize them.66*67* heap_init() is called to expand the heap when needed.68* Increment size in blocks of 16 entries.69* Returns 1 on error, 0 on success70*/71#define HEAP_FATHER(x) ( ( (x) - 1 ) / 2 )72#define HEAP_LEFT(x) ( (x)+(x) + 1 )73#define HEAP_SWAP(a, b, buffer) { buffer = a ; a = b ; b = buffer ; }74#define HEAP_INCREMENT 157576static int77heap_resize(struct dn_heap *h, unsigned int new_size)78{79struct dn_heap_entry *p;8081if ((unsigned int)h->size >= new_size ) /* have enough room */82return 0;83#if 1 /* round to the next power of 2 */84new_size |= new_size >> 1;85new_size |= new_size >> 2;86new_size |= new_size >> 4;87new_size |= new_size >> 8;88new_size |= new_size >> 16;89#else90new_size = (new_size + HEAP_INCREMENT ) & ~HEAP_INCREMENT;91#endif92p = mallocarray(new_size, sizeof(*p), M_DN_HEAP, M_NOWAIT);93if (p == NULL) {94printf("--- %s, resize %d failed\n", __func__, new_size );95return 1; /* error */96}97if (h->size > 0) {98bcopy(h->p, p, h->size * sizeof(*p) );99free(h->p, M_DN_HEAP);100}101h->p = p;102h->size = new_size;103return 0;104}105106int107heap_init(struct dn_heap *h, int size, int ofs)108{109if (heap_resize(h, size))110return 1;111h->elements = 0;112h->ofs = ofs;113return 0;114}115116/*117* Insert element in heap. Normally, p != NULL, we insert p in118* a new position and bubble up. If p == NULL, then the element is119* already in place, and key is the position where to start the120* bubble-up.121* Returns 1 on failure (cannot allocate new heap entry)122*123* If ofs > 0 the position (index, int) of the element in the heap is124* also stored in the element itself at the given offset in bytes.125*/126#define SET_OFFSET(h, i) do { \127if (h->ofs > 0) \128*((int32_t *)((char *)(h->p[i].object) + h->ofs)) = i; \129} while (0)130/*131* RESET_OFFSET is used for sanity checks. It sets ofs132* to an invalid value.133*/134#define RESET_OFFSET(h, i) do { \135if (h->ofs > 0) \136*((int32_t *)((char *)(h->p[i].object) + h->ofs)) = -16; \137} while (0)138139int140heap_insert(struct dn_heap *h, uint64_t key1, void *p)141{142int son = h->elements;143144//log("%s key %llu p %p\n", __FUNCTION__, key1, p);145if (p == NULL) { /* data already there, set starting point */146son = key1;147} else { /* insert new element at the end, possibly resize */148son = h->elements;149if (son == h->size) /* need resize... */150// XXX expand by 16 or so151if (heap_resize(h, h->elements+16) )152return 1; /* failure... */153h->p[son].object = p;154h->p[son].key = key1;155h->elements++;156}157/* make sure that son >= father along the path */158while (son > 0) {159int father = HEAP_FATHER(son);160struct dn_heap_entry tmp;161162if (DN_KEY_LT( h->p[father].key, h->p[son].key ) )163break; /* found right position */164/* son smaller than father, swap and repeat */165HEAP_SWAP(h->p[son], h->p[father], tmp);166SET_OFFSET(h, son);167son = father;168}169SET_OFFSET(h, son);170return 0;171}172173/*174* remove top element from heap, or obj if obj != NULL175*/176bool177heap_extract(struct dn_heap *h, void *obj)178{179int child, father, max = h->elements - 1;180181if (max < 0) {182return false;183}184if (obj == NULL)185father = 0; /* default: move up smallest child */186else { /* extract specific element, index is at offset */187if (h->ofs <= 0)188panic("%s: extract from middle not set on %p\n",189__FUNCTION__, h);190father = *((int *)((char *)obj + h->ofs));191if (father < 0 || father >= h->elements)192return false;193}194/* We should make sure that the object we're trying to remove is195* actually in this heap. */196if (obj != NULL && h->p[father].object != obj)197return false;198199/*200* below, father is the index of the empty element, which201* we replace at each step with the smallest child until we202* reach the bottom level.203*/204// XXX why removing RESET_OFFSET increases runtime by 10% ?205RESET_OFFSET(h, father);206while ( (child = HEAP_LEFT(father)) <= max ) {207if (child != max &&208DN_KEY_LT(h->p[child+1].key, h->p[child].key) )209child++; /* take right child, otherwise left */210h->p[father] = h->p[child];211SET_OFFSET(h, father);212father = child;213}214h->elements--;215if (father != max) {216/*217* Fill hole with last entry and bubble up,218* reusing the insert code219*/220h->p[father] = h->p[max];221heap_insert(h, father, NULL);222}223224return true;225}226227#if 0228/*229* change object position and update references230* XXX this one is never used!231*/232static void233heap_move(struct dn_heap *h, uint64_t new_key, void *object)234{235int temp, i, max = h->elements-1;236struct dn_heap_entry *p, buf;237238if (h->ofs <= 0)239panic("cannot move items on this heap");240p = h->p; /* shortcut */241242i = *((int *)((char *)object + h->ofs));243if (DN_KEY_LT(new_key, p[i].key) ) { /* must move up */244p[i].key = new_key;245for (; i>0 &&246DN_KEY_LT(new_key, p[(temp = HEAP_FATHER(i))].key);247i = temp ) { /* bubble up */248HEAP_SWAP(p[i], p[temp], buf);249SET_OFFSET(h, i);250}251} else { /* must move down */252p[i].key = new_key;253while ( (temp = HEAP_LEFT(i)) <= max ) {254/* found left child */255if (temp != max &&256DN_KEY_LT(p[temp+1].key, p[temp].key))257temp++; /* select child with min key */258if (DN_KEY_LT(>p[temp].key, new_key)) {259/* go down */260HEAP_SWAP(p[i], p[temp], buf);261SET_OFFSET(h, i);262} else263break;264i = temp;265}266}267SET_OFFSET(h, i);268}269#endif /* heap_move, unused */270271/*272* heapify() will reorganize data inside an array to maintain the273* heap property. It is needed when we delete a bunch of entries.274*/275static void276heapify(struct dn_heap *h)277{278int i;279280for (i = 0; i < h->elements; i++ )281heap_insert(h, i , NULL);282}283284int285heap_scan(struct dn_heap *h, int (*fn)(void *, uintptr_t),286uintptr_t arg)287{288int i, ret, found;289290for (i = found = 0 ; i < h->elements ;) {291ret = fn(h->p[i].object, arg);292if (ret & HEAP_SCAN_DEL) {293h->elements-- ;294h->p[i] = h->p[h->elements] ;295found++ ;296} else297i++ ;298if (ret & HEAP_SCAN_END)299break;300}301if (found)302heapify(h);303return found;304}305306/*307* cleanup the heap and free data structure308*/309void310heap_free(struct dn_heap *h)311{312if (h->size >0 )313free(h->p, M_DN_HEAP);314bzero(h, sizeof(*h) );315}316317/*318* hash table support.319*/320321struct dn_ht {322int buckets; /* how many buckets, really buckets - 1*/323int entries; /* how many entries */324int ofs; /* offset of link field */325uint32_t (*hash)(uintptr_t, int, void *arg);326int (*match)(void *_el, uintptr_t key, int, void *);327void *(*newh)(uintptr_t, int, void *);328void **ht; /* bucket heads */329};330/*331* Initialize, allocating bucket pointers inline.332* Recycle previous record if possible.333* If the 'newh' function is not supplied, we assume that the334* key passed to ht_find is the same object to be stored in.335*/336struct dn_ht *337dn_ht_init(struct dn_ht *ht, int buckets, int ofs,338uint32_t (*h)(uintptr_t, int, void *),339int (*match)(void *, uintptr_t, int, void *),340void *(*newh)(uintptr_t, int, void *))341{342int l;343344/*345* Notes about rounding bucket size to a power of two.346* Given the original bucket size, we compute the nearest lower and347* higher power of two, minus 1 (respectively b_min and b_max) because348* this value will be used to do an AND with the index returned349* by hash function.350* To choice between these two values, the original bucket size is351* compared with b_min. If the original size is greater than 4/3 b_min,352* we round the bucket size to b_max, else to b_min.353* This ratio try to round to the nearest power of two, advantaging354* the greater size if the different between two power is relatively355* big.356* Rounding the bucket size to a power of two avoid the use of357* module when calculating the correct bucket.358* The ht->buckets variable store the bucket size - 1 to simply359* do an AND between the index returned by hash function and ht->bucket360* instead of a module.361*/362int b_min; /* min buckets */363int b_max; /* max buckets */364int b_ori; /* original buckets */365366if (h == NULL || match == NULL) {367printf("--- missing hash or match function");368return NULL;369}370if (buckets < 1 || buckets > 65536)371return NULL;372373b_ori = buckets;374/* calculate next power of 2, - 1*/375buckets |= buckets >> 1;376buckets |= buckets >> 2;377buckets |= buckets >> 4;378buckets |= buckets >> 8;379buckets |= buckets >> 16;380381b_max = buckets; /* Next power */382b_min = buckets >> 1; /* Previous power */383384/* Calculate the 'nearest' bucket size */385if (b_min * 4000 / 3000 < b_ori)386buckets = b_max;387else388buckets = b_min;389390if (ht) { /* see if we can reuse */391if (buckets <= ht->buckets) {392ht->buckets = buckets;393} else {394/* free pointers if not allocated inline */395if (ht->ht != (void *)(ht + 1))396free(ht->ht, M_DN_HEAP);397free(ht, M_DN_HEAP);398ht = NULL;399}400}401if (ht == NULL) {402/* Allocate buckets + 1 entries because buckets is use to403* do the AND with the index returned by hash function404*/405l = sizeof(*ht) + (buckets + 1) * sizeof(void **);406ht = malloc(l, M_DN_HEAP, M_NOWAIT | M_ZERO);407}408if (ht) {409ht->ht = (void **)(ht + 1);410ht->buckets = buckets;411ht->ofs = ofs;412ht->hash = h;413ht->match = match;414ht->newh = newh;415}416return ht;417}418419/* dummy callback for dn_ht_free to unlink all */420static int421do_del(void *obj, void *arg)422{423(void)obj;424(void)arg;425return DNHT_SCAN_DEL;426}427428void429dn_ht_free(struct dn_ht *ht, int flags)430{431if (ht == NULL)432return;433if (flags & DNHT_REMOVE) {434(void)dn_ht_scan(ht, do_del, NULL);435} else {436if (ht->ht && ht->ht != (void *)(ht + 1))437free(ht->ht, M_DN_HEAP);438free(ht, M_DN_HEAP);439}440}441442int443dn_ht_entries(struct dn_ht *ht)444{445return ht ? ht->entries : 0;446}447448/* lookup and optionally create or delete element */449void *450dn_ht_find(struct dn_ht *ht, uintptr_t key, int flags, void *arg)451{452int i;453void **pp, *p;454455if (ht == NULL) /* easy on an empty hash */456return NULL;457i = (ht->buckets == 1) ? 0 :458(ht->hash(key, flags, arg) & ht->buckets);459460for (pp = &ht->ht[i]; (p = *pp); pp = (void **)((char *)p + ht->ofs)) {461if (flags & DNHT_MATCH_PTR) {462if (key == (uintptr_t)p)463break;464} else if (ht->match(p, key, flags, arg)) /* found match */465break;466}467if (p) {468if (flags & DNHT_REMOVE) {469/* link in the next element */470*pp = *(void **)((char *)p + ht->ofs);471*(void **)((char *)p + ht->ofs) = NULL;472ht->entries--;473}474} else if (flags & DNHT_INSERT) {475// printf("%s before calling new, bucket %d ofs %d\n",476// __FUNCTION__, i, ht->ofs);477p = ht->newh ? ht->newh(key, flags, arg) : (void *)key;478// printf("%s newh returns %p\n", __FUNCTION__, p);479if (p) {480ht->entries++;481*(void **)((char *)p + ht->ofs) = ht->ht[i];482ht->ht[i] = p;483}484}485return p;486}487488/*489* do a scan with the option to delete the object. Extract next before490* running the callback because the element may be destroyed there.491*/492int493dn_ht_scan(struct dn_ht *ht, int (*fn)(void *, void *), void *arg)494{495int i, ret, found = 0;496void **curp, *cur, *next;497498if (ht == NULL || fn == NULL)499return 0;500for (i = 0; i <= ht->buckets; i++) {501curp = &ht->ht[i];502while ( (cur = *curp) != NULL) {503next = *(void **)((char *)cur + ht->ofs);504ret = fn(cur, arg);505if (ret & DNHT_SCAN_DEL) {506found++;507ht->entries--;508*curp = next;509} else {510curp = (void **)((char *)cur + ht->ofs);511}512if (ret & DNHT_SCAN_END)513return found;514}515}516return found;517}518519/*520* Similar to dn_ht_scan(), except that the scan is performed only521* in the bucket 'bucket'. The function returns a correct bucket number if522* the original is invalid.523* If the callback returns DNHT_SCAN_END, the function move the ht->ht[i]524* pointer to the last entry processed. Moreover, the bucket number passed525* by caller is decremented, because usually the caller increment it.526*/527int528dn_ht_scan_bucket(struct dn_ht *ht, int *bucket, int (*fn)(void *, void *),529void *arg)530{531int i, ret, found = 0;532void **curp, *cur, *next;533534if (ht == NULL || fn == NULL)535return 0;536if (*bucket > ht->buckets)537*bucket = 0;538i = *bucket;539540curp = &ht->ht[i];541while ( (cur = *curp) != NULL) {542next = *(void **)((char *)cur + ht->ofs);543ret = fn(cur, arg);544if (ret & DNHT_SCAN_DEL) {545found++;546ht->entries--;547*curp = next;548} else {549curp = (void **)((char *)cur + ht->ofs);550}551if (ret & DNHT_SCAN_END)552return found;553}554return found;555}556557558