aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2023-12-29 17:43:50 -0800
committerGarrett D'Amore <garrett@damore.org>2023-12-29 18:25:04 -0800
commit3298ac1e93742e7a1ef5c4dc2e9b603dfa89d3cb (patch)
treea1051ba1a3edcd5bc6c75c9a1f43ae1a14813b45
parent5954332f1690e95c329b991a25b2d89b9a44ef02 (diff)
downloadnng-3298ac1e93742e7a1ef5c4dc2e9b603dfa89d3cb.tar.gz
nng-3298ac1e93742e7a1ef5c4dc2e9b603dfa89d3cb.tar.bz2
nng-3298ac1e93742e7a1ef5c4dc2e9b603dfa89d3cb.zip
fixes #1740 Public ID hash API
This includes a manual page documenting the entire set of functions in one step. The hash is 64-bit based for now, to be maximally flexible. An internal 32-bit convenience for the common internal use is also provided (not public). The public API includes a test suite.
-rw-r--r--cmake/NNGOptions.cmake5
-rw-r--r--docs/man/CMakeLists.txt1
-rw-r--r--docs/man/libnng.3.adoc1
-rw-r--r--docs/man/nng_id_map.3supp.adoc100
-rw-r--r--src/core/dialer.c2
-rw-r--r--src/core/id_test.c10
-rw-r--r--src/core/idhash.c34
-rw-r--r--src/core/idhash.h22
-rw-r--r--src/core/listener.c2
-rw-r--r--src/core/pipe.c2
-rw-r--r--src/core/socket.c4
-rw-r--r--src/sp/protocol/reqrep0/req.c2
-rw-r--r--src/sp/protocol/survey0/survey.c2
-rw-r--r--src/supplemental/CMakeLists.txt1
-rw-r--r--src/supplemental/idhash/CMakeLists.txt11
-rw-r--r--src/supplemental/idhash/idhash.c60
-rw-r--r--src/supplemental/idhash/idhash.h27
-rw-r--r--src/supplemental/idhash/idhash_test.c309
18 files changed, 562 insertions, 33 deletions
diff --git a/cmake/NNGOptions.cmake b/cmake/NNGOptions.cmake
index 12075f4c..d6240bc8 100644
--- a/cmake/NNGOptions.cmake
+++ b/cmake/NNGOptions.cmake
@@ -141,3 +141,8 @@ if (NNG_TRANSPORT_WS OR NNG_TRANSPORT_WSS)
set(NNG_SUPP_BASE64 ON)
set(NNG_SUPP_SHA1 ON)
endif()
+
+# ID hash API is small wrapper around core, probably should always be enabled unless memory
+# is extraordinarily constrained.
+option(NNG_SUPP_IDHASH "Enable application IDHASH API" ON)
+mark_as_advanced(NNG_SUPP_IDHASH) \ No newline at end of file
diff --git a/docs/man/CMakeLists.txt b/docs/man/CMakeLists.txt
index 17d43c8e..9e3e8128 100644
--- a/docs/man/CMakeLists.txt
+++ b/docs/man/CMakeLists.txt
@@ -295,6 +295,7 @@ if (NNG_ENABLE_DOC)
nng_cv_wait
nng_cv_wake
nng_cv_wake1
+ nng_id_map
nng_msleep
nng_mtx_alloc
nng_mtx_free
diff --git a/docs/man/libnng.3.adoc b/docs/man/libnng.3.adoc
index c9df9c0d..7153b14f 100644
--- a/docs/man/libnng.3.adoc
+++ b/docs/man/libnng.3.adoc
@@ -298,6 +298,7 @@ as a convenience to aid in creating portable applications.
|xref:nng_cv_wait.3supp.adoc[nng_cv_wait()]|wait for condition
|xref:nng_cv_wake.3supp.adoc[nng_cv_wake()]|wake all waiters
|xref:nng_cv_wake1.3supp.adoc[nng_cv_wake1()]|wake one waiter
+|xref:nng_id_map.3supp.adoc[nng_id_map]|identifier based mapping table
|xref:nng_msleep.3supp.adoc[nng_msleep()]|sleep for milliseconds
|xref:nng_mtx_alloc.3supp.adoc[nng_mtx_alloc()]|allocate mutex
|xref:nng_mtx_free.3supp.adoc[nng_mtx_free()]|free mutex
diff --git a/docs/man/nng_id_map.3supp.adoc b/docs/man/nng_id_map.3supp.adoc
new file mode 100644
index 00000000..bd5dace3
--- /dev/null
+++ b/docs/man/nng_id_map.3supp.adoc
@@ -0,0 +1,100 @@
+= nng_id_map(3supp)
+//
+// Copyright 2023 Staysail Systems, Inc. <info@staysail.tech>
+//
+// This document is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+== NAME
+
+nng_id_map - identifier based mapping table
+
+== SYNOPSIS
+
+[source, c]
+----
+#include <nng/nng.h>
+#include <nng/supplemental/idhash/idhash.h>
+
+typedef struct nng_id_map_s nng_id_map;
+
+#define NNG_MAP_RANDOM 1
+
+int nng_id_map_alloc(nng_id_map **map_p, uint64_t lo, uint64_t hi, int flags);
+void nng_id_map_free(nng_id_map *map);
+void *nng_id_get(nng_id_map *map, uint64_t id);
+int nng_id_set(nng_id_map *map, uint64_t, void *value);
+int nng_id_alloc(nng_id_map *map, uint64_t *id_p, void *value);
+int nng_id_remove(nng_id_map *map, uint64_t id);
+
+----
+
+== DESCRIPTION
+
+These functions provide support for managing tables of data based on
+identifiers, ensuring that identifiers are allocated uniquely and within
+specified range limits.
+
+The table stores data pointers (which must not be `NULL`) at a logical numeric index.
+It does so efficiently, even if large gaps exist, and it provides a means to efficiently
+allocate a numeric identifier from a pool of unused identifiers.
+
+Identifiers are allocated in increasing order, without reusing old identifiers until the
+largest possible identifier is allocated. After wrapping, only identifiers that are no longer
+in use will be considered.
+No effort to order the availability of identifiers based on when they were freed is made.
+
+An initial table is allocated with `nng_id_map_alloc()`, which takes the lowest legal identifier in _lo_,
+and the largest legal identifier in _hi_.
+The new table is returned in _map_p_, and should be used as the _map_ argument to the rest of these functions.
+
+****
+As a special convenience, if these are specified as zero, then a full range of 32-bit identifiers is assumed.
+If identifiers larger than or equal to 2^32^ are required, then both _lo_ and _hi_ must be specified with the
+exact values desired.
+****
+
+The _flags_ argument is a bit mask of flags for the table.
+If `NNG_MAP_RANDOM` is specified, then the starting point for allocations is randomized, but subsequent allocations will then be monotonically increasing.
+This is useful to reduce the odds of different instances of an application using the same identifiers at the same time.
+
+The `nng_id_get()` function returns the value previously stored with the given identifier.
+If no value is currently associated with the identifer, it returns `NULL`.
+
+The `nng_id_set()` function sets the value with the associated identifier.
+This can be used to replace a previously allocated identifier.
+If the identifier was not previously allocated, then it is allocated as part of the call.
+This function does not necessarily honor the identifier range limits set for the map when it was allocated.
+
+The `nng_id_alloc()` function allocates a new identifier from the range for the map, and associates it with
+the supplied _value_.
+
+The `nng_id_remove()` function removes the identifier and its associated value from the table.
+
+NOTE: These functions are limited to storing at most 2^32^ identifiers, even though the identifers may
+themselves be larger than 2^32^.
+
+IMPORTANT: These functions are *not* thread-safe.
+Callers should use a xref:nng_mtx_lock.3supp[mutex] or similar approach when thread-safety is needed.
+
+== RETURN VALUES
+
+The `nng_id_map_alloc()`, `nng_id_set()`, `nng_id_alloc()`, and `nng_id_remove()` functions
+return 0 on success, or -1 on failure.
+
+The `nng_id_map_get()` function returns the requested data pointer, or `NULL` if the identifier was not found.
+
+== ERRORS
+
+[horizontal]
+`NNG_ENOENT`:: The _id_ does not exist in the table.
+`NNG_ENOMEM`:: Insufficient memory is available, or the table is full.
+
+== SEE ALSO
+
+[.text-left]
+xref:nng_mtx_lock.3supp.adoc[nng(7)]
+xref:nng.7.adoc[nng(7)]
diff --git a/src/core/dialer.c b/src/core/dialer.c
index 91d18dc8..b1ded52f 100644
--- a/src/core/dialer.c
+++ b/src/core/dialer.c
@@ -259,7 +259,7 @@ nni_dialer_create(nni_dialer **dp, nni_sock *s, const char *url_str)
nni_aio_init(&d->d_tmo_aio, dialer_timer_cb, d);
nni_mtx_lock(&dialers_lk);
- rv = nni_id_alloc(&dialers, &d->d_id, d);
+ rv = nni_id_alloc32(&dialers, &d->d_id, d);
nni_mtx_unlock(&dialers_lk);
#ifdef NNG_ENABLE_STATS
diff --git a/src/core/id_test.c b/src/core/id_test.c
index b948cc13..78be63b8 100644
--- a/src/core/id_test.c
+++ b/src/core/id_test.c
@@ -1,5 +1,5 @@
//
-// Copyright 2021 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2023 Staysail Systems, Inc. <info@staysail.tech>
//
// This software is supplied under the terms of the MIT License, a
// copy of which should be located in the distribution where this
@@ -39,7 +39,7 @@ void
test_random(void)
{
int i;
- uint32_t id;
+ uint64_t id;
for (i = 0; i < 2; i++) {
nni_id_map m;
nni_id_map_init(&m, 0, 0, true);
@@ -94,7 +94,7 @@ void
test_not_found(void)
{
nni_id_map m;
- uint32_t id;
+ uint64_t id;
nni_id_map_init(&m, 0, 0, false);
NUTS_PASS(nni_id_alloc(&m, &id, &id));
@@ -137,7 +137,7 @@ test_dynamic(void)
{
nni_id_map m;
int expect[5];
- uint32_t id;
+ uint64_t id;
nni_id_map_init(&m, 10, 13, false);
@@ -175,7 +175,7 @@ test_set_out_of_range(void)
// We can insert outside the range forcibly.
NUTS_PASS(nni_id_set(&m, 1, &x));
NUTS_PASS(nni_id_set(&m, 100, &x));
- NUTS_PASS(nni_id_alloc(&m, &id, &x));
+ NUTS_PASS(nni_id_alloc32(&m, &id, &x));
NUTS_TRUE(id == 10);
nni_id_map_fini(&m);
}
diff --git a/src/core/idhash.c b/src/core/idhash.c
index c56c8191..67ae67ea 100644
--- a/src/core/idhash.c
+++ b/src/core/idhash.c
@@ -1,5 +1,5 @@
//
-// Copyright 2021 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2023 Staysail Systems, Inc. <info@staysail.tech>
// Copyright 2018 Capitar IT Group BV <info@capitar.com>
//
// This software is supplied under the terms of the MIT License, a
@@ -13,7 +13,7 @@
#include <string.h>
struct nni_id_entry {
- uint32_t key;
+ uint64_t key;
uint32_t skips;
void *val;
};
@@ -24,7 +24,7 @@ static nni_id_map **id_reg_map = NULL;
static nni_mtx id_reg_mtx = NNI_MTX_INITIALIZER;
void
-nni_id_map_init(nni_id_map *m, uint32_t lo, uint32_t hi, bool randomize)
+nni_id_map_init(nni_id_map *m, uint64_t lo, uint64_t hi, bool randomize)
{
if (lo == 0) {
lo = 1;
@@ -68,7 +68,7 @@ nni_id_map_fini(nni_id_map *m)
#define ID_INDEX(m, j) ((j) & (m->id_cap - 1))
static size_t
-id_find(nni_id_map *m, uint32_t id)
+id_find(nni_id_map *m, uint64_t id)
{
size_t index;
size_t start;
@@ -98,7 +98,7 @@ id_find(nni_id_map *m, uint32_t id)
}
void *
-nni_id_get(nni_id_map *m, uint32_t id)
+nni_id_get(nni_id_map *m, uint64_t id)
{
size_t index;
if ((index = id_find(m, id)) == (size_t) -1) {
@@ -130,7 +130,8 @@ id_map_register(nni_id_map *m)
}
id_reg_len = len;
if (id_reg_map != NULL)
- memcpy(mr, id_reg_map, id_reg_num * sizeof(nni_id_map *));
+ memcpy(
+ mr, id_reg_map, id_reg_num * sizeof(nni_id_map *));
id_reg_map = mr;
}
id_reg_map[id_reg_num++] = m;
@@ -233,7 +234,7 @@ id_resize(nni_id_map *m)
}
int
-nni_id_remove(nni_id_map *m, uint32_t id)
+nni_id_remove(nni_id_map *m, uint64_t id)
{
size_t index;
size_t probe;
@@ -251,7 +252,7 @@ nni_id_remove(nni_id_map *m, uint32_t id)
nni_id_entry *entry;
// The load was increased once each hashing operation we used
- // to place the the item. Decrement it accordingly.
+ // to place the item. Decrement it accordingly.
m->id_load--;
entry = &m->id_entries[probe];
if (probe == index) {
@@ -273,7 +274,7 @@ nni_id_remove(nni_id_map *m, uint32_t id)
}
int
-nni_id_set(nni_id_map *m, uint32_t id, void *val)
+nni_id_set(nni_id_map *m, uint64_t id, void *val)
{
size_t index;
nni_id_entry *ent;
@@ -314,9 +315,9 @@ nni_id_set(nni_id_map *m, uint32_t id, void *val)
}
int
-nni_id_alloc(nni_id_map *m, uint32_t *idp, void *val)
+nni_id_alloc(nni_id_map *m, uint64_t *idp, void *val)
{
- uint32_t id;
+ uint64_t id;
int rv;
NNI_ASSERT(val != NULL);
@@ -355,3 +356,14 @@ nni_id_alloc(nni_id_map *m, uint32_t *idp, void *val)
}
return (rv);
}
+
+int
+nni_id_alloc32(nni_id_map *m, uint32_t *idp, void *val)
+{
+ uint64_t id;
+ int rv;
+ rv = nni_id_alloc(m, &id, val);
+ NNI_ASSERT(id < (1ULL << 32));
+ *idp = (uint32_t) id;
+ return (rv);
+} \ No newline at end of file
diff --git a/src/core/idhash.h b/src/core/idhash.h
index e8894dfb..b2c07062 100644
--- a/src/core/idhash.h
+++ b/src/core/idhash.h
@@ -1,5 +1,5 @@
//
-// Copyright 2021 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2023 Staysail Systems, Inc. <info@staysail.tech>
// Copyright 2018 Capitar IT Group BV <info@capitar.com>
//
// This software is supplied under the terms of the MIT License, a
@@ -28,16 +28,17 @@ typedef struct nni_id_entry nni_id_entry;
// NB: These details are entirely private to the hash implementation.
// They are provided here to facilitate inlining in structures.
+// We can support at most 2^32 ~ 4 billion ~ entries.
struct nni_id_map {
+ uint32_t id_flags;
uint32_t id_cap;
uint32_t id_count;
uint32_t id_load;
uint32_t id_min_load; // considers placeholders
uint32_t id_max_load;
- uint32_t id_min_val;
- uint32_t id_max_val;
- uint32_t id_dyn_val;
- uint32_t id_flags;
+ uint64_t id_min_val;
+ uint64_t id_max_val;
+ uint64_t id_dyn_val;
nni_id_entry *id_entries;
};
@@ -45,12 +46,13 @@ struct nni_id_map {
#define NNI_ID_FLAG_RANDOM 2 // start at a random value
#define NNI_ID_FLAG_REGISTER 4 // map is registered for finalization
-extern void nni_id_map_init(nni_id_map *, uint32_t, uint32_t, bool);
+extern void nni_id_map_init(nni_id_map *, uint64_t, uint64_t, bool);
extern void nni_id_map_fini(nni_id_map *);
-extern void *nni_id_get(nni_id_map *, uint32_t);
-extern int nni_id_set(nni_id_map *, uint32_t, void *);
-extern int nni_id_alloc(nni_id_map *, uint32_t *, void *);
-extern int nni_id_remove(nni_id_map *, uint32_t);
+extern void *nni_id_get(nni_id_map *, uint64_t);
+extern int nni_id_set(nni_id_map *, uint64_t, void *);
+extern int nni_id_alloc(nni_id_map *, uint64_t *, void *);
+extern int nni_id_alloc32(nni_id_map *, uint32_t *, void *);
+extern int nni_id_remove(nni_id_map *, uint64_t);
extern void nni_id_map_sys_fini(void);
#define NNI_ID_MAP_INITIALIZER(min, max, flags) \
diff --git a/src/core/listener.c b/src/core/listener.c
index 7d3e3e0d..53d5c843 100644
--- a/src/core/listener.c
+++ b/src/core/listener.c
@@ -243,7 +243,7 @@ nni_listener_create(nni_listener **lp, nni_sock *s, const char *url_str)
nni_aio_init(&l->l_tmo_aio, listener_timer_cb, l);
nni_mtx_lock(&listeners_lk);
- rv = nni_id_alloc(&listeners, &l->l_id, l);
+ rv = nni_id_alloc32(&listeners, &l->l_id, l);
nni_mtx_unlock(&listeners_lk);
#ifdef NNG_ENABLE_STATS
diff --git a/src/core/pipe.c b/src/core/pipe.c
index 58267d69..10a1f35a 100644
--- a/src/core/pipe.c
+++ b/src/core/pipe.c
@@ -259,7 +259,7 @@ pipe_create(nni_pipe **pp, nni_sock *sock, nni_sp_tran *tran, void *tran_data)
nni_cv_init(&p->p_cv, &pipes_lk);
nni_mtx_lock(&pipes_lk);
- rv = nni_id_alloc(&pipes, &p->p_id, p);
+ rv = nni_id_alloc32(&pipes, &p->p_id, p);
nni_mtx_unlock(&pipes_lk);
#ifdef NNG_ENABLE_STATS
diff --git a/src/core/socket.c b/src/core/socket.c
index 1f44ebfa..d9fd5266 100644
--- a/src/core/socket.c
+++ b/src/core/socket.c
@@ -641,7 +641,7 @@ nni_sock_open(nni_sock **sockp, const nni_proto *proto)
}
nni_mtx_lock(&sock_lk);
- if ((rv = nni_id_alloc(&sock_ids, &s->s_id, s)) != 0) {
+ if ((rv = nni_id_alloc32(&sock_ids, &s->s_id, s)) != 0) {
nni_mtx_unlock(&sock_lk);
sock_destroy(s);
return (rv);
@@ -1343,7 +1343,7 @@ nni_ctx_open(nni_ctx **ctxp, nni_sock *sock)
nni_free(ctx, ctx->c_size);
return (NNG_ECLOSED);
}
- if ((rv = nni_id_alloc(&ctx_ids, &ctx->c_id, ctx)) != 0) {
+ if ((rv = nni_id_alloc32(&ctx_ids, &ctx->c_id, ctx)) != 0) {
nni_mtx_unlock(&sock_lk);
nni_free(ctx, ctx->c_size);
return (rv);
diff --git a/src/sp/protocol/reqrep0/req.c b/src/sp/protocol/reqrep0/req.c
index 7e3e3db8..35c496f0 100644
--- a/src/sp/protocol/reqrep0/req.c
+++ b/src/sp/protocol/reqrep0/req.c
@@ -717,7 +717,7 @@ req0_ctx_send(void *arg, nni_aio *aio)
req0_ctx_reset(ctx);
// Insert us on the per ID hash list, so that receives can find us.
- if ((rv = nni_id_alloc(&s->requests, &ctx->request_id, ctx)) != 0) {
+ if ((rv = nni_id_alloc32(&s->requests, &ctx->request_id, ctx)) != 0) {
nni_mtx_unlock(&s->mtx);
nni_aio_finish_error(aio, rv);
return;
diff --git a/src/sp/protocol/survey0/survey.c b/src/sp/protocol/survey0/survey.c
index 18074016..dc7346c7 100644
--- a/src/sp/protocol/survey0/survey.c
+++ b/src/sp/protocol/survey0/survey.c
@@ -220,7 +220,7 @@ surv0_ctx_send(void *arg, nni_aio *aio)
surv0_ctx_abort(ctx, NNG_ECANCELED);
// Allocate the new ID.
- if ((rv = nni_id_alloc(&sock->surveys, &ctx->survey_id, ctx)) != 0) {
+ if ((rv = nni_id_alloc32(&sock->surveys, &ctx->survey_id, ctx)) != 0) {
nni_mtx_unlock(&sock->mtx);
nni_aio_finish_error(aio, rv);
return;
diff --git a/src/supplemental/CMakeLists.txt b/src/supplemental/CMakeLists.txt
index 5be439b1..3fa254e7 100644
--- a/src/supplemental/CMakeLists.txt
+++ b/src/supplemental/CMakeLists.txt
@@ -11,6 +11,7 @@ nng_directory(supplemental)
add_subdirectory(base64)
add_subdirectory(http)
+add_subdirectory(idhash)
add_subdirectory(sha1)
add_subdirectory(tls)
add_subdirectory(util)
diff --git a/src/supplemental/idhash/CMakeLists.txt b/src/supplemental/idhash/CMakeLists.txt
new file mode 100644
index 00000000..3a9bbb0b
--- /dev/null
+++ b/src/supplemental/idhash/CMakeLists.txt
@@ -0,0 +1,11 @@
+#
+# Copyright 2023 Staysail Systems, Inc. <info@staysail.tech>
+#
+# This software is supplied under the terms of the MIT License, a
+# copy of which should be located in the distribution where this
+# file was obtained (LICENSE.txt). A copy of the license may also be
+# found online at https://opensource.org/licenses/MIT.
+#
+
+nng_sources_if(NNG_SUPP_IDHASH idhash.c idhash.h)
+nng_test_if(NNG_SUPP_IDHASH idhash_test)
diff --git a/src/supplemental/idhash/idhash.c b/src/supplemental/idhash/idhash.c
new file mode 100644
index 00000000..051b686a
--- /dev/null
+++ b/src/supplemental/idhash/idhash.c
@@ -0,0 +1,60 @@
+//
+// Copyright 2023 Staysail Systems, Inc. <info@staysail.tech>
+//
+// This software is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+#include "supplemental/idhash/idhash.h"
+#include "core/nng_impl.h"
+
+struct nng_id_map_s {
+ nni_id_map m;
+};
+
+int
+nng_id_map_alloc(nng_id_map **map, uint64_t lo, uint64_t hi, int flags)
+{
+ nng_id_map *m;
+
+ if ((m = NNI_ALLOC_STRUCT(m)) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ nni_id_map_init(
+ &m->m, lo, hi, (flags & NNG_MAP_RANDOM) ? true : false);
+ *map = m;
+ return (0);
+}
+
+void
+nng_id_map_free(nng_id_map *map)
+{
+ nni_id_map_fini(&map->m);
+ NNI_FREE_STRUCT(map);
+}
+
+void *
+nng_id_get(nng_id_map *map, uint64_t id)
+{
+ return (nni_id_get(&map->m, id));
+}
+
+int
+nng_id_set(nng_id_map *map, uint64_t id, void *val)
+{
+ return (nni_id_set(&map->m, id, val));
+}
+
+int
+nng_id_remove(nng_id_map *map, uint64_t id)
+{
+ return (nni_id_remove(&map->m, id));
+}
+
+int
+nng_id_alloc(nng_id_map *map, uint64_t *id, void *val)
+{
+ return (nni_id_alloc(&map->m, id, val));
+}
diff --git a/src/supplemental/idhash/idhash.h b/src/supplemental/idhash/idhash.h
new file mode 100644
index 00000000..5fd8f235
--- /dev/null
+++ b/src/supplemental/idhash/idhash.h
@@ -0,0 +1,27 @@
+//
+// Copyright 2023 Staysail Systems, Inc. <info@staysail.tech>
+//
+// This software is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+#ifndef NNG_SUPPLEMENTAL_IDHASH_IDHASH_H
+#define NNG_SUPPLEMENTAL_IDHASH_IDHASH_H
+
+#include <nng/nng.h>
+
+typedef struct nng_id_map_s nng_id_map;
+
+#define NNG_MAP_RANDOM 1
+
+NNG_DECL int nng_id_map_alloc(
+ nng_id_map **map, uint64_t lo, uint64_t hi, int flags);
+NNG_DECL void nng_id_map_free(nng_id_map *map);
+NNG_DECL void *nng_id_get(nng_id_map *, uint64_t);
+NNG_DECL int nng_id_set(nng_id_map *, uint64_t, void *);
+NNG_DECL int nng_id_alloc(nng_id_map *, uint64_t *, void *);
+NNG_DECL int nng_id_remove(nng_id_map *, uint64_t);
+
+#endif // NNG_SUPPLEMENTAL_IDHASH_IDHASH_H
diff --git a/src/supplemental/idhash/idhash_test.c b/src/supplemental/idhash/idhash_test.c
new file mode 100644
index 00000000..367f34bc
--- /dev/null
+++ b/src/supplemental/idhash/idhash_test.c
@@ -0,0 +1,309 @@
+//
+// Copyright 2023 Staysail Systems, Inc. <info@staysail.tech>
+//
+// This software is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+#include <nuts.h>
+
+#include "idhash.h"
+
+void
+test_id_basic(void)
+{
+ nng_id_map *m;
+ char *five = "five";
+ char *four = "four";
+
+ NUTS_PASS(nng_id_map_alloc(&m, 0, 0, 0));
+
+ // insert it
+ NUTS_PASS(nng_id_set(m, 5, five));
+ // retrieve it
+ NUTS_TRUE(nng_id_get(m, 5) == five);
+
+ // change it
+ NUTS_PASS(nng_id_set(m, 5, four));
+ NUTS_TRUE(nng_id_get(m, 5) == four);
+
+ // delete
+ NUTS_PASS(nng_id_remove(m, 5));
+
+ nng_id_map_free(m);
+}
+
+void
+test_id_random(void)
+{
+ int i;
+ uint64_t id;
+ for (i = 0; i < 2; i++) {
+ nng_id_map *m;
+ NUTS_PASS(nng_id_map_alloc(&m, 0, 0, NNG_MAP_RANDOM));
+ NUTS_PASS(nng_id_alloc(m, &id, &id));
+ nng_id_map_free(m);
+ NUTS_TRUE(id != 0);
+ if (id != 1) {
+ break;
+ }
+ // one chance in 4 billion, but try again
+ }
+
+ NUTS_TRUE(id != 1);
+ NUTS_TRUE(i < 2);
+}
+
+void
+test_id_collision(void)
+{
+ nng_id_map *m;
+ char *five = "five";
+ char *four = "four";
+
+ NUTS_PASS(nng_id_map_alloc(&m, 0, 0, 0));
+
+ // Carefully crafted -- 13 % 8 == 5.
+ NUTS_PASS(nng_id_set(m, 5, five));
+ NUTS_PASS(nng_id_set(m, 13, four));
+ NUTS_TRUE(nng_id_get(m, 5) == five);
+ NUTS_TRUE(nng_id_get(m, 13) == four);
+
+ // Delete the intermediate
+ NUTS_PASS(nng_id_remove(m, 5));
+ NUTS_TRUE(nng_id_get(m, 13) == four);
+
+ nng_id_map_free(m);
+}
+
+void
+test_id_empty(void)
+{
+ nng_id_map *m;
+
+ NUTS_PASS(nng_id_map_alloc(&m, 0, 0, 0));
+
+ NUTS_TRUE(nng_id_get(m, 42) == NULL);
+ NUTS_FAIL(nng_id_remove(m, 42), NNG_ENOENT);
+ NUTS_FAIL(nng_id_remove(m, 1), NNG_ENOENT);
+ nng_id_map_free(m);
+}
+
+void
+test_id_not_found(void)
+{
+ nng_id_map *m;
+ uint64_t id;
+
+ NUTS_PASS(nng_id_map_alloc(&m, 0, 0, 0));
+
+ NUTS_PASS(nng_id_alloc(m, &id, &id));
+ NUTS_FAIL(nng_id_remove(m, 42), NNG_ENOENT);
+ NUTS_FAIL(nng_id_remove(m, 2), NNG_ENOENT);
+ NUTS_PASS(nng_id_remove(m, id));
+ nng_id_map_free(m);
+}
+
+void
+test_id_resize(void)
+{
+ nng_id_map *m;
+ int rv;
+ int i;
+ int expect[1024];
+
+ for (i = 0; i < 1024; i++) {
+ expect[i] = i;
+ }
+
+ NUTS_PASS(nng_id_map_alloc(&m, 0, 0, 0));
+
+ for (i = 0; i < 1024; i++) {
+ if ((rv = nng_id_set(m, i, &expect[i])) != 0) {
+ NUTS_PASS(rv);
+ }
+ }
+
+ for (i = 0; i < 1024; i++) {
+ if ((rv = nng_id_remove(m, i)) != 0) {
+ NUTS_PASS(rv);
+ }
+ }
+ nng_id_map_free(m);
+}
+
+void
+test_id_dynamic(void)
+{
+ nng_id_map *m;
+ int expect[5];
+ uint64_t id;
+
+ NUTS_PASS(nng_id_map_alloc(&m, 10, 13, 0));
+
+ // We can fill the table.
+ NUTS_PASS(nng_id_alloc(m, &id, &expect[0]));
+ NUTS_TRUE(id == 10);
+ NUTS_PASS(nng_id_alloc(m, &id, &expect[1]));
+ NUTS_TRUE(id == 11);
+ NUTS_PASS(nng_id_alloc(m, &id, &expect[2]));
+ NUTS_TRUE(id == 12);
+ NUTS_PASS(nng_id_alloc(m, &id, &expect[3]));
+ NUTS_TRUE(id == 13);
+
+ // Adding another fails.
+ NUTS_FAIL(nng_id_alloc(m, &id, &expect[4]), NNG_ENOMEM);
+
+ // Delete one.
+ NUTS_PASS(nng_id_remove(m, 11));
+
+ // And now we can allocate one.
+ NUTS_PASS(nng_id_alloc(m, &id, &expect[4]));
+ NUTS_TRUE(id == 11);
+ nng_id_map_free(m);
+}
+
+void
+test_id_set_out_of_range(void)
+{
+ nng_id_map *m;
+ int x;
+ uint64_t id;
+
+ NUTS_PASS(nng_id_map_alloc(&m, 10, 13, 0));
+
+ // We can insert outside the range forcibly.
+ NUTS_PASS(nng_id_set(m, 1, &x));
+ NUTS_PASS(nng_id_set(m, 100, &x));
+ NUTS_PASS(nng_id_alloc(m, &id, &x));
+ NUTS_TRUE(id == 10);
+ nng_id_map_free(m);
+}
+
+#define STRESS_LOAD 50000
+#define NUM_VALUES 1000
+
+void
+test_id_stress(void)
+{
+ void *values[NUM_VALUES];
+ nng_id_map *m;
+ size_t i;
+ int rv;
+ void *x;
+ int v;
+
+ NUTS_PASS(nng_id_map_alloc(&m, 0, 0, 0));
+ for (i = 0; i < NUM_VALUES; i++) {
+ values[i] = NULL;
+ }
+
+ for (i = 0; i < STRESS_LOAD; i++) {
+ v = rand() % NUM_VALUES; // Keep it constrained
+
+ switch (rand() & 3) {
+ case 0:
+ x = &values[rand() % NUM_VALUES];
+ values[v] = x;
+ if ((rv = nng_id_set(m, v, x)) != 0) {
+ NUTS_PASS(rv);
+ goto out;
+ }
+ break;
+
+ case 1:
+ rv = nng_id_remove(m, v);
+ if (values[v] == NULL) {
+ if (rv != NNG_ENOENT) {
+ NUTS_FAIL(rv, NNG_ENOENT);
+ goto out;
+ }
+ } else {
+ values[v] = NULL;
+ if (rv != 0) {
+ NUTS_PASS(rv);
+ goto out;
+ }
+ }
+ break;
+ case 2:
+ x = nng_id_get(m, v);
+ if (x != values[v]) {
+ NUTS_TRUE(x == values[v]);
+ goto out;
+ }
+ break;
+ }
+ }
+out:
+ NUTS_TRUE(i == STRESS_LOAD);
+
+ // Post stress check.
+ for (i = 0; i < NUM_VALUES; i++) {
+ x = nng_id_get(m, (uint32_t) i);
+ if (x != values[i]) {
+ NUTS_TRUE(x == values[i]);
+ break;
+ }
+
+ // We only use the test macros if we know they are going
+ // to fail. Otherwise, there will be too many errors reported.
+ rv = nng_id_remove(m, (uint32_t) i);
+ if ((x == NULL) && (rv != NNG_ENOENT)) {
+ NUTS_FAIL(rv, NNG_ENOENT);
+ } else if ((x != NULL) && (rv != 0)) {
+ NUTS_PASS(rv);
+ }
+ }
+ NUTS_TRUE(i == NUM_VALUES);
+
+ nng_id_map_free(m);
+}
+
+void
+test_id_alloc_long_long(void)
+{
+#define TEST_IDS 100
+ nng_id_map *m;
+ int x;
+ uint64_t ids[TEST_IDS];
+
+ NUTS_PASS(nng_id_map_alloc(&m, 1ULL << 32, (int64_t) -1, 0));
+
+ // We can insert outside the range forcibly - making sure we are
+ // choosing numbers above 64 bits.
+ for (int i = 0; i < TEST_IDS; i++) {
+ NUTS_PASS(nng_id_alloc(m, &ids[i], &x));
+ NUTS_ASSERT(ids[i] > 0xFFFFFFFFULL);
+ }
+ for (int i = 0; i < TEST_IDS; i++) {
+ bool matched = false;
+ for (int j = 0; j < i; j++) {
+ // only dump the assertion on failure
+ // otherwise it is too noisy
+ if (ids[i] == ids[j]) {
+ matched = true;
+ break;
+ }
+ }
+ NUTS_ASSERT(!matched);
+ }
+ nng_id_map_free(m);
+#undef TEST_IDS
+}
+
+NUTS_TESTS = {
+ { "id basic", test_id_basic },
+ { "id random", test_id_random },
+ { "id collision", test_id_collision },
+ { "id empty", test_id_empty },
+ { "not found", test_id_not_found },
+ { "id resize", test_id_resize },
+ { "id dynamic", test_id_dynamic },
+ { "id set out of range", test_id_set_out_of_range },
+ { "id stress", test_id_stress },
+ { "id alloc long long", test_id_alloc_long_long },
+ { NULL, NULL },
+};