mirror of https://github.com/c-ares/c-ares.git
Modernization: Implement base data-structures and replace usage (#540)
c-ares currently lacks modern data structures that can make coding easier and more efficient. This PR implements a new linked list, skip list (sorted linked list), and hashtable implementation that are easy to use and hard to misuse. Though these implementations use more memory allocations than the prior implementation, the ability to more rapidly iterate on the codebase is a bigger win than any marginal performance difference (which is unlikely to be visible, modern systems are much more powerful than when c-ares was initially created). The data structure implementation favors readability and audit-ability over performance, however using the algorithmically correct data type for the purpose should offset any perceived losses. The primary motivation for this PR is to facilitate future implementation for Issues #444, #135, #458, and possibly #301 A couple additional notes: The ares_timeout() function is now O(1) complexity instead of O(n) due to the use of a skiplist. Some obscure bugs were uncovered which were actually being incorrectly validated in the test cases. These have been addressed in this PR but are not explicitly discussed. Fixed some dead code warnings in ares_rand for systems that don't need rc4 Fix By: Brad House (@bradh352)pull/542/head
parent
39311a6031
commit
cf99c025cf
26 changed files with 2362 additions and 474 deletions
@ -0,0 +1,328 @@ |
||||
/* Copyright (C) 2023 by Brad House
|
||||
* |
||||
* Permission to use, copy, modify, and distribute this |
||||
* software and its documentation for any purpose and without |
||||
* fee is hereby granted, provided that the above copyright |
||||
* notice appear in all copies and that both that copyright |
||||
* notice and this permission notice appear in supporting |
||||
* documentation, and that the name of M.I.T. not be used in |
||||
* advertising or publicity pertaining to distribution of the |
||||
* software without specific, written prior permission. |
||||
* M.I.T. makes no representations about the suitability of |
||||
* this software for any purpose. It is provided "as is" |
||||
* without express or implied warranty. |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
#include "ares_setup.h" |
||||
#include "ares.h" |
||||
#include "ares_private.h" |
||||
#include "ares__llist.h" |
||||
#include "ares__htable.h" |
||||
|
||||
#define ARES__HTABLE_MAX_BUCKETS (1U<<24) |
||||
#define ARES__HTABLE_MIN_BUCKETS (1U<<4) |
||||
#define ARES__HTABLE_EXPAND_PERCENT 75 |
||||
|
||||
struct ares__htable { |
||||
ares__htable_hashfunc_t hash; |
||||
ares__htable_bucket_key_t bucket_key; |
||||
ares__htable_bucket_free_t bucket_free; |
||||
ares__htable_key_eq_t key_eq; |
||||
unsigned int seed; |
||||
unsigned int size; |
||||
size_t num_keys; |
||||
/* NOTE: if we converted buckets into ares__slist_t we could guarantee on
|
||||
* hash collisions we would have O(log n) worst case insert and search |
||||
* performance. (We'd also need to make key_eq into a key_cmp to |
||||
* support sort). That said, risk with a random hash seed is near zero, |
||||
* and ares__slist_t is heavier weight so I think using ares__llist_t is |
||||
* is an overall win. */ |
||||
ares__llist_t **buckets; |
||||
}; |
||||
|
||||
|
||||
static unsigned int ares__htable_generate_seed(ares__htable_t *htable) |
||||
{ |
||||
unsigned int seed = 0; |
||||
|
||||
/* Mix stack address, heap address, and time to generate a random seed, it
|
||||
* doesn't have to be super secure, just quick. Likelihood of a hash |
||||
* collision attack is very low with a small amount of effort */ |
||||
seed |= (unsigned int)((size_t)htable & 0xFFFFFFFF); |
||||
seed |= (unsigned int)((size_t)&seed & 0xFFFFFFFF); |
||||
seed |= (unsigned int)time(NULL) & 0xFFFFFFFF; |
||||
return seed; |
||||
} |
||||
|
||||
static void ares__htable_buckets_destroy(ares__llist_t **buckets, |
||||
unsigned int size, |
||||
unsigned char destroy_vals) |
||||
{ |
||||
unsigned int i; |
||||
|
||||
if (buckets == NULL) |
||||
return; |
||||
|
||||
for (i=0; i<size; i++) { |
||||
if (buckets[i] == NULL) |
||||
continue; |
||||
|
||||
if (!destroy_vals) |
||||
ares__llist_replace_destructor(buckets[i], NULL); |
||||
|
||||
ares__llist_destroy(buckets[i]); |
||||
} |
||||
|
||||
ares_free(buckets); |
||||
} |
||||
|
||||
|
||||
void ares__htable_destroy(ares__htable_t *htable) |
||||
{ |
||||
if (htable == NULL) |
||||
return; |
||||
ares__htable_buckets_destroy(htable->buckets, htable->size, 1); |
||||
ares_free(htable); |
||||
} |
||||
|
||||
|
||||
ares__htable_t *ares__htable_create(ares__htable_hashfunc_t hash_func, |
||||
ares__htable_bucket_key_t bucket_key, |
||||
ares__htable_bucket_free_t bucket_free, |
||||
ares__htable_key_eq_t key_eq) |
||||
{ |
||||
ares__htable_t *htable = NULL; |
||||
|
||||
if (hash_func == NULL || bucket_key == NULL || bucket_free == NULL || |
||||
key_eq == NULL) { |
||||
goto fail; |
||||
} |
||||
|
||||
htable = ares_malloc(sizeof(*htable)); |
||||
if (htable == NULL) |
||||
goto fail; |
||||
|
||||
memset(htable, 0, sizeof(*htable)); |
||||
|
||||
htable->hash = hash_func; |
||||
htable->bucket_key = bucket_key; |
||||
htable->bucket_free = bucket_free; |
||||
htable->key_eq = key_eq; |
||||
htable->seed = ares__htable_generate_seed(htable); |
||||
htable->size = ARES__HTABLE_MIN_BUCKETS; |
||||
htable->buckets = ares_malloc(sizeof(*htable->buckets) * htable->size); |
||||
|
||||
if (htable->buckets == NULL) |
||||
goto fail; |
||||
|
||||
memset(htable->buckets, 0, sizeof(*htable->buckets) * htable->size); |
||||
|
||||
return htable; |
||||
|
||||
fail: |
||||
ares__htable_destroy(htable); |
||||
return NULL; |
||||
} |
||||
|
||||
|
||||
/*! Grabs the Hashtable index from the key and length. The h index is
|
||||
* the hash of the function reduced to the size of the bucket list. |
||||
* We are doing "hash & (size - 1)" since we are guaranteeing a power of |
||||
* 2 for size. This is equivalent to "hash % size", but should be more |
||||
* efficient */ |
||||
#define HASH_IDX(h, key) h->hash(key, h->seed) & (h->size - 1) |
||||
|
||||
static ares__llist_node_t *ares__htable_find(ares__htable_t *htable, |
||||
unsigned int idx, |
||||
const void *key) |
||||
{ |
||||
ares__llist_node_t *node = NULL; |
||||
|
||||
for (node = ares__llist_node_first(htable->buckets[idx]); |
||||
node != NULL; |
||||
node = ares__llist_node_next(node)) { |
||||
|
||||
if (htable->key_eq(key, htable->bucket_key(ares__llist_node_val(node)))) |
||||
break; |
||||
} |
||||
|
||||
return node; |
||||
} |
||||
|
||||
|
||||
static unsigned int ares__htable_expand(ares__htable_t *htable) |
||||
{ |
||||
ares__llist_t **buckets = NULL; |
||||
unsigned int old_size = htable->size; |
||||
size_t i; |
||||
|
||||
/* Not a failure, just won't expand */ |
||||
if (old_size == ARES__HTABLE_MAX_BUCKETS) |
||||
return 1; |
||||
|
||||
htable->size <<= 1; |
||||
|
||||
/* We must do this in 2 passes as we want it to be non-destructive in case
|
||||
* there is a memory allocation failure. So we will actually use more
|
||||
* memory doing it this way, but at least we might be able to gracefully |
||||
* recover */ |
||||
buckets = ares_malloc(sizeof(*buckets) * htable->size); |
||||
if (buckets == NULL) |
||||
goto fail; |
||||
|
||||
memset(buckets, 0, sizeof(*buckets) * htable->size); |
||||
|
||||
for (i=0; i<old_size; i++) { |
||||
ares__llist_node_t *node; |
||||
for (node = ares__llist_node_first(htable->buckets[i]); |
||||
node != NULL; |
||||
node = ares__llist_node_next(node)) { |
||||
|
||||
void *val = ares__llist_node_val(node); |
||||
size_t idx = HASH_IDX(htable, htable->bucket_key(val)); |
||||
|
||||
if (buckets[idx] == NULL) { |
||||
buckets[idx] = ares__llist_create(htable->bucket_free); |
||||
if (buckets[idx] == NULL) |
||||
goto fail; |
||||
} |
||||
|
||||
if (ares__llist_insert_first(buckets[idx], val) == NULL) { |
||||
goto fail; |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
/* Swap out buckets */ |
||||
ares__htable_buckets_destroy(htable->buckets, old_size, 0); |
||||
htable->buckets = buckets; |
||||
return 1; |
||||
|
||||
fail: |
||||
ares__htable_buckets_destroy(buckets, htable->size, 0); |
||||
htable->size = old_size; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
|
||||
unsigned int ares__htable_insert(ares__htable_t *htable, void *bucket) |
||||
{ |
||||
unsigned int idx = 0; |
||||
ares__llist_node_t *node = NULL; |
||||
const void *key = NULL; |
||||
|
||||
if (htable == NULL || bucket == NULL) |
||||
return 0; |
||||
|
||||
|
||||
key = htable->bucket_key(bucket); |
||||
idx = HASH_IDX(htable, key); |
||||
|
||||
/* See if we have a matching bucket already, if so, replace it */ |
||||
node = ares__htable_find(htable, idx, key); |
||||
if (node != NULL) { |
||||
ares__llist_node_replace(node, bucket); |
||||
return 1; |
||||
} |
||||
|
||||
/* Check to see if we should rehash because likelihood of collisions has
|
||||
* increased beyond our threshold */ |
||||
if (htable->num_keys+1 > (htable->size * ARES__HTABLE_EXPAND_PERCENT) / 100) { |
||||
if (!ares__htable_expand(htable)) { |
||||
return 0; |
||||
} |
||||
/* If we expanded, need to calculate a new index */ |
||||
idx = HASH_IDX(htable, key); |
||||
} |
||||
|
||||
/* We lazily allocate the linked list */ |
||||
if (htable->buckets[idx] == NULL) { |
||||
htable->buckets[idx] = ares__llist_create(htable->bucket_free); |
||||
if (htable->buckets[idx] == NULL) |
||||
return 0; |
||||
} |
||||
|
||||
node = ares__llist_insert_first(htable->buckets[idx], bucket); |
||||
if (node == NULL) |
||||
return 0; |
||||
|
||||
htable->num_keys++; |
||||
|
||||
return 1; |
||||
} |
||||
|
||||
|
||||
void *ares__htable_get(ares__htable_t *htable, const void *key) |
||||
{ |
||||
unsigned int idx; |
||||
|
||||
if (htable == NULL || key == NULL) |
||||
return NULL; |
||||
|
||||
idx = HASH_IDX(htable, key); |
||||
|
||||
return ares__llist_node_val(ares__htable_find(htable, idx, key)); |
||||
} |
||||
|
||||
|
||||
unsigned int ares__htable_remove(ares__htable_t *htable, const void *key) |
||||
{ |
||||
ares__llist_node_t *node; |
||||
unsigned int idx; |
||||
|
||||
if (htable == NULL || key == NULL) |
||||
return 0; |
||||
|
||||
idx = HASH_IDX(htable, key); |
||||
node = ares__htable_find(htable, idx, key); |
||||
if (node == NULL) |
||||
return 0; |
||||
|
||||
htable->num_keys--; |
||||
ares__llist_node_destroy(node); |
||||
return 1; |
||||
} |
||||
|
||||
size_t ares__htable_num_keys(ares__htable_t *htable) |
||||
{ |
||||
if (htable == NULL) |
||||
return 0; |
||||
return htable->num_keys; |
||||
} |
||||
|
||||
unsigned int ares__htable_hash_FNV1a(const void *key, size_t key_len, |
||||
unsigned int seed) |
||||
{ |
||||
const unsigned char *data = key; |
||||
/* recommended seed is 2166136261U, but we don't want collisions */ |
||||
unsigned int hv = seed;
|
||||
size_t i; |
||||
|
||||
for (i = 0; i < key_len; i++) { |
||||
hv ^= (unsigned int)data[i]; |
||||
/* hv *= 0x01000193 */ |
||||
hv += (hv<<1) + (hv<<4) + (hv<<7) + (hv<<8) + (hv<<24); |
||||
} |
||||
|
||||
return hv; |
||||
} |
||||
|
||||
/* Case insensitive version, meant for strings */ |
||||
unsigned int ares__htable_hash_FNV1a_casecmp(const void *key, size_t key_len, |
||||
unsigned int seed) |
||||
{ |
||||
const unsigned char *data = key; |
||||
unsigned int hv = seed; |
||||
size_t i; |
||||
|
||||
for (i = 0; i < key_len; i++) { |
||||
hv ^= (unsigned int)tolower((char)data[i]); |
||||
/* hv *= 16777619 */ |
||||
hv += (hv<<1) + (hv<<4) + (hv<<7) + (hv<<8) + (hv<<24); |
||||
} |
||||
|
||||
return hv; |
||||
} |
@ -0,0 +1,154 @@ |
||||
/* Copyright (C) 2023 by Brad House
|
||||
* |
||||
* Permission to use, copy, modify, and distribute this |
||||
* software and its documentation for any purpose and without |
||||
* fee is hereby granted, provided that the above copyright |
||||
* notice appear in all copies and that both that copyright |
||||
* notice and this permission notice appear in supporting |
||||
* documentation, and that the name of M.I.T. not be used in |
||||
* advertising or publicity pertaining to distribution of the |
||||
* software without specific, written prior permission. |
||||
* M.I.T. makes no representations about the suitability of |
||||
* this software for any purpose. It is provided "as is" |
||||
* without express or implied warranty. |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
#ifndef __ARES__HTABLE_H |
||||
#define __ARES__HTABLE_H |
||||
|
||||
|
||||
/*! \addtogroup ares__htable Base HashTable Data Structure
|
||||
* |
||||
* This is a basic hashtable data structure that is meant to be wrapped |
||||
* by a higher level implementation. This data structure is designed to |
||||
* be callback-based in order to facilitate wrapping without needing to |
||||
* worry about any underlying complexities of the hashtable implementation. |
||||
* |
||||
* This implementation supports automatic growing by powers of 2 when reaching |
||||
* 75% capacity. A rehash will be performed on the expanded bucket list. |
||||
* |
||||
* Average time complexity: |
||||
* - Insert: O(1) |
||||
* - Search: O(1) |
||||
* - Delete: O(1) |
||||
* |
||||
* @{ |
||||
*/ |
||||
|
||||
struct ares__htable_t; |
||||
|
||||
/*! Opaque data type for generic hash table implementation */ |
||||
typedef struct ares__htable ares__htable_t; |
||||
|
||||
/*! Callback for generating a hash of the key.
|
||||
*
|
||||
* \param[in] key pointer to key to be hashed |
||||
* \param[in] seed randomly generated seed used by hash function. |
||||
* value is specific to the hashtable instance |
||||
* but otherwise will not change between calls. |
||||
* \return hash |
||||
*/ |
||||
typedef unsigned int (*ares__htable_hashfunc_t)(const void *key, |
||||
unsigned int seed); |
||||
|
||||
/*! Callback to free the bucket
|
||||
*
|
||||
* \param[in] bucket user provided bucket |
||||
*/ |
||||
typedef void (*ares__htable_bucket_free_t)(void *bucket); |
||||
|
||||
/*! Callback to extract the key from the user-provided bucket
|
||||
*
|
||||
* \param[in] bucket user provided bucket |
||||
* \return pointer to key held in bucket |
||||
*/ |
||||
typedef const void *(*ares__htable_bucket_key_t)(const void *bucket); |
||||
|
||||
/*! Callback to compare two keys for equality
|
||||
*
|
||||
* \param[in] key1 first key |
||||
* \param[in] key2 second key |
||||
* \return 1 if equal, 0 if not |
||||
*/ |
||||
typedef unsigned int (*ares__htable_key_eq_t)(const void *key1, |
||||
const void *key2); |
||||
|
||||
|
||||
/*! Destroy the initialized hashtable
|
||||
*
|
||||
* \param[in] initialized hashtable |
||||
*/ |
||||
void ares__htable_destroy(ares__htable_t *htable); |
||||
|
||||
/*! Create a new hashtable
|
||||
*
|
||||
* \param[in] hash_func Required. Callback for Hash function. |
||||
* \param[in] bucket_key Required. Callback to extract key from bucket. |
||||
* \param[in] bucket_free Required. Callback to free bucket. |
||||
* \param[in] key_eq Required. Callback to check for key equality. |
||||
* \return initialized hashtable. NULL if out of memory or misuse. |
||||
*/ |
||||
ares__htable_t *ares__htable_create(ares__htable_hashfunc_t hash_func, |
||||
ares__htable_bucket_key_t bucket_key, |
||||
ares__htable_bucket_free_t bucket_free, |
||||
ares__htable_key_eq_t key_eq); |
||||
|
||||
/*! Count of keys from initialized hashtable
|
||||
*
|
||||
* \param[in] htable Initialized hashtable. |
||||
* \return count of keys |
||||
*/ |
||||
size_t ares__htable_num_keys(ares__htable_t *htable); |
||||
|
||||
/*! Insert bucket into hashtable
|
||||
*
|
||||
* \param[in] htable Initialized hashtable. |
||||
* \param[in] bucket User-provided bucket to insert. Takes "ownership". Not |
||||
* allowed to be NULL. |
||||
* \return 1 on success, 0 if out of memory |
||||
*/ |
||||
unsigned int ares__htable_insert(ares__htable_t *htable, void *bucket); |
||||
|
||||
/*! Retrieve bucket from hashtable based on key.
|
||||
*
|
||||
* \param[in] htable Initialized hashtable |
||||
* \param[in] key Pointer to key to use for comparison. |
||||
* \return matching bucket, or NULL if not found. |
||||
*/ |
||||
void *ares__htable_get(ares__htable_t *htable, const void *key); |
||||
|
||||
/*! Remove bucket from hashtable by key
|
||||
*
|
||||
* \param[in] htable Initialized hashtable |
||||
* \param[in] key Pointer to key to use for comparison |
||||
* \return 1 if found, 0 if not found |
||||
*/ |
||||
unsigned int ares__htable_remove(ares__htable_t *htable, const void *key); |
||||
|
||||
/*! FNV1a hash algorithm. Can be used as underlying primitive for building
|
||||
* a wrapper hashtable. |
||||
*
|
||||
* \param[in] key pointer to key |
||||
* \param[in] key_len Length of key |
||||
* \param[in] seed Seed for generating hash |
||||
* \return hash value |
||||
*/ |
||||
unsigned int ares__htable_hash_FNV1a(const void *key, size_t key_len, |
||||
unsigned int seed); |
||||
|
||||
/*! FNV1a hash algorithm, but converts all characters to lowercase before
|
||||
* hashing to make the hash case-insensitive. Can be used as underlying |
||||
* primitive for building a wrapper hashtable. Used on string-based keys. |
||||
*
|
||||
* \param[in] key pointer to key |
||||
* \param[in] key_len Length of key |
||||
* \param[in] seed Seed for generating hash |
||||
* \return hash value |
||||
*/ |
||||
unsigned int ares__htable_hash_FNV1a_casecmp(const void *key, size_t key_len, |
||||
unsigned int); |
||||
|
||||
/*! @} */ |
||||
|
||||
#endif /* __ARES__HTABLE_H */ |
@ -0,0 +1,183 @@ |
||||
/* Copyright (C) 2023 by Brad House
|
||||
* |
||||
* Permission to use, copy, modify, and distribute this |
||||
* software and its documentation for any purpose and without |
||||
* fee is hereby granted, provided that the above copyright |
||||
* notice appear in all copies and that both that copyright |
||||
* notice and this permission notice appear in supporting |
||||
* documentation, and that the name of M.I.T. not be used in |
||||
* advertising or publicity pertaining to distribution of the |
||||
* software without specific, written prior permission. |
||||
* M.I.T. makes no representations about the suitability of |
||||
* this software for any purpose. It is provided "as is" |
||||
* without express or implied warranty. |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
#include "ares_setup.h" |
||||
#include "ares.h" |
||||
#include "ares_private.h" |
||||
#include "ares__htable.h" |
||||
#include "ares__htable_stvp.h" |
||||
|
||||
|
||||
struct ares__htable_stvp { |
||||
ares__htable_stvp_val_free_t free_val; |
||||
ares__htable_t *hash; |
||||
}; |
||||
|
||||
|
||||
typedef struct { |
||||
size_t key; |
||||
void *val; |
||||
ares__htable_stvp_t *parent; |
||||
} ares__htable_stvp_bucket_t; |
||||
|
||||
|
||||
void ares__htable_stvp_destroy(ares__htable_stvp_t *htable) |
||||
{ |
||||
if (htable == NULL) |
||||
return; |
||||
|
||||
ares__htable_destroy(htable->hash); |
||||
ares_free(htable); |
||||
} |
||||
|
||||
|
||||
static unsigned int hash_func(const void *bucket, unsigned int seed) |
||||
{ |
||||
const ares__htable_stvp_bucket_t *arg = bucket; |
||||
return ares__htable_hash_FNV1a(&arg->key, sizeof(arg->key), seed); |
||||
} |
||||
|
||||
|
||||
static const void *bucket_key(const void *bucket) |
||||
{ |
||||
const ares__htable_stvp_bucket_t *arg = bucket; |
||||
return &arg->key; |
||||
} |
||||
|
||||
|
||||
static void bucket_free(void *bucket) |
||||
{ |
||||
ares__htable_stvp_bucket_t *arg = bucket; |
||||
|
||||
if (arg->parent->free_val) |
||||
arg->parent->free_val(arg->val); |
||||
|
||||
ares_free(arg); |
||||
} |
||||
|
||||
|
||||
static unsigned int key_eq(const void *key1, const void *key2) |
||||
{ |
||||
const size_t *k1 = key1; |
||||
const size_t *k2 = key2; |
||||
|
||||
if (*k1 == *k2) |
||||
return 1; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
|
||||
ares__htable_stvp_t *ares__htable_stvp_create( |
||||
ares__htable_stvp_val_free_t val_free) |
||||
{ |
||||
ares__htable_stvp_t *htable = ares_malloc(sizeof(*htable)); |
||||
if (htable == NULL) |
||||
goto fail; |
||||
|
||||
htable->hash = ares__htable_create(hash_func, |
||||
bucket_key, |
||||
bucket_free, |
||||
key_eq); |
||||
if (htable->hash == NULL) |
||||
goto fail; |
||||
|
||||
htable->free_val = val_free; |
||||
|
||||
return htable; |
||||
|
||||
fail: |
||||
if (htable) { |
||||
ares__htable_destroy(htable->hash); |
||||
ares_free(htable); |
||||
} |
||||
return NULL; |
||||
} |
||||
|
||||
|
||||
unsigned int ares__htable_stvp_insert(ares__htable_stvp_t *htable, size_t key, |
||||
void *val) |
||||
{ |
||||
ares__htable_stvp_bucket_t *bucket = NULL; |
||||
|
||||
if (htable == NULL) |
||||
goto fail; |
||||
|
||||
bucket = ares_malloc(sizeof(*bucket)); |
||||
if (bucket == NULL) |
||||
goto fail; |
||||
|
||||
bucket->parent = htable; |
||||
bucket->key = key; |
||||
bucket->val = val; |
||||
|
||||
if (!ares__htable_insert(htable->hash, bucket)) |
||||
goto fail; |
||||
|
||||
return 1; |
||||
|
||||
fail: |
||||
if (bucket) { |
||||
ares_free(bucket); |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
|
||||
unsigned int ares__htable_stvp_get(ares__htable_stvp_t *htable, size_t key, |
||||
void **val) |
||||
{ |
||||
ares__htable_stvp_bucket_t *bucket = NULL; |
||||
|
||||
if (val) |
||||
*val = NULL; |
||||
|
||||
if (htable == NULL) |
||||
return 0; |
||||
|
||||
bucket = ares__htable_get(htable->hash, &key); |
||||
if (bucket == NULL) |
||||
return 0; |
||||
|
||||
if (val) |
||||
*val = bucket->val; |
||||
return 1; |
||||
} |
||||
|
||||
|
||||
void *ares__htable_stvp_get_direct(ares__htable_stvp_t *htable, size_t key) |
||||
{ |
||||
void *val = NULL; |
||||
ares__htable_stvp_get(htable, key, &val); |
||||
return val; |
||||
} |
||||
|
||||
|
||||
unsigned int ares__htable_stvp_remove(ares__htable_stvp_t *htable, size_t key) |
||||
{ |
||||
if (htable == NULL) |
||||
return 0; |
||||
|
||||
return ares__htable_remove(htable->hash, &key); |
||||
} |
||||
|
||||
|
||||
size_t ares__htable_stvp_num_keys(ares__htable_stvp_t *htable) |
||||
{ |
||||
if (htable == NULL) |
||||
return 0; |
||||
return ares__htable_num_keys(htable->hash); |
||||
} |
@ -0,0 +1,106 @@ |
||||
/* Copyright (C) 2023 by Brad House
|
||||
* |
||||
* Permission to use, copy, modify, and distribute this |
||||
* software and its documentation for any purpose and without |
||||
* fee is hereby granted, provided that the above copyright |
||||
* notice appear in all copies and that both that copyright |
||||
* notice and this permission notice appear in supporting |
||||
* documentation, and that the name of M.I.T. not be used in |
||||
* advertising or publicity pertaining to distribution of the |
||||
* software without specific, written prior permission. |
||||
* M.I.T. makes no representations about the suitability of |
||||
* this software for any purpose. It is provided "as is" |
||||
* without express or implied warranty. |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
#ifndef __ARES__HTABLE_STVP_H |
||||
#define __ARES__HTABLE_STVP_H |
||||
|
||||
/*! \addtogroup ares__htable_stvp HashTable with size_t Key and void pointer Value
|
||||
* |
||||
* This data structure wraps the base ares__htable data structure in order to |
||||
* split the key and value data types as size_t and void pointer, respectively. |
||||
* |
||||
* Average time complexity: |
||||
* - Insert: O(1) |
||||
* - Search: O(1) |
||||
* - Delete: O(1) |
||||
* |
||||
* @{ |
||||
*/ |
||||
|
||||
struct ares__htable_stvp; |
||||
|
||||
/*! Opaque data type for size_t key, void pointer hash table implementation */ |
||||
typedef struct ares__htable_stvp ares__htable_stvp_t; |
||||
|
||||
/*! Callback to free value stored in hashtable
|
||||
*
|
||||
* \param[in] val user-supplied value |
||||
*/ |
||||
typedef void (*ares__htable_stvp_val_free_t)(void *val); |
||||
|
||||
/*! Destroy hashtable
|
||||
*
|
||||
* \param[in] htable Initialized hashtable |
||||
*/ |
||||
void ares__htable_stvp_destroy(ares__htable_stvp_t *htable); |
||||
|
||||
/*! Create size_t key, void pointer value hash table
|
||||
*
|
||||
* \param[in] val_free Optional. Call back to free user-supplied value. If |
||||
* NULL it is expected the caller will clean up any user |
||||
* supplied values. |
||||
*/ |
||||
ares__htable_stvp_t *ares__htable_stvp_create( |
||||
ares__htable_stvp_val_free_t val_free); |
||||
|
||||
/*! Insert key/value into hash table
|
||||
*
|
||||
* \param[in] htable Initialized hash table |
||||
* \param[in] key key to associate with value |
||||
* \param[in] val value to store (takes ownership). May be NULL. |
||||
* \return 1 on success, 0 on out of memory or misuse |
||||
*/ |
||||
unsigned int ares__htable_stvp_insert(ares__htable_stvp_t *htable, size_t key, |
||||
void *val); |
||||
|
||||
/*! Retrieve value from hashtable based on key
|
||||
*
|
||||
* \param[in] htable Initialized hash table |
||||
* \param[in] key key to use to search |
||||
* \param[out] val Optional. Pointer to store value. |
||||
* \return 1 on success, 0 on failure |
||||
*/ |
||||
unsigned int ares__htable_stvp_get(ares__htable_stvp_t *htable, size_t key, |
||||
void **val); |
||||
|
||||
/*! Retrieve value from hashtable directly as return value. Caveat to this
|
||||
* function over ares__htable_stvp_get() is that if a NULL value is stored |
||||
* you cannot determine if the key is not found or the value is NULL. |
||||
*
|
||||
* \param[in] htable Initialized hash table |
||||
* \param[in] key key to use to search |
||||
* \return value associated with key in hashtable or NULL |
||||
*/ |
||||
void *ares__htable_stvp_get_direct(ares__htable_stvp_t *htable, size_t key); |
||||
|
||||
/*! Remove a value from the hashtable by key
|
||||
*
|
||||
* \param[in] htable Initialized hash table |
||||
* \param[in] key key to use to search |
||||
* \return 1 if found, 0 if not |
||||
*/ |
||||
unsigned int ares__htable_stvp_remove(ares__htable_stvp_t *htable, size_t key); |
||||
|
||||
/*! Retrieve the number of keys stored in the hash table
|
||||
*
|
||||
* \param[in] htable Initialized hash table |
||||
* \return count |
||||
*/ |
||||
size_t ares__htable_stvp_num_keys(ares__htable_stvp_t *htable); |
||||
|
||||
/*! @} */ |
||||
|
||||
#endif /* __ARES__HTABLE_STVP_H */ |
@ -0,0 +1,307 @@ |
||||
/* Copyright (C) 2023 by Brad House
|
||||
* |
||||
* Permission to use, copy, modify, and distribute this |
||||
* software and its documentation for any purpose and without |
||||
* fee is hereby granted, provided that the above copyright |
||||
* notice appear in all copies and that both that copyright |
||||
* notice and this permission notice appear in supporting |
||||
* documentation, and that the name of M.I.T. not be used in |
||||
* advertising or publicity pertaining to distribution of the |
||||
* software without specific, written prior permission. |
||||
* M.I.T. makes no representations about the suitability of |
||||
* this software for any purpose. It is provided "as is" |
||||
* without express or implied warranty. |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
#include "ares_setup.h" |
||||
#include "ares.h" |
||||
#include "ares_private.h" |
||||
#include "ares__llist.h" |
||||
|
||||
struct ares__llist { |
||||
ares__llist_node_t *head; |
||||
ares__llist_node_t *tail; |
||||
ares__llist_destructor_t destruct; |
||||
size_t cnt; |
||||
}; |
||||
|
||||
|
||||
struct ares__llist_node { |
||||
void *data; |
||||
ares__llist_node_t *prev; |
||||
ares__llist_node_t *next; |
||||
ares__llist_t *parent; |
||||
}; |
||||
|
||||
|
||||
ares__llist_t *ares__llist_create(ares__llist_destructor_t destruct) |
||||
{ |
||||
ares__llist_t *list = ares_malloc(sizeof(*list)); |
||||
|
||||
if (list == NULL) |
||||
return NULL; |
||||
|
||||
memset(list, 0, sizeof(*list)); |
||||
|
||||
list->destruct = destruct; |
||||
|
||||
return list; |
||||
} |
||||
|
||||
|
||||
void ares__llist_replace_destructor(ares__llist_t *list, |
||||
ares__llist_destructor_t destruct) |
||||
{ |
||||
if (list == NULL) |
||||
return; |
||||
|
||||
list->destruct = destruct; |
||||
} |
||||
|
||||
|
||||
typedef enum { |
||||
ARES__LLIST_INSERT_HEAD, |
||||
ARES__LLIST_INSERT_TAIL, |
||||
ARES__LLIST_INSERT_BEFORE |
||||
} ares__llist_insert_type_t; |
||||
|
||||
|
||||
static ares__llist_node_t *ares__llist_insert_at(ares__llist_t *list,
|
||||
ares__llist_insert_type_t type, |
||||
ares__llist_node_t *at, |
||||
void *val) |
||||
{ |
||||
ares__llist_node_t *node = NULL; |
||||
|
||||
if (list == NULL || val == NULL) |
||||
return NULL; |
||||
|
||||
node = ares_malloc(sizeof(*node)); |
||||
|
||||
if (node == NULL) |
||||
return NULL; |
||||
|
||||
memset(node, 0, sizeof(*node)); |
||||
node->data = val; |
||||
node->parent = list; |
||||
|
||||
if (type == ARES__LLIST_INSERT_BEFORE && (at == list->head || at == NULL)) { |
||||
type = ARES__LLIST_INSERT_HEAD; |
||||
} |
||||
|
||||
switch (type) { |
||||
case ARES__LLIST_INSERT_HEAD: |
||||
node->next = list->head; |
||||
node->prev = NULL; |
||||
if (list->head) |
||||
list->head->prev = node; |
||||
list->head = node; |
||||
break; |
||||
case ARES__LLIST_INSERT_TAIL: |
||||
node->next = NULL; |
||||
node->prev = list->tail; |
||||
if (list->tail) |
||||
list->tail->next = node; |
||||
list->tail = node; |
||||
break; |
||||
case ARES__LLIST_INSERT_BEFORE: |
||||
node->next = at; |
||||
node->prev = at->prev; |
||||
at->prev = node; |
||||
break; |
||||
} |
||||
if (list->tail == NULL) |
||||
list->tail = node; |
||||
if (list->head == NULL) |
||||
list->head = node; |
||||
|
||||
list->cnt++; |
||||
|
||||
return node; |
||||
} |
||||
|
||||
|
||||
ares__llist_node_t *ares__llist_insert_first(ares__llist_t *list, void *val) |
||||
{ |
||||
return ares__llist_insert_at(list, ARES__LLIST_INSERT_HEAD, NULL, val); |
||||
} |
||||
|
||||
|
||||
ares__llist_node_t *ares__llist_insert_last(ares__llist_t *list, void *val) |
||||
{ |
||||
return ares__llist_insert_at(list, ARES__LLIST_INSERT_TAIL, NULL, val); |
||||
} |
||||
|
||||
|
||||
ares__llist_node_t *ares__llist_insert_before(ares__llist_node_t *node, |
||||
void *val) |
||||
{ |
||||
if (node == NULL) |
||||
return NULL; |
||||
|
||||
return ares__llist_insert_at(node->parent, ARES__LLIST_INSERT_BEFORE, node, |
||||
val); |
||||
} |
||||
|
||||
|
||||
ares__llist_node_t *ares__llist_insert_after(ares__llist_node_t *node, |
||||
void *val) |
||||
{ |
||||
if (node == NULL) |
||||
return NULL; |
||||
|
||||
if (node->next == NULL) |
||||
return ares__llist_insert_last(node->parent, val); |
||||
|
||||
return ares__llist_insert_at(node->parent, ARES__LLIST_INSERT_BEFORE, |
||||
node->next, val); |
||||
} |
||||
|
||||
|
||||
ares__llist_node_t *ares__llist_node_first(ares__llist_t *list) |
||||
{ |
||||
if (list == NULL) |
||||
return NULL; |
||||
return list->head; |
||||
} |
||||
|
||||
|
||||
ares__llist_node_t *ares__llist_node_last(ares__llist_t *list) |
||||
{ |
||||
if (list == NULL) |
||||
return NULL; |
||||
return list->tail; |
||||
} |
||||
|
||||
|
||||
ares__llist_node_t *ares__llist_node_next(ares__llist_node_t *node) |
||||
{ |
||||
if (node == NULL) |
||||
return NULL; |
||||
return node->next; |
||||
} |
||||
|
||||
|
||||
ares__llist_node_t *ares__llist_node_prev(ares__llist_node_t *node) |
||||
{ |
||||
if (node == NULL) |
||||
return NULL; |
||||
return node->prev; |
||||
} |
||||
|
||||
|
||||
void *ares__llist_node_val(ares__llist_node_t *node) |
||||
{ |
||||
if (node == NULL) |
||||
return NULL; |
||||
|
||||
return node->data; |
||||
} |
||||
|
||||
|
||||
size_t ares__llist_len(ares__llist_t *list) |
||||
{ |
||||
if (list == NULL) |
||||
return 0; |
||||
return list->cnt; |
||||
} |
||||
|
||||
|
||||
ares__llist_t *ares__llist_node_parent(ares__llist_node_t *node) |
||||
{ |
||||
if (node == NULL) |
||||
return NULL; |
||||
return node->parent; |
||||
} |
||||
|
||||
|
||||
void *ares__llist_first_val(ares__llist_t *list) |
||||
{ |
||||
return ares__llist_node_val(ares__llist_node_first(list)); |
||||
} |
||||
|
||||
|
||||
void *ares__llist_last_val(ares__llist_t *list) |
||||
{ |
||||
return ares__llist_node_val(ares__llist_node_last(list)); |
||||
} |
||||
|
||||
|
||||
void *ares__llist_node_claim(ares__llist_node_t *node) |
||||
{ |
||||
void *val; |
||||
ares__llist_t *list; |
||||
|
||||
if (node == NULL) |
||||
return NULL; |
||||
|
||||
list = node->parent; |
||||
val = node->data; |
||||
|
||||
if (node->prev) { |
||||
node->prev->next = node->next; |
||||
} |
||||
|
||||
if (node->next) { |
||||
node->next->prev = node->prev; |
||||
} |
||||
|
||||
if (node == list->head) { |
||||
list->head = node->next; |
||||
} |
||||
|
||||
if (node == list->tail) { |
||||
list->tail = node->prev; |
||||
} |
||||
ares_free(node); |
||||
|
||||
list->cnt--; |
||||
|
||||
return val; |
||||
} |
||||
|
||||
|
||||
void ares__llist_node_destroy(ares__llist_node_t *node) |
||||
{ |
||||
ares__llist_destructor_t destruct; |
||||
void *val; |
||||
|
||||
if (node == NULL) |
||||
return; |
||||
|
||||
destruct = node->parent->destruct; |
||||
|
||||
val = ares__llist_node_claim(node); |
||||
if (val != NULL && destruct != NULL) |
||||
destruct(val); |
||||
} |
||||
|
||||
|
||||
void ares__llist_node_replace(ares__llist_node_t *node, void *val) |
||||
{ |
||||
ares__llist_destructor_t destruct; |
||||
|
||||
if (node == NULL) |
||||
return; |
||||
|
||||
destruct = node->parent->destruct; |
||||
if (destruct != NULL) |
||||
destruct(node->data); |
||||
|
||||
node->data = val; |
||||
} |
||||
|
||||
|
||||
void ares__llist_destroy(ares__llist_t *list) |
||||
{ |
||||
ares__llist_node_t *node; |
||||
|
||||
if (list == NULL) |
||||
return; |
||||
|
||||
while ((node = ares__llist_node_first(list)) != NULL) { |
||||
ares__llist_node_destroy(node); |
||||
} |
||||
ares_free(list); |
||||
} |
@ -0,0 +1,194 @@ |
||||
/* Copyright (C) 2023 by Brad House
|
||||
* |
||||
* Permission to use, copy, modify, and distribute this |
||||
* software and its documentation for any purpose and without |
||||
* fee is hereby granted, provided that the above copyright |
||||
* notice appear in all copies and that both that copyright |
||||
* notice and this permission notice appear in supporting |
||||
* documentation, and that the name of M.I.T. not be used in |
||||
* advertising or publicity pertaining to distribution of the |
||||
* software without specific, written prior permission. |
||||
* M.I.T. makes no representations about the suitability of |
||||
* this software for any purpose. It is provided "as is" |
||||
* without express or implied warranty. |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
#ifndef __ARES__LLIST_H |
||||
#define __ARES__LLIST_H |
||||
|
||||
/*! \addtogroup ares__llist LinkedList Data Structure
|
||||
* |
||||
* This is a doubly-linked list data structure. |
||||
* |
||||
* Average time complexity: |
||||
* - Insert: O(1) -- head or tail |
||||
* - Search: O(n) |
||||
* - Delete: O(1) -- delete assumes you hold a node pointer |
||||
* |
||||
* @{ |
||||
*/ |
||||
|
||||
struct ares__llist; |
||||
|
||||
/*! Opaque data structure for linked list */ |
||||
typedef struct ares__llist ares__llist_t; |
||||
|
||||
struct ares__llist_node; |
||||
|
||||
/*! Opaque data structure for a node in a linked list */ |
||||
typedef struct ares__llist_node ares__llist_node_t; |
||||
|
||||
/*! Callback to free user-defined node data
|
||||
*
|
||||
* \param[in] data user supplied data |
||||
*/ |
||||
typedef void (*ares__llist_destructor_t)(void *data); |
||||
|
||||
/*! Create a linked list object
|
||||
*
|
||||
* \param[in] destruct Optional. Destructor to call on all removed nodes |
||||
* \return linked list object or NULL on out of memory |
||||
*/ |
||||
ares__llist_t *ares__llist_create(ares__llist_destructor_t destruct); |
||||
|
||||
/*! Replace destructor for linked list nodes. Typically this is used
|
||||
* when wanting to disable the destructor by using NULL. |
||||
*
|
||||
* \param[in] list Initialized linked list object |
||||
* \param[in] destruct replacement destructor, NULL is allowed |
||||
*/ |
||||
void ares__llist_replace_destructor(ares__llist_t *list, |
||||
ares__llist_destructor_t destruct); |
||||
|
||||
/*! Insert value as the first node in the linked list
|
||||
*
|
||||
* \param[in] list Initialized linked list object |
||||
* \param[in] val user-supplied value. |
||||
* \return node object referencing place in list, or null if out of memory or |
||||
* misuse |
||||
*/ |
||||
ares__llist_node_t *ares__llist_insert_first(ares__llist_t *list, void *val); |
||||
|
||||
/*! Insert value as the last node in the linked list
|
||||
*
|
||||
* \param[in] list Initialized linked list object |
||||
* \param[in] val user-supplied value. |
||||
* \return node object referencing place in list, or null if out of memory or |
||||
* misuse |
||||
*/ |
||||
ares__llist_node_t *ares__llist_insert_last(ares__llist_t *list, void *val); |
||||
|
||||
/*! Insert value before specified node in the linked list
|
||||
*
|
||||
* \param[in] node node referenced to insert before |
||||
* \param[in] val user-supplied value. |
||||
* \return node object referencing place in list, or null if out of memory or |
||||
* misuse |
||||
*/ |
||||
ares__llist_node_t *ares__llist_insert_before(ares__llist_node_t *node, |
||||
void *val); |
||||
|
||||
/*! Insert value after specified node in the linked list
|
||||
*
|
||||
* \param[in] node node referenced to insert after |
||||
* \param[in] val user-supplied value. |
||||
* \return node object referencing place in list, or null if out of memory or |
||||
* misuse |
||||
*/ |
||||
ares__llist_node_t *ares__llist_insert_after(ares__llist_node_t *node, |
||||
void *val); |
||||
|
||||
/*! Obtain first node in list
|
||||
*
|
||||
* \param[in] list Initialized list object |
||||
* \return first node in list or NULL if none |
||||
*/ |
||||
ares__llist_node_t *ares__llist_node_first(ares__llist_t *list); |
||||
|
||||
/*! Obtain last node in list
|
||||
*
|
||||
* \param[in] list Initialized list object |
||||
* \return last node in list or NULL if none |
||||
*/ |
||||
ares__llist_node_t *ares__llist_node_last(ares__llist_t *list); |
||||
|
||||
/*! Obtain next node in respect to specified node
|
||||
*
|
||||
* \param[in] node Node referenced |
||||
* \return node or NULL if none |
||||
*/ |
||||
ares__llist_node_t *ares__llist_node_next(ares__llist_node_t *node); |
||||
|
||||
/*! Obtain previous node in respect to specified node
|
||||
*
|
||||
* \param[in] node Node referenced |
||||
* \return node or NULL if none |
||||
*/ |
||||
ares__llist_node_t *ares__llist_node_prev(ares__llist_node_t *node); |
||||
|
||||
/*! Obtain value from node
|
||||
*
|
||||
* \param[in] node Node referenced |
||||
* \return user provided value from node |
||||
*/ |
||||
void *ares__llist_node_val(ares__llist_node_t *node); |
||||
|
||||
/*! Obtain the number of entries in the list
|
||||
*
|
||||
* \param[in] list Initialized list object |
||||
* \return count |
||||
*/ |
||||
size_t ares__llist_len(ares__llist_t *list); |
||||
|
||||
/*! Obtain list object from referenced node
|
||||
*
|
||||
* \param[in] node Node referenced |
||||
* \return list object node belongs to |
||||
*/ |
||||
ares__llist_t *ares__llist_node_parent(ares__llist_node_t *node); |
||||
|
||||
/*! Obtain the first user-supplied value in the list
|
||||
*
|
||||
* \param[in] list Initialized list object |
||||
* \return first user supplied value or NULL if none |
||||
*/ |
||||
void *ares__llist_first_val(ares__llist_t *list); |
||||
|
||||
/*! Obtain the last user-supplied value in the list
|
||||
*
|
||||
* \param[in] list Initialized list object |
||||
* \return last user supplied value or NULL if none |
||||
*/ |
||||
void *ares__llist_last_val(ares__llist_t *list); |
||||
|
||||
/*! Take ownership of user-supplied value in list without calling destructor.
|
||||
* Will unchain entry from list. |
||||
*
|
||||
* \param[in] node Node referenced |
||||
* \return user supplied value |
||||
*/ |
||||
void *ares__llist_node_claim(ares__llist_node_t *node); |
||||
|
||||
/*! Replace user-supplied value for node
|
||||
*
|
||||
* \param[in] node Node referenced |
||||
* \param[in] val new user-supplied value |
||||
*/ |
||||
void ares__llist_node_replace(ares__llist_node_t *node, void *val); |
||||
|
||||
/*! Destroy the node, removing it from the list and calling destructor.
|
||||
*
|
||||
* \param[in] node Node referenced |
||||
*/ |
||||
void ares__llist_node_destroy(ares__llist_node_t *node); |
||||
|
||||
/*! Destroy the list object and all nodes in the list.
|
||||
*
|
||||
* \param[in] list Initialized list object |
||||
*/ |
||||
void ares__llist_destroy(ares__llist_t *list); |
||||
|
||||
/*! @} */ |
||||
|
||||
#endif /* __ARES__LLIST_H */ |
@ -0,0 +1,493 @@ |
||||
/* Copyright (C) 2023 by Brad House
|
||||
* |
||||
* Permission to use, copy, modify, and distribute this |
||||
* software and its documentation for any purpose and without |
||||
* fee is hereby granted, provided that the above copyright |
||||
* notice appear in all copies and that both that copyright |
||||
* notice and this permission notice appear in supporting |
||||
* documentation, and that the name of M.I.T. not be used in |
||||
* advertising or publicity pertaining to distribution of the |
||||
* software without specific, written prior permission. |
||||
* M.I.T. makes no representations about the suitability of |
||||
* this software for any purpose. It is provided "as is" |
||||
* without express or implied warranty. |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
#include "ares_setup.h" |
||||
#include "ares.h" |
||||
#include "ares_private.h" |
||||
#include "ares__slist.h" |
||||
|
||||
/* SkipList implementation */ |
||||
|
||||
#define ARES__SLIST_START_LEVELS 4 |
||||
|
||||
|
||||
struct ares__slist { |
||||
ares_rand_state *rand_state; |
||||
unsigned char rand_data[8]; |
||||
size_t rand_bits; |
||||
|
||||
ares__slist_node_t **head; |
||||
size_t levels;
|
||||
ares__slist_node_t *tail; |
||||
|
||||
ares__slist_cmp_t cmp; |
||||
ares__slist_destructor_t destruct; |
||||
size_t cnt; |
||||
}; |
||||
|
||||
|
||||
struct ares__slist_node { |
||||
void *data; |
||||
ares__slist_node_t **prev; |
||||
ares__slist_node_t **next; |
||||
size_t levels; |
||||
ares__slist_t *parent; |
||||
}; |
||||
|
||||
|
||||
ares__slist_t *ares__slist_create(ares_rand_state *rand_state, |
||||
ares__slist_cmp_t cmp, |
||||
ares__slist_destructor_t destruct) |
||||
{ |
||||
ares__slist_t *list; |
||||
|
||||
if (rand_state == NULL || cmp == NULL) |
||||
return NULL; |
||||
|
||||
list = ares_malloc(sizeof(*list)); |
||||
|
||||
if (list == NULL) |
||||
return NULL; |
||||
|
||||
memset(list, 0, sizeof(*list)); |
||||
|
||||
list->rand_state = rand_state; |
||||
list->cmp = cmp; |
||||
list->destruct = destruct; |
||||
|
||||
list->levels = ARES__SLIST_START_LEVELS; |
||||
list->head = ares_malloc(sizeof(*list->head) * list->levels); |
||||
if (list->head == NULL) { |
||||
ares_free(list); |
||||
return NULL; |
||||
} |
||||
|
||||
memset(list->head, 0, sizeof(*list->head) * list->levels); |
||||
|
||||
return list; |
||||
} |
||||
|
||||
|
||||
static unsigned int ares__slist_coin_flip(ares__slist_t *list) |
||||
{ |
||||
size_t total_bits = sizeof(list->rand_data) * 8; |
||||
size_t bit; |
||||
|
||||
/* Refill random data used for coin flips. We pull this in 8 byte chunks.
|
||||
* ares__rand_bytes() has some built-in caching of its own so we don't need |
||||
* to be excessive in caching ourselves. Prefer to require less memory per |
||||
* skiplist */ |
||||
if (list->rand_bits == 0) { |
||||
ares__rand_bytes(list->rand_state, list->rand_data, |
||||
sizeof(list->rand_data)); |
||||
list->rand_bits = total_bits; |
||||
} |
||||
|
||||
bit = total_bits - list->rand_bits; |
||||
list->rand_bits--; |
||||
|
||||
return (list->rand_data[bit / 8] & (1 << (bit % 8)))?1:0; |
||||
} |
||||
|
||||
|
||||
void ares__slist_replace_destructor(ares__slist_t *list, |
||||
ares__slist_destructor_t destruct) |
||||
{ |
||||
if (list == NULL) |
||||
return; |
||||
|
||||
list->destruct = destruct; |
||||
} |
||||
|
||||
/* Uses public domain code snipets from http://graphics.stanford.edu/~seander/bithacks.html */ |
||||
|
||||
static size_t ares__round_up_pow2(size_t n) |
||||
{ |
||||
n--; |
||||
n |= n >> 1; |
||||
n |= n >> 2; |
||||
n |= n >> 4; |
||||
n |= n >> 8; |
||||
n |= n >> 16; |
||||
if (sizeof(size_t) > 4) |
||||
n |= n >> 32; |
||||
n++; |
||||
return n; |
||||
} |
||||
|
||||
|
||||
static size_t ares__log2(size_t n) |
||||
{ |
||||
static const unsigned char tab32[32] = { |
||||
0, 1, 28, 2, 29, 14, 24, 3, |
||||
30, 22, 20, 15, 25, 17, 4, 8,
|
||||
31, 27, 13, 23, 21, 19, 16, 7, |
||||
26, 12, 18, 6, 11, 5, 10, 9 |
||||
}; |
||||
static const unsigned char tab64[64] = { |
||||
63, 0, 58, 1, 59, 47, 53, 2, |
||||
60, 39, 48, 27, 54, 33, 42, 3, |
||||
61, 51, 37, 40, 49, 18, 28, 20, |
||||
55, 30, 34, 11, 43, 14, 22, 4, |
||||
62, 57, 46, 52, 38, 26, 32, 41, |
||||
50, 36, 17, 19, 29, 10, 13, 21, |
||||
56, 45, 25, 31, 35, 16, 9, 12, |
||||
44, 24, 15, 8, 23, 7, 6, 5 |
||||
}; |
||||
|
||||
if (sizeof(size_t) == 4) |
||||
return tab32[(size_t)(n*0x077CB531) >> 27]; |
||||
|
||||
return tab64[((size_t)(n*0x07EDD5E59A4E28C2)) >> 58]; |
||||
} |
||||
|
||||
|
||||
static size_t ares__slist_max_level(ares__slist_t *list) |
||||
{ |
||||
size_t max_level = 0; |
||||
|
||||
if (list->cnt+1 <= (1 << ARES__SLIST_START_LEVELS)) { |
||||
max_level = ARES__SLIST_START_LEVELS; |
||||
} else { |
||||
max_level = ares__log2(ares__round_up_pow2(list->cnt+1)); |
||||
} |
||||
|
||||
if (list->levels > max_level) |
||||
max_level = list->levels; |
||||
|
||||
return max_level; |
||||
} |
||||
|
||||
|
||||
static size_t ares__slist_calc_level(ares__slist_t *list) |
||||
{ |
||||
size_t max_level = ares__slist_max_level(list); |
||||
size_t level; |
||||
|
||||
for (level=1; ares__slist_coin_flip(list) && level < max_level; level++) |
||||
; |
||||
|
||||
return level; |
||||
} |
||||
|
||||
|
||||
ares__slist_node_t *ares__slist_insert(ares__slist_t *list, void *val) |
||||
{ |
||||
ares__slist_node_t *node = NULL; |
||||
ares__slist_node_t *left = NULL; |
||||
size_t i; |
||||
|
||||
if (list == NULL || val == NULL) |
||||
return NULL; |
||||
|
||||
node = ares_malloc(sizeof(*node)); |
||||
|
||||
if (node == NULL) |
||||
goto fail; |
||||
|
||||
memset(node, 0, sizeof(*node)); |
||||
node->data = val; |
||||
node->parent = list; |
||||
|
||||
/* Randomly determine the number of levels we want to use */ |
||||
node->levels = ares__slist_calc_level(list); |
||||
|
||||
/* Allocate array of next and prev nodes for linking each level */ |
||||
node->next = ares_malloc(sizeof(*node->next) * node->levels); |
||||
if (node->next == NULL) |
||||
goto fail; |
||||
|
||||
memset(node->next, 0, sizeof(*node->next) * node->levels); |
||||
|
||||
node->prev = ares_malloc(sizeof(*node->prev) * node->levels); |
||||
if (node->prev == NULL) |
||||
goto fail; |
||||
|
||||
memset(node->prev, 0, sizeof(*node->prev) * node->levels); |
||||
|
||||
/* If the number of levels is greater than we currently support in the slist,
|
||||
* increase the count */ |
||||
if (list->levels < node->levels) { |
||||
size_t zero_len = sizeof(*list->head) * (node->levels - list->levels); |
||||
size_t offset = sizeof(*list->head) * list->levels; |
||||
void *ptr = ares_realloc(list->head, sizeof(*list->head) * list->levels); |
||||
if (ptr == NULL) |
||||
goto fail; |
||||
|
||||
memset((unsigned char *)ptr + offset, 0, zero_len); |
||||
list->head = ptr; |
||||
list->levels = node->levels; |
||||
} |
||||
|
||||
|
||||
/* Scan from highest level in the slist, even if we're not using that number
|
||||
* of levels for this entry as this is what makes it O(log n) */ |
||||
for (i=list->levels; i-- > 0; ) { |
||||
|
||||
/* set left if left is NULL and the current node value is greater than the
|
||||
* head at this level */ |
||||
if (left == NULL &&
|
||||
list->head[i] != NULL &&
|
||||
list->cmp(node->data, list->head[i]->data) > 0 |
||||
) { |
||||
left = list->head[i]; |
||||
} |
||||
|
||||
if (left != NULL) { |
||||
/* scan forward to find our insertion point */ |
||||
while (left->next[i] != NULL && |
||||
list->cmp(node->data, left->next[i]->data) > 0) { |
||||
left = left->next[i]; |
||||
} |
||||
} |
||||
|
||||
/* search only as we didn't randomly select this number of levels */ |
||||
if (i >= node->levels) |
||||
continue; |
||||
|
||||
if (left == NULL) { |
||||
/* head insertion */ |
||||
node->next[i] = list->head[i]; |
||||
node->prev[i] = NULL; |
||||
list->head[i] = node; |
||||
} else { |
||||
/* Chain */ |
||||
node->next[i] = left->next[i]; |
||||
node->prev[i] = left; |
||||
left->next[i] = node; |
||||
} |
||||
|
||||
if (node->next[i] != NULL) { |
||||
/* chain prev */ |
||||
node->next[i]->prev[i] = node; |
||||
} else { |
||||
if (i == 0) { |
||||
/* update tail */ |
||||
list->tail = node; |
||||
} |
||||
} |
||||
} |
||||
|
||||
list->cnt++; |
||||
|
||||
return node; |
||||
|
||||
fail: |
||||
if (node) { |
||||
ares_free(node->prev); |
||||
ares_free(node->next); |
||||
ares_free(node); |
||||
} |
||||
return NULL; |
||||
} |
||||
|
||||
|
||||
ares__slist_node_t *ares__slist_node_find(ares__slist_t *list, const void *val) |
||||
{ |
||||
size_t i; |
||||
ares__slist_node_t *node = NULL; |
||||
int rv = -1; |
||||
|
||||
if (list == NULL || val == NULL) |
||||
return NULL; |
||||
|
||||
/* Scan nodes starting at the highest level. For each level scan forward
|
||||
* until the value is between the prior and next node, or if equal quit |
||||
* as we found a match */ |
||||
for (i=list->levels; i-- > 0; ) { |
||||
if (node == NULL) |
||||
node = list->head[i]; |
||||
|
||||
if (node == NULL) |
||||
continue; |
||||
|
||||
do { |
||||
rv = list->cmp(val, node->data); |
||||
|
||||
if (rv < 0) { |
||||
/* back off, our value is greater than current node reference */ |
||||
node = node->prev[i]; |
||||
} else if (rv > 0) { |
||||
/* move forward and try again. if it goes past, it will loop again and
|
||||
* go to previous entry */ |
||||
node = node->next[i]; |
||||
} |
||||
|
||||
/* rv == 0 will terminate loop */ |
||||
|
||||
} while (node != NULL && rv > 0); |
||||
|
||||
/* Found a match, no need to continue */ |
||||
if (rv == 0) { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/* no match */ |
||||
if (rv != 0) { |
||||
return NULL; |
||||
} |
||||
|
||||
/* The list may have multiple entries that match. They're guaranteed to be
|
||||
* in order, but we're not guaranteed to have selected the _first_ matching |
||||
* node. Lets scan backwards to find the first match */ |
||||
while (node->prev[0] != NULL && list->cmp(node->prev[0]->data, val) == 0) { |
||||
node = node->prev[0]; |
||||
} |
||||
|
||||
return node; |
||||
} |
||||
|
||||
|
||||
ares__slist_node_t *ares__slist_node_first(ares__slist_t *list) |
||||
{ |
||||
if (list == NULL) |
||||
return NULL; |
||||
|
||||
return list->head[0]; |
||||
} |
||||
|
||||
|
||||
ares__slist_node_t *ares__slist_node_last(ares__slist_t *list) |
||||
{ |
||||
if (list == NULL) |
||||
return NULL; |
||||
return list->tail; |
||||
} |
||||
|
||||
|
||||
ares__slist_node_t *ares__slist_node_next(ares__slist_node_t *node) |
||||
{ |
||||
if (node == NULL) |
||||
return NULL; |
||||
return node->next[0]; |
||||
} |
||||
|
||||
|
||||
ares__slist_node_t *ares__slist_node_prev(ares__slist_node_t *node) |
||||
{ |
||||
if (node == NULL) |
||||
return NULL; |
||||
return node->prev[0]; |
||||
} |
||||
|
||||
|
||||
void *ares__slist_node_val(ares__slist_node_t *node) |
||||
{ |
||||
if (node == NULL) |
||||
return NULL; |
||||
|
||||
return node->data; |
||||
} |
||||
|
||||
|
||||
size_t ares__slist_len(ares__slist_t *list) |
||||
{ |
||||
if (list == NULL) |
||||
return 0; |
||||
return list->cnt; |
||||
} |
||||
|
||||
|
||||
ares__slist_t *ares__slist_node_parent(ares__slist_node_t *node) |
||||
{ |
||||
if (node == NULL) |
||||
return NULL; |
||||
return node->parent; |
||||
} |
||||
|
||||
|
||||
void *ares__slist_first_val(ares__slist_t *list) |
||||
{ |
||||
return ares__slist_node_val(ares__slist_node_first(list)); |
||||
} |
||||
|
||||
|
||||
void *ares__slist_last_val(ares__slist_t *list) |
||||
{ |
||||
return ares__slist_node_val(ares__slist_node_last(list)); |
||||
} |
||||
|
||||
|
||||
void *ares__slist_node_claim(ares__slist_node_t *node) |
||||
{ |
||||
void *val; |
||||
ares__slist_t *list; |
||||
size_t i; |
||||
|
||||
if (node == NULL) |
||||
return NULL; |
||||
|
||||
list = node->parent; |
||||
val = node->data; |
||||
|
||||
/* relink each node at each level */ |
||||
for (i=node->levels; i-- > 0; ) { |
||||
if (node->next[i] == NULL) { |
||||
if (i == 0) { |
||||
list->tail = node->prev[0]; |
||||
} |
||||
} else { |
||||
node->next[i]->prev[i] = node->prev[i]; |
||||
} |
||||
|
||||
if (node->prev[i] == NULL) { |
||||
list->head[i] = node->next[i]; |
||||
} else { |
||||
node->prev[i]->next[i] = node->next[i]; |
||||
} |
||||
} |
||||
|
||||
ares_free(node->next); |
||||
ares_free(node->prev); |
||||
ares_free(node); |
||||
|
||||
list->cnt--; |
||||
|
||||
return val; |
||||
} |
||||
|
||||
|
||||
void ares__slist_node_destroy(ares__slist_node_t *node) |
||||
{ |
||||
ares__slist_destructor_t destruct; |
||||
void *val; |
||||
|
||||
if (node == NULL) |
||||
return; |
||||
|
||||
destruct = node->parent->destruct; |
||||
val = ares__slist_node_claim(node); |
||||
|
||||
if (val != NULL && destruct != NULL) |
||||
destruct(val); |
||||
} |
||||
|
||||
|
||||
void ares__slist_destroy(ares__slist_t *list) |
||||
{ |
||||
ares__slist_node_t *node; |
||||
|
||||
if (list == NULL) |
||||
return; |
||||
|
||||
while ((node = ares__slist_node_first(list)) != NULL) { |
||||
ares__slist_node_destroy(node); |
||||
} |
||||
|
||||
ares_free(list->head); |
||||
ares_free(list); |
||||
} |
@ -0,0 +1,188 @@ |
||||
/* Copyright (C) 2023 by Brad House
|
||||
* |
||||
* Permission to use, copy, modify, and distribute this |
||||
* software and its documentation for any purpose and without |
||||
* fee is hereby granted, provided that the above copyright |
||||
* notice appear in all copies and that both that copyright |
||||
* notice and this permission notice appear in supporting |
||||
* documentation, and that the name of M.I.T. not be used in |
||||
* advertising or publicity pertaining to distribution of the |
||||
* software without specific, written prior permission. |
||||
* M.I.T. makes no representations about the suitability of |
||||
* this software for any purpose. It is provided "as is" |
||||
* without express or implied warranty. |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
#ifndef __ARES__SLIST_H |
||||
#define __ARES__SLIST_H |
||||
|
||||
|
||||
/*! \addtogroup ares__slist SkipList Data Structure
|
||||
* |
||||
* This data structure is known as a Skip List, which in essence is a sorted |
||||
* linked list with multiple levels of linkage to gain some algorithmic |
||||
* advantages. The usage symantecs are almost identical to what you'd expect |
||||
* with a linked list. |
||||
* |
||||
* Average time complexity: |
||||
* - Insert: O(log n) |
||||
* - Search: O(log n) |
||||
* - Delete: O(1) -- delete assumes you hold a node pointer |
||||
* |
||||
* It should be noted, however, that "effort" involved with an insert or |
||||
* remove operation is higher than a normal linked list. For very small |
||||
* lists this may be less efficient, but for any list with a moderate number |
||||
* of entries this will prove much more efficient. |
||||
* |
||||
* This data structure is often compared with a Binary Search Tree in |
||||
* functionality and usage. |
||||
* |
||||
* @{ |
||||
*/ |
||||
struct ares__slist; |
||||
|
||||
/*! SkipList Object, opaque */ |
||||
typedef struct ares__slist ares__slist_t; |
||||
|
||||
struct ares__slist_node; |
||||
|
||||
/*! SkipList Node Object, opaque */ |
||||
typedef struct ares__slist_node ares__slist_node_t; |
||||
|
||||
/*! SkipList Node Value destructor callback
|
||||
*
|
||||
* \param[in] data User-defined data to destroy |
||||
*/ |
||||
typedef void (*ares__slist_destructor_t)(void *data); |
||||
|
||||
/*! SkipList comparison function
|
||||
*
|
||||
* \param[in] data1 First user-defined data object |
||||
* \param[in] data2 Second user-defined data object |
||||
* \return < 0 if data1 < data1, > 0 if data1 > data2, 0 if data1 == data2 |
||||
*/ |
||||
typedef int (*ares__slist_cmp_t)(const void *data1, const void *data2); |
||||
|
||||
/*! Create SkipList
|
||||
*
|
||||
* \param[in] rand_state Initialized ares random state. |
||||
* \param[in] cmp SkipList comparison function |
||||
* \param[in] destruct SkipList Node Value Destructor. Optional, use NULL. |
||||
* \return Initialized SkipList Object or NULL on misuse or ENOMEM |
||||
*/
|
||||
ares__slist_t *ares__slist_create(ares_rand_state *rand_state, |
||||
ares__slist_cmp_t cmp, |
||||
ares__slist_destructor_t destruct); |
||||
|
||||
/*! Replace SkipList Node Value Destructor
|
||||
*
|
||||
* \param[in] list Initialized SkipList Object |
||||
* \param[in] destruct Replacement destructor. May be NULL. |
||||
*/ |
||||
void ares__slist_replace_destructor(ares__slist_t *list, |
||||
ares__slist_destructor_t destruct); |
||||
|
||||
/*! Insert Value into SkipList
|
||||
*
|
||||
* \param[in] list Initialized SkipList Object |
||||
* \param[in] val Node Value. Must not be NULL. Function takes ownership |
||||
* and will have destructor called. |
||||
* \return SkipList Node Object or NULL on misuse or ENOMEM |
||||
*/ |
||||
ares__slist_node_t *ares__slist_insert(ares__slist_t *list, void *val); |
||||
|
||||
/*! Fetch first node in SkipList
|
||||
*
|
||||
* \param[in] list Initialized SkipList Object |
||||
* \return SkipList Node Object or NULL if none |
||||
*/ |
||||
ares__slist_node_t *ares__slist_node_first(ares__slist_t *list); |
||||
|
||||
/*! Fetch last node in SkipList
|
||||
*
|
||||
* \param[in] list Initialized SkipList Object |
||||
* \return SkipList Node Object or NULL if none |
||||
*/ |
||||
ares__slist_node_t *ares__slist_node_last(ares__slist_t *list); |
||||
|
||||
/*! Fetch next node in SkipList
|
||||
*
|
||||
* \param[in] node SkipList Node Object |
||||
* \return SkipList Node Object or NULL if none |
||||
*/ |
||||
ares__slist_node_t *ares__slist_node_next(ares__slist_node_t *node); |
||||
|
||||
/*! Fetch previous node in SkipList
|
||||
*
|
||||
* \param[in] node SkipList Node Object |
||||
* \return SkipList Node Object or NULL if none |
||||
*/ |
||||
ares__slist_node_t *ares__slist_node_prev(ares__slist_node_t *node); |
||||
|
||||
/*! Fetch SkipList Node Object by Value
|
||||
*
|
||||
* \param[in] list Initialized SkipList Object |
||||
* \param[in] val Object to use for comparison |
||||
* \return SkipList Node Object or NULL if not found |
||||
*/ |
||||
ares__slist_node_t *ares__slist_node_find(ares__slist_t *list, const void *val); |
||||
|
||||
|
||||
/*! Fetch Node Value
|
||||
*
|
||||
* \param[in] node SkipList Node Object |
||||
* \return user defined node value |
||||
*/ |
||||
void *ares__slist_node_val(ares__slist_node_t *node); |
||||
|
||||
/*! Fetch number of entries in SkipList Object
|
||||
*
|
||||
* \param[in] list Initialized SkipList Object |
||||
* \return number of entries |
||||
*/ |
||||
size_t ares__slist_len(ares__slist_t *list); |
||||
|
||||
/*! Fetch SkipList Object from SkipList Node
|
||||
*
|
||||
* \param[in] node SkipList Node Object |
||||
* \return SkipList Object |
||||
*/ |
||||
ares__slist_t *ares__slist_node_parent(ares__slist_node_t *node); |
||||
|
||||
/*! Fetch first Node Value in SkipList
|
||||
*
|
||||
* \param[in] list Initialized SkipList Object |
||||
* \return user defined node value or NULL if none |
||||
*/ |
||||
void *ares__slist_first_val(ares__slist_t *list); |
||||
|
||||
/*! Fetch last Node Value in SkipList
|
||||
*
|
||||
* \param[in] list Initialized SkipList Object |
||||
* \return user defined node value or NULL if none |
||||
*/ |
||||
void *ares__slist_last_val(ares__slist_t *list); |
||||
|
||||
/*! Take back ownership of Node Value in SkipList, remove from SkipList.
|
||||
*
|
||||
* \param[in] node SkipList Node Object |
||||
* \return user defined node value |
||||
*/ |
||||
void *ares__slist_node_claim(ares__slist_node_t *node); |
||||
|
||||
/*! Remove Node from SkipList, calling destructor for Node Value.
|
||||
*
|
||||
* \param[in] node SkipList Node Object |
||||
*/ |
||||
void ares__slist_node_destroy(ares__slist_node_t *node); |
||||
|
||||
/*! Destroy SkipList Object. If there are any nodes, they will be destroyed.
|
||||
*
|
||||
* \param[in] list Initialized SkipList Object |
||||
*/ |
||||
void ares__slist_destroy(ares__slist_t *list); |
||||
|
||||
/*! @} */ |
||||
|
||||
#endif /* __ARES__SLIST_H */ |
@ -1,65 +0,0 @@ |
||||
|
||||
/* Copyright 1998 by the Massachusetts Institute of Technology.
|
||||
* |
||||
* Permission to use, copy, modify, and distribute this |
||||
* software and its documentation for any purpose and without |
||||
* fee is hereby granted, provided that the above copyright |
||||
* notice appear in all copies and that both that copyright |
||||
* notice and this permission notice appear in supporting |
||||
* documentation, and that the name of M.I.T. not be used in |
||||
* advertising or publicity pertaining to distribution of the |
||||
* software without specific, written prior permission. |
||||
* M.I.T. makes no representations about the suitability of |
||||
* this software for any purpose. It is provided "as is" |
||||
* without express or implied warranty. |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
|
||||
#include "ares_setup.h" |
||||
|
||||
#include "ares.h" |
||||
#include "ares_private.h" |
||||
|
||||
/* Routines for managing doubly-linked circular linked lists with a
|
||||
* dummy head. |
||||
*/ |
||||
|
||||
/* Initialize a new head node */ |
||||
void ares__init_list_head(struct list_node* head) { |
||||
head->prev = head; |
||||
head->next = head; |
||||
head->data = NULL; |
||||
} |
||||
|
||||
/* Initialize a list node */ |
||||
void ares__init_list_node(struct list_node* node, void* d) { |
||||
node->prev = NULL; |
||||
node->next = NULL; |
||||
node->data = d; |
||||
} |
||||
|
||||
/* Returns true iff the given list is empty */ |
||||
int ares__is_list_empty(struct list_node* head) { |
||||
return ((head->next == head) && (head->prev == head)); |
||||
} |
||||
|
||||
/* Inserts new_node before old_node */ |
||||
void ares__insert_in_list(struct list_node* new_node, |
||||
struct list_node* old_node) { |
||||
new_node->next = old_node; |
||||
new_node->prev = old_node->prev; |
||||
old_node->prev->next = new_node; |
||||
old_node->prev = new_node; |
||||
} |
||||
|
||||
/* Removes the node from the list it's in, if any */ |
||||
void ares__remove_from_list(struct list_node* node) { |
||||
if (node->next != NULL) { |
||||
node->prev->next = node->next; |
||||
node->next->prev = node->prev; |
||||
node->prev = NULL; |
||||
node->next = NULL; |
||||
} |
||||
} |
||||
|
@ -1,41 +0,0 @@ |
||||
#ifndef __ARES_LLIST_H |
||||
#define __ARES_LLIST_H |
||||
|
||||
|
||||
/* Copyright 1998 by the Massachusetts Institute of Technology.
|
||||
* |
||||
* Permission to use, copy, modify, and distribute this |
||||
* software and its documentation for any purpose and without |
||||
* fee is hereby granted, provided that the above copyright |
||||
* notice appear in all copies and that both that copyright |
||||
* notice and this permission notice appear in supporting |
||||
* documentation, and that the name of M.I.T. not be used in |
||||
* advertising or publicity pertaining to distribution of the |
||||
* software without specific, written prior permission. |
||||
* M.I.T. makes no representations about the suitability of |
||||
* this software for any purpose. It is provided "as is" |
||||
* without express or implied warranty. |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
|
||||
|
||||
/* Node definition for circular, doubly-linked list */ |
||||
struct list_node { |
||||
struct list_node *prev; |
||||
struct list_node *next; |
||||
void* data; |
||||
}; |
||||
|
||||
void ares__init_list_head(struct list_node* head); |
||||
|
||||
void ares__init_list_node(struct list_node* node, void* d); |
||||
|
||||
int ares__is_list_empty(struct list_node* head); |
||||
|
||||
void ares__insert_in_list(struct list_node* new_node, |
||||
struct list_node* old_node); |
||||
|
||||
void ares__remove_from_list(struct list_node* node); |
||||
|
||||
#endif /* __ARES_LLIST_H */ |
Loading…
Reference in new issue