diff --git a/READMEs/README.lws_map.md b/READMEs/README.lws_map.md new file mode 100644 index 000000000..7772d6602 --- /dev/null +++ b/READMEs/README.lws_map.md @@ -0,0 +1,117 @@ +# lws_map generic map abstraction + +||| +|---|---|---| +|cmake|core feature| +|Header| ./include/libwebsockets/lws-map.h| +|api-test| ./minimal-examples/api-tests/api-test-lws_map/| + +lws_map provides a robust abstraction for containing a collection of items that +map key objects to value objects, where both the key and value objects may +differ in size each time and have any type. + +Its one-level linked-list hashtables are useful up to hundreds or low thousands +of items in the map on may platforms. + +The map itself and the items inside it are opaque. + +## Creating and destroying the map + +The user should prepare a `lws_map_info_t` object, it's legal for it to be +all zeros to select defaults, an 8-way hashtable with item allocation from heap, +simple bytewise key comparison, and xor / shift key hashing. These are often +what you want simplifying construction. + +The info object allows user override of item allocator, freeing, key comparison +and object hashing, allowing custom objects to be keys if desired. + +Custom allocator / free implementations for using lwsac for item allocation are +provided to simplify that case. + +Just call `lws_map_create()` with the info struct to create the map, later it +and all its contents can be destroyed with `lws_map_destroy()`. The info struct +can go out of scope immediately after the create call. + +``` +lws_map_t * +lws_map_create(const lws_map_info_t *info); +void +lws_map_destroy(lws_map_t **pmap); +``` + +## Keys in lws_map + +Items are managed in the map by a key, this may be, eg, a string, but it also +can be an arbitrary object itself. If comparing keys takes more than a simple +bytewise comparison, the map creation info struct ._compare() operation should +be overridden with a user-supplied one that knows how to use the user's +custom key objects. + +Keys are not required to be the same length, so objects with variable size +overallocation can be used as keys. + +Keys and values are copied into allocations inside the map, the original objects +they are copied from may go out of scope after item creation assuming there are +no pointers to them copied in the objects themselves. + +## Adding items to a map + +The map's info._alloc allocator is used for creating items. By default that +just creates into the heap. + +If you create a new item with the same key as an existing one, the existing one +is destroyed before the new one is created. So there is only one item allowed +at a given key at a time. + +To allocate and create a new item containing the key and value, use +`lws_map_item_create()` + +``` +lws_map_item_t * +lws_map_item_create(lws_map_t *map, + const lws_map_key_t key, size_t keylen, + const lws_map_value_t value, size_t valuelen); +``` + +Eg, + +``` + if (!lws_map_item_create(map, (lws_map_key_t)&my_key, + sizeof(my_key), + (lws_map_value_t)"4567", 4)) + /* fail */ +``` + + +In the case the key is a string, there is a ..._ks wrapper to simplify usage + +``` + if (!lws_map_item_create_ks(map, "123", (lws_map_value_t)"4567", 4)) + /* fail */ +``` + +## Lookups in the map + +You can retreive a pointer to an item in the map with a give key using + +``` +lws_map_item_t * +lws_map_item_lookup(lws_map_t *map, const lws_map_key_t key, size_t keylen); +``` + +The item is opaque, but there are accessors + +|Accessor|Function| +|---|---| +|`lws_map_item_key(lws_map_item_t *_item)`|get a pointer to the item key| +|`lws_map_item_value(lws_map_item_t *_item)`|get a pointer to the item value| +|`lws_map_item_key_len(lws_map_item_t *_item)`|get the key length| +|`lws_map_item_value_len(lws_map_item_t *_item)`|get the value length| + +Again there is a ..._ks() helper to simplify C strings as keys + +``` + item = lws_map_item_lookup_ks(map, "abc"); + if (!item) + /* fail */ +``` diff --git a/include/libwebsockets.h b/include/libwebsockets.h index 5f14f19b4..306fd9016 100644 --- a/include/libwebsockets.h +++ b/include/libwebsockets.h @@ -571,6 +571,8 @@ struct lws_vhost; struct lws; #include +#include + #include #include #include diff --git a/include/libwebsockets/lws-map.h b/include/libwebsockets/lws-map.h new file mode 100644 index 000000000..4462881be --- /dev/null +++ b/include/libwebsockets/lws-map.h @@ -0,0 +1,188 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2021 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/** \defgroup lws_map generic map apis + * ##Generic map structures and apis + * \ingroup lwsapi + * + * lws_map + * + * Discrete owner object represents the whole map, created with key-specific + * ops for hashing the key to a uint32_t and comparing two keys. Owns a list + * of hash tables whose size / modulo it set at creation time. + * + * Items in the map are contained in a lws_map_item_t that is indexed in a + * hash table. + * + * It's difficult to make a single compact map abstraction that fits all cases, + * this is useful to the extent you have the memory to trade off the number of + * hashtables needed for the amount of items and the lookup latency limit for + * your application, typically for hundreds or low thousands of items. + */ +//@{ + +typedef struct lws_map lws_map_t; +typedef struct lws_map_item lws_map_item_t; + +typedef void * lws_map_key_t; +typedef void * lws_map_value_t; +typedef uint32_t lws_map_hash_t; + +typedef lws_map_hash_t (*lws_map_hash_from_key_t)(const lws_map_key_t key, + size_t kl); +typedef int (*lws_map_compare_key_t)(const lws_map_key_t key1, size_t kl1, + const lws_map_value_t key2, size_t kl2); +typedef void * (*lws_map_alloc_t)(struct lws_map *mo, size_t x); +typedef void (*lws_map_free_t)(void *); + +/* + * Creation parameters for the map, copied into the map owner + */ + +typedef struct lws_map_info { + lws_map_hash_from_key_t _hash; + lws_map_compare_key_t _compare; + lws_map_alloc_t _alloc; /* NULL = lws_malloc */ + lws_map_free_t _free; /* NULL = lws_free */ + + void *opaque; + /**< &lwsac if using lwsac allocator */ + void *aux; + /**< chunk size if using lwsac allocator */ + /**< this can be used by the alloc handler, eg for lws_ac */ + size_t modulo; + /**< number of hashed owner lists to create */ +} lws_map_info_t; + +LWS_VISIBLE LWS_EXTERN const void * +lws_map_item_key(lws_map_item_t *_item); +LWS_VISIBLE LWS_EXTERN const void * +lws_map_item_value(lws_map_item_t *_item); +LWS_VISIBLE LWS_EXTERN size_t +lws_map_item_key_len(lws_map_item_t *_item); +LWS_VISIBLE LWS_EXTERN size_t +lws_map_item_value_len(lws_map_item_t *_item); + +/* + * Helpers for C string keys case + */ + +#define lws_map_item_create_ks(_map, _str, _v, _vl) \ + lws_map_item_create(_map, (const lws_map_key_t)_str, \ + strlen(_str), (const lws_map_value_t)_v, \ + _vl) +#define lws_map_item_lookup_ks(_map, _str) \ + lws_map_item_lookup(_map, (const lws_map_key_t)_str, strlen(_str)) + +/** + * lws_map_create() - create a map object and hashtables on heap + * + * \param info: description of map to create + * + * Creates a map object on heap, using lws_malloc(). + * + * \p info may be all zeros inside, if so, modulo defaults to 8, and the + * operation callbacks default to using lws_malloc() / _free() for item alloc, + * a default xor / shift based hash and simple linear memory key compare. + * + * For less typical use-cases, the provided \p info members can be tuned to + * control how the allocation of mapped items is done, lws provides two exports + * lws_map_alloc_lwsac() and lws_map_free_lwsac() that can be used for _alloc + * and _free to have items allocated inside an lwsac. + * + * The map itself is created on the heap directly, the info._alloc() op is only + * used when creating items. + * + * keys have individual memory sizes and do not need to all be the same length. + */ +LWS_VISIBLE LWS_EXTERN lws_map_t * +lws_map_create(const lws_map_info_t *info); + +/* + * helpers that can be used for info._alloc and info._free if using lwsac + * allocation for items, set info.opaque to point to the lwsac pointer, and + * aux to (void *)chunksize, or leave zero / NULL for the default + */ + +LWS_VISIBLE LWS_EXTERN void * +lws_map_alloc_lwsac(struct lws_map *map, size_t x); + +LWS_VISIBLE LWS_EXTERN void +lws_map_free_lwsac(void *v); + +/** + * lws_map_destroy() - deallocate all items and free map + * + * \param pmap: pointer to pointer map object to deallocate + * + * Frees all items in the map, using info._free(), and then frees the map + * from heap directly. \p *pmap is set to NULL. + */ +LWS_VISIBLE LWS_EXTERN void +lws_map_destroy(lws_map_t **pmap); + +/** + * lws_map_item_create() - allocate and map an item into an existing map + * + * \param map: the map to add items into + * \param key: the key, may be any kind of object + * \param keylen: the length of the key in bytes + * \param value: the value, may be any kind of object + * \param valuelen: the length of value + * + * Allocates space for the item, key and value using the map allocator, and + * if non-NULL, copies the key and value into the item. + * + * If an item with the same key exists, it is removed and destroyed before + * creating and adding the new one. + */ + +LWS_VISIBLE LWS_EXTERN lws_map_item_t * +lws_map_item_create(lws_map_t *map, + const lws_map_key_t key, size_t keylen, + const lws_map_value_t value, size_t valuelen); + +/** + * lws_map_item_destroy() - remove item from map and free + * + * \param item: the item in the map to remove and free + */ +LWS_VISIBLE LWS_EXTERN void +lws_map_item_destroy(lws_map_item_t *item); + +/** + * lws_map_item_lookup() - look for a item with the given key in the map + * + * \param map: the map + * \param key: the key to look for + * \param keylen: the length of the key to look for + * + * Searches for the key in the map, using the map's key hash and key compare + * functions. + */ + +LWS_VISIBLE LWS_EXTERN lws_map_item_t * +lws_map_item_lookup(lws_map_t *map, const lws_map_key_t key, size_t keylen); + +//@} diff --git a/lib/core/CMakeLists.txt b/lib/core/CMakeLists.txt index 75bec6c36..dc6479421 100644 --- a/lib/core/CMakeLists.txt +++ b/lib/core/CMakeLists.txt @@ -29,6 +29,7 @@ list(APPEND SOURCES core/buflist.c core/context.c core/lws_dll2.c + core/lws_map.c core/libwebsockets.c core/logs.c ) diff --git a/lib/core/lws_map.c b/lib/core/lws_map.c new file mode 100644 index 000000000..d149d8675 --- /dev/null +++ b/lib/core/lws_map.c @@ -0,0 +1,266 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2021 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "private-lib-core.h" + +typedef struct lws_map_hashtable { + struct lws_map *map_owner; /* so items can find map */ + lws_dll2_owner_t ho; +} lws_map_hashtable_t; + +typedef struct lws_map { + lws_map_info_t info; + + /* array of info.modulo x lws_map_hashtable_t overallocated */ +} lws_map_t; + +typedef struct lws_map_item { + lws_dll2_t list; /* owned by hashtable */ + + size_t keylen; + size_t valuelen; + + /* key then value is overallocated */ +} lws_map_item_t; + +/* + * lwsac-aware allocator + */ + +void * +lws_map_alloc_lwsac(struct lws_map *map, size_t x) +{ + return lwsac_use((struct lwsac **)map->info.opaque, x, + (size_t)map->info.aux); +} + +void +lws_map_free_lwsac(void *v) +{ +} + +/* + * Default allocation / free if none given in info + */ + +void * +lws_map_alloc_lws_malloc(struct lws_map *mo, size_t x) +{ + return lws_malloc(x, __func__); +} + +void +lws_map_free_lws_free(void *v) +{ + lws_free(v); +} + +/* + * This just needs to approximate a flat distribution, it's not related to + * security at all. + */ + +lws_map_hash_t +lws_map_hash_from_key_default(const lws_map_key_t key, size_t kl) +{ + lws_map_hash_t h = 0x12345678; + const uint8_t *u = (const uint8_t *)key; + + while (kl--) + h = ((((h << 7) | (h >> 25)) + 0xa1b2c3d4) ^ (*u++)) ^ h; + + return h; +} + +int +lws_map_compare_key_default(const lws_map_key_t key1, size_t kl1, + const lws_map_value_t key2, size_t kl2) +{ + if (kl1 != kl2) + return 1; + + return memcmp(key1, key2, kl1); +} + +lws_map_t * +lws_map_create(const lws_map_info_t *info) +{ + lws_map_t *map; + lws_map_alloc_t a = info->_alloc; + size_t modulo = info->modulo; + lws_map_hashtable_t *ht; + size_t size; + + if (!a) + a = lws_map_alloc_lws_malloc; + + if (!modulo) + modulo = 8; + + size = sizeof(*map) + (modulo * sizeof(lws_map_hashtable_t)); + map = lws_malloc(size, __func__); + if (!map) + return NULL; + + memset(map, 0, size); + + map->info = *info; + + map->info._alloc = a; + map->info.modulo = modulo; + if (!info->_free) + map->info._free = lws_map_free_lws_free; + if (!info->_hash) + map->info._hash = lws_map_hash_from_key_default; + if (!info->_compare) + map->info._compare = lws_map_compare_key_default; + + ht = (lws_map_hashtable_t *)&map[1]; + while (modulo--) + ht[modulo].map_owner = map; + + return map; +} + +static int +ho_free_item(struct lws_dll2 *d, void *user) +{ + lws_map_item_t *i = lws_container_of(d, lws_map_item_t, list); + + lws_map_item_destroy(i); + + return 0; +} + +void +lws_map_destroy(lws_map_t **pmap) +{ + lws_map_hashtable_t *ht; + lws_map_t *map = *pmap; + + if (!map) + return; + + /* empty out all the hashtables */ + + ht = (lws_map_hashtable_t *)&(map[1]); + while (map->info.modulo--) { + lws_dll2_foreach_safe(&ht->ho, ht, ho_free_item); + ht++; + } + + /* free the map itself */ + + lws_free_set_NULL(*pmap); +} + +lws_map_item_t * +lws_map_item_create(lws_map_t *map, + const lws_map_key_t key, size_t keylen, + const lws_map_value_t value, size_t valuelen) +{ + lws_map_hashtable_t *ht; + lws_map_item_t *item; + lws_map_hash_t h; + size_t hti; + uint8_t *u; + + item = lws_map_item_lookup(map, key, keylen); + if (item) + lws_map_item_destroy(item); + + item = map->info._alloc(map, sizeof(*item) + keylen + valuelen); + if (!item) + return NULL; + + lws_dll2_clear(&item->list); + item->keylen = keylen; + item->valuelen = valuelen; + + u = (uint8_t *)&item[1]; + memcpy(u, key, keylen); + u += keylen; + if (value) + memcpy(u, value, valuelen); + + h = map->info._hash(key, keylen); + + hti = h % map->info.modulo; + ht = (lws_map_hashtable_t *)&map[1]; + + lws_dll2_add_head(&item->list, &ht[hti].ho); + + return item; +} + +void +lws_map_item_destroy(lws_map_item_t *item) +{ + lws_map_hashtable_t *ht = lws_container_of(item->list.owner, + lws_map_hashtable_t, ho); + + lws_dll2_remove(&item->list); + ht->map_owner->info._free(item); +} + +lws_map_item_t * +lws_map_item_lookup(lws_map_t *map, const lws_map_key_t key, size_t keylen) +{ + lws_map_hash_t h = map->info._hash(key, keylen); + lws_map_hashtable_t *ht = (lws_map_hashtable_t *)&map[1]; + + lws_start_foreach_dll(struct lws_dll2 *, p, + ht[h % map->info.modulo].ho.head) { + lws_map_item_t *i = lws_container_of(p, lws_map_item_t, list); + + if (!map->info._compare(key, keylen, &i[1], i->keylen)) + return i; + } lws_end_foreach_dll(p); + + return NULL; +} + +const void * +lws_map_item_key(lws_map_item_t *_item) +{ + return ((void *)&_item[1]); +} + +const void * +lws_map_item_value(lws_map_item_t *_item) +{ + return (void *)(((uint8_t *)&_item[1]) + _item->keylen); +} + +size_t +lws_map_item_key_len(lws_map_item_t *_item) +{ + return _item->keylen; +} + +size_t +lws_map_item_value_len(lws_map_item_t *_item) +{ + return _item->valuelen; +} diff --git a/minimal-examples/api-tests/api-test-lws_map/CMakeLists.txt b/minimal-examples/api-tests/api-test-lws_map/CMakeLists.txt new file mode 100644 index 000000000..897042c7f --- /dev/null +++ b/minimal-examples/api-tests/api-test-lws_map/CMakeLists.txt @@ -0,0 +1,17 @@ +project(lws-api-test-lws_map C) +cmake_minimum_required(VERSION 2.8.12) +find_package(libwebsockets CONFIG REQUIRED) +list(APPEND CMAKE_MODULE_PATH ${LWS_CMAKE_DIR}) +include(CheckCSourceCompiles) + + + add_executable(${PROJECT_NAME} main.c) + add_test(NAME api-test-lws_map COMMAND lws-api-test-lws_map) + + if (websockets_shared) + target_link_libraries(${PROJECT_NAME} websockets_shared ${LIBWEBSOCKETS_DEP_LIBS}) + add_dependencies(${PROJECT_NAME} websockets_shared) + else() + target_link_libraries(${PROJECT_NAME} websockets ${LIBWEBSOCKETS_DEP_LIBS}) + endif() + diff --git a/minimal-examples/api-tests/api-test-lws_map/main.c b/minimal-examples/api-tests/api-test-lws_map/main.c new file mode 100644 index 000000000..022c98a84 --- /dev/null +++ b/minimal-examples/api-tests/api-test-lws_map/main.c @@ -0,0 +1,264 @@ +/* + * lws-api-test-lws_map + * + * Written in 2010-2021 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * unit tests for lws_map + */ + +#include + +/* custom key and comparator for test 3 */ + +typedef struct mykey { + int key; +} mykey_t; + +static int +compare_mykey_t(const lws_map_key_t key1, size_t kl1, + const lws_map_value_t key2, size_t kl2) +{ + const mykey_t *m1 = (mykey_t *)key1, *m2 = (mykey_t *)key2; + + return m1->key != m2->key; +} + +int main(int argc, const char **argv) +{ + int e = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE, + expected = 4, pass = 0; + mykey_t k1 = { .key = 123 }, k2 = { .key = 234 }, k3 = { .key = 999 }; + struct lwsac *ac = NULL; + lws_map_item_t *item; + lws_map_info_t info; + lws_map_t *map; + const char *p; + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS API selftest: lws_map\n"); + + /* Test 1: string keys */ + + lwsl_user("%s: test1\n", __func__); + memset(&info, 0, sizeof(info)); + map = lws_map_create(&info); + if (!map) { + e++; + goto end_t1; + } + if (!lws_map_item_create_ks(map, "abc", (lws_map_value_t)"def", 3)) { + e++; + goto end_t1; + } + if (!lws_map_item_create_ks(map, "123", (lws_map_value_t)"4567", 4)) { + e++; + goto end_t1; + } + item = lws_map_item_lookup_ks(map, "abc"); + if (!item) { + e++; + goto end_t1; + } + + if (lws_map_item_value_len(item) != 3 || + memcmp(lws_map_item_value(item), "def", 3)) { + e++; + goto end_t1; + } + + item = lws_map_item_lookup_ks(map, "123"); + if (!item) { + e++; + goto end_t1; + } + + if (lws_map_item_value_len(item) != 4 || + memcmp(lws_map_item_value(item), "4567", 4)) { + e++; + goto end_t1; + } + + item = lws_map_item_lookup_ks(map, "nope"); + if (item) { + e++; + goto end_t1; + } + + pass++; + +end_t1: + lws_map_destroy(&map); + + /* Test 2: Use lwsac item allocators */ + + lwsl_user("%s: test2\n", __func__); + memset(&info, 0, sizeof(info)); + info._alloc = lws_map_alloc_lwsac; + info._free = lws_map_free_lwsac; + info.opaque = (void *)∾ + + map = lws_map_create(&info); + if (!map) { + e++; + goto end_t2; + } + if (!lws_map_item_create_ks(map, "abc", "def", 3)) { + e++; + goto end_t2; + } + if (!lws_map_item_create_ks(map, "123", "4567", 4)) { + e++; + goto end_t2; + } + item = lws_map_item_lookup_ks(map, "abc"); + if (!item) { + e++; + goto end_t2; + } + + if (lws_map_item_value_len(item) != 3 || + memcmp(lws_map_item_value(item), "def", 3)) { + e++; + goto end_t2; + } + + item = lws_map_item_lookup_ks(map, "123"); + if (!item) { + e++; + goto end_t2; + } + + if (lws_map_item_value_len(item) != 4 || + memcmp(lws_map_item_value(item), "4567", 4)) { + e++; + goto end_t2; + } + + item = lws_map_item_lookup_ks(map, "nope"); + if (item) { + e++; + goto end_t2; + } + + pass++; + +end_t2: + lws_map_destroy(&map); + lwsac_free(&ac); + + /* Test 3: custom key object and comparator */ + + lwsl_user("%s: test3\n", __func__); + memset(&info, 0, sizeof(info)); + info._compare = compare_mykey_t; + + map = lws_map_create(&info); + if (!map) { + e++; + goto end_t3; + } + if (!lws_map_item_create(map, (lws_map_key_t)&k1, sizeof(k1), + (lws_map_value_t)"def", 3)) { + lwsl_err("%s: t3; a\n", __func__); + e++; + goto end_t3; + } + if (!lws_map_item_create(map, (lws_map_key_t)&k2, sizeof(k2), + (lws_map_value_t)"4567", 4)) { + lwsl_err("%s: t3; b\n", __func__); + e++; + goto end_t3; + } + item = lws_map_item_lookup(map, (lws_map_key_t)&k1, sizeof(k1)); + if (!item) { + lwsl_err("%s: t3; c\n", __func__); + e++; + goto end_t3; + } + + if (lws_map_item_value_len(item) != 3 || + memcmp(lws_map_item_value(item), "def", 3)) { + lwsl_err("%s: t3; d\n", __func__); + e++; + goto end_t3; + } + + item = lws_map_item_lookup(map, (lws_map_key_t)&k2, sizeof(k2)); + if (!item) { + lwsl_err("%s: t3; e\n", __func__); + e++; + goto end_t3; + } + + if (lws_map_item_value_len(item) != 4 || + memcmp(lws_map_item_value(item), "4567", 4)) { + lwsl_err("%s: t3; f\n", __func__); + e++; + goto end_t3; + } + + item = lws_map_item_lookup(map, (lws_map_key_t)&k3, sizeof(k3)); + if (item) { + lwsl_err("%s: t3; g\n", __func__); + e++; + goto end_t3; + } + + pass++; + +end_t3: + lws_map_destroy(&map); + + /* Test 4: same key items */ + + lwsl_user("%s: test4\n", __func__); + memset(&info, 0, sizeof(info)); + map = lws_map_create(&info); + if (!map) { + e++; + goto end_t4; + } + if (!lws_map_item_create_ks(map, "abc", (lws_map_value_t)"def", 3)) { + e++; + goto end_t4; + } + if (!lws_map_item_create_ks(map, "abc", (lws_map_value_t)"4567", 4)) { + e++; + goto end_t4; + } + item = lws_map_item_lookup_ks(map, "abc"); + if (!item) { + e++; + goto end_t4; + } + + if (lws_map_item_value_len(item) != 4 || + memcmp(lws_map_item_value(item), "4567", 4)) { + e++; + goto end_t4; + } + + pass++; + +end_t4: + lws_map_destroy(&map); + + if (e) + goto bail; + + lwsl_user("Completed: PASS %d / %d\n", pass, expected); + + return 0; + +bail: + lwsl_user("Completed: FAIL, passed %d / %d (e %d)\n", pass, + expected, e); + + return 1; +}