From 3298ac1e93742e7a1ef5c4dc2e9b603dfa89d3cb Mon Sep 17 00:00:00 2001 From: Garrett D'Amore Date: Fri, 29 Dec 2023 17:43:50 -0800 Subject: [PATCH] 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. --- cmake/NNGOptions.cmake | 5 + docs/man/CMakeLists.txt | 1 + docs/man/libnng.3.adoc | 1 + docs/man/nng_id_map.3supp.adoc | 100 ++++++++ src/core/dialer.c | 2 +- src/core/id_test.c | 10 +- src/core/idhash.c | 34 ++- src/core/idhash.h | 22 +- src/core/listener.c | 2 +- src/core/pipe.c | 2 +- src/core/socket.c | 4 +- src/sp/protocol/reqrep0/req.c | 2 +- src/sp/protocol/survey0/survey.c | 2 +- src/supplemental/CMakeLists.txt | 1 + src/supplemental/idhash/CMakeLists.txt | 11 + src/supplemental/idhash/idhash.c | 60 +++++ src/supplemental/idhash/idhash.h | 27 +++ src/supplemental/idhash/idhash_test.c | 309 +++++++++++++++++++++++++ 18 files changed, 562 insertions(+), 33 deletions(-) create mode 100644 docs/man/nng_id_map.3supp.adoc create mode 100644 src/supplemental/idhash/CMakeLists.txt create mode 100644 src/supplemental/idhash/idhash.c create mode 100644 src/supplemental/idhash/idhash.h create mode 100644 src/supplemental/idhash/idhash_test.c diff --git a/cmake/NNGOptions.cmake b/cmake/NNGOptions.cmake index 12075f4c1..d6240bc8b 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 17d43c8ef..9e3e81283 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 c9df9c0d2..7153b14fc 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 000000000..bd5dace30 --- /dev/null +++ b/docs/man/nng_id_map.3supp.adoc @@ -0,0 +1,100 @@ += nng_id_map(3supp) +// +// Copyright 2023 Staysail Systems, Inc. +// +// 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 +#include + +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 91d18dc8c..b1ded52fb 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 b948cc132..78be63b85 100644 --- a/src/core/id_test.c +++ b/src/core/id_test.c @@ -1,5 +1,5 @@ // -// Copyright 2021 Staysail Systems, Inc. +// Copyright 2023 Staysail Systems, Inc. // // 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 c56c81912..67ae67eaf 100644 --- a/src/core/idhash.c +++ b/src/core/idhash.c @@ -1,5 +1,5 @@ // -// Copyright 2021 Staysail Systems, Inc. +// Copyright 2023 Staysail Systems, Inc. // Copyright 2018 Capitar IT Group BV // // This software is supplied under the terms of the MIT License, a @@ -13,7 +13,7 @@ #include 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 e8894dfb8..b2c070621 100644 --- a/src/core/idhash.h +++ b/src/core/idhash.h @@ -1,5 +1,5 @@ // -// Copyright 2021 Staysail Systems, Inc. +// Copyright 2023 Staysail Systems, Inc. // Copyright 2018 Capitar IT Group BV // // 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 7d3e3e0d4..53d5c843b 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 58267d694..10a1f35af 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 1f44ebfa3..d9fd52660 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 7e3e3db81..35c496f0d 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 18074016e..dc7346c7b 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 5be439b11..3fa254e79 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 000000000..3a9bbb0bd --- /dev/null +++ b/src/supplemental/idhash/CMakeLists.txt @@ -0,0 +1,11 @@ +# +# Copyright 2023 Staysail Systems, Inc. +# +# 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 000000000..051b686ac --- /dev/null +++ b/src/supplemental/idhash/idhash.c @@ -0,0 +1,60 @@ +// +// Copyright 2023 Staysail Systems, Inc. +// +// 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 000000000..5fd8f235c --- /dev/null +++ b/src/supplemental/idhash/idhash.h @@ -0,0 +1,27 @@ +// +// Copyright 2023 Staysail Systems, Inc. +// +// 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 + +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 000000000..367f34bcc --- /dev/null +++ b/src/supplemental/idhash/idhash_test.c @@ -0,0 +1,309 @@ +// +// Copyright 2023 Staysail Systems, Inc. +// +// 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 + +#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 }, +};