1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-09 00:00:04 +01:00
This commit is contained in:
Andy Green 2021-07-11 10:53:48 +01:00
parent 6ab149b5d3
commit b67d192100
7 changed files with 855 additions and 0 deletions

117
READMEs/README.lws_map.md Normal file
View file

@ -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 */
```

View file

@ -571,6 +571,8 @@ struct lws_vhost;
struct lws;
#include <libwebsockets/lws-dll2.h>
#include <libwebsockets/lws-map.h>
#include <libwebsockets/lws-fault-injection.h>
#include <libwebsockets/lws-timeout-timer.h>
#include <libwebsockets/lws-cache-ttl.h>

View file

@ -0,0 +1,188 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2021 Andy Green <andy@warmcat.com>
*
* 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);
//@}

View file

@ -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
)

266
lib/core/lws_map.c Normal file
View file

@ -0,0 +1,266 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2021 Andy Green <andy@warmcat.com>
*
* 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;
}

View file

@ -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()

View file

@ -0,0 +1,264 @@
/*
* lws-api-test-lws_map
*
* Written in 2010-2021 by Andy Green <andy@warmcat.com>
*
* This file is made available under the Creative Commons CC0 1.0
* Universal Public Domain Dedication.
*
* unit tests for lws_map
*/
#include <libwebsockets.h>
/* 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 *)&ac;
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;
}