Home | History | Annotate | Line # | Download | only in src
      1 /* $NetBSD: dict.c,v 1.10 2025/12/16 12:03:39 nia Exp $ */
      2 
      3 /* Copyright (c) 2010 The NetBSD Foundation, Inc.
      4  * All rights reserved.
      5  *
      6  * This code is derived from software contributed to The NetBSD Foundation
      7  * by Mateusz Kocielski.
      8  *
      9  * Redistribution and use in source and binary forms, with or without
     10  * modification, are permitted provided that the following conditions
     11  * are met:
     12  * 1. Redistributions of source code must retain the above copyright
     13  *    notice, this list of conditions and the following disclaimer.
     14  * 2. Redistributions in binary form must reproduce the above copyright
     15  *    notice, this list of conditions and the following disclaimer in the
     16  *    documentation and/or other materials provided with the distribution.
     17  * 3. Neither the name of The NetBSD Foundation nor the names of its
     18  *    contributors may be used to endorse or promote products derived
     19  *    from this software without specific prior written permission.
     20  *
     21  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     23  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     24  * PURPOSE ARE DISCLAIMED.	IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     25  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     26  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     27  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     28  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     29  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     31  * POSSIBILITY OF SUCH DAMAGE.
     32  */
     33 #include <sys/cdefs.h>
     34 __RCSID("$NetBSD: dict.c,v 1.10 2025/12/16 12:03:39 nia Exp $");
     35 
     36 #include <sys/queue.h>
     37 
     38 #include <ctype.h>
     39 #include <errno.h>
     40 #include <stdbool.h>
     41 #include <stdlib.h>
     42 #include <string.h>
     43 
     44 #include "dict.h"
     45 #include "msg.h"
     46 
     47 /** dictionary */
     48 LIST_HEAD(saslc__dict_t, saslc__dict_node_t);
     49 
     50 /** dictionary linked list */
     51 typedef struct saslc__dict_node_t {
     52 	LIST_ENTRY(saslc__dict_node_t) nodes;
     53 	char * key;		/* key */
     54 	char * value;		/* value */
     55 	size_t value_len;	/* value length */
     56 } saslc__dict_node_t;
     57 
     58 /*
     59  * XXX: If you add or change property keys, please readjust these
     60  * values so that saslc__dict_hashval() remains collisionless.
     61  * dist/test_hash/test_hash.c can help with this.
     62  */
     63 /* no collisions: hsize=18  hinit=0  shift=2 */
     64 #define HASH_SIZE       18
     65 #define HASH_INIT       0
     66 #define HASH_SHIFT      2
     67 
     68 /**
     69  * @brief compute the hash value for a given string.
     70  * @param cp string to hash.
     71  * @return the hash value.
     72  *
     73  * NB: The defines HASH_INIT, HASH_SHIFT, and HASH_SIZE should be
     74  * adjusted to make this collisionless for the keys used.
     75  */
     76 static size_t
     77 saslc__dict_hashval(const char *cp)
     78 {
     79 	size_t hval;
     80 
     81 	hval = HASH_INIT;
     82 	for (/*EMPTY*/; *cp != '\0'; cp++) {
     83 		hval <<= HASH_SHIFT;
     84 		hval += (size_t)*cp;
     85 	}
     86 	return hval % HASH_SIZE;
     87 }
     88 
     89 /**
     90  * @brief return the hash bucket corresponding to the key string
     91  * @param dict dictionary to use
     92  * @param cp key to use for lookup
     93  * @return the hash bucket for the key.
     94  */
     95 static saslc__dict_t *
     96 saslc__dict_hash(saslc__dict_t *dict, const char *cp)
     97 {
     98 
     99 	return dict + saslc__dict_hashval(cp);
    100 }
    101 
    102 /**
    103  * @brief checks if the key is legal.
    104  * @param key node key - must not be NULL
    105  * @return true if key is legal, false otherwise
    106  *
    107  * Note: A legal key begins with an isalpha(3) character and is
    108  * followed by isalnum(3) or '_' characters.
    109  */
    110 static bool
    111 saslc__dict_valid_key(const char *key)
    112 {
    113 
    114         /* key is not NULL */
    115 	if (!isalpha((unsigned char)*key))
    116 		return false;
    117 
    118 	key++;
    119 	while (isalnum((unsigned char)*key) || *key == '_')
    120 		key++;
    121 
    122 	return *key == '\0';
    123 }
    124 
    125 /**
    126  * @brief destroys and deallocates list node
    127  * @param node list node
    128  */
    129 static void
    130 saslc__dict_list_node_destroy(saslc__dict_node_t *node)
    131 {
    132 
    133 	free(node->key);
    134 	/* zero value, it may contain sensitive data */
    135 	explicit_memset(node->value, 0, node->value_len);
    136 	free(node->value);
    137 	LIST_REMOVE(node, nodes);
    138 	free(node);
    139 }
    140 
    141 /**
    142  * @brief gets node from the dictionary using key
    143  * @param dict dictionary
    144  * @param key node key
    145  * @return pointer to node if key is in the dictionary, NULL otherwise
    146  */
    147 static saslc__dict_node_t *
    148 saslc__dict_get_node_by_key(saslc__dict_t *dict, const char *key)
    149 {
    150 	saslc__dict_node_t *node;
    151 
    152 	dict = saslc__dict_hash(dict, key);
    153 	LIST_FOREACH(node, dict, nodes) {
    154 		if (strcmp(node->key, key) == 0)
    155 			return node;
    156 	}
    157 	return NULL;
    158 }
    159 
    160 /**
    161  * @brief destroys and deallocates dictionary
    162  * @param dict dictionary
    163  */
    164 void
    165 saslc__dict_destroy(saslc__dict_t *dict)
    166 {
    167 	size_t i;
    168 
    169 	for (i = 0; i < HASH_SIZE; i++) {
    170 		while (!LIST_EMPTY(dict + i))
    171 			saslc__dict_list_node_destroy(LIST_FIRST(dict + i));
    172 	}
    173 	free(dict);
    174 }
    175 
    176 /**
    177  * @brief removes node from the dictionary using key
    178  * @param dict dictionary
    179  * @param key node key
    180  * @return DICT_OK on success, DICT_KEYNOTFOUND if node was not found (key
    181  * does not exist in the dictionary.
    182  */
    183 saslc__dict_result_t
    184 saslc__dict_remove(saslc__dict_t *dict, const char *key)
    185 {
    186 	saslc__dict_node_t *node;
    187 
    188 	node = saslc__dict_get_node_by_key(dict, key);
    189 	if (node == NULL)
    190 		return DICT_KEYNOTFOUND;
    191 
    192 	saslc__dict_list_node_destroy(node);
    193 	saslc__msg_dbg("%s: removed key %s", __func__, key);
    194 	return DICT_OK;
    195 }
    196 
    197 /**
    198  * @brief gets node value from the dictionary using key
    199  * @param dict dictionary
    200  * @param key node key
    201  * @return pointer to the value if key was found in the dictionary, NULL
    202  * otherwise.
    203  */
    204 const char *
    205 saslc__dict_get(saslc__dict_t *dict, const char *key)
    206 {
    207 	saslc__dict_node_t *node;
    208 
    209 	node = saslc__dict_get_node_by_key(dict, key);
    210 	return node != NULL ? node->value : NULL;
    211 }
    212 
    213 /**
    214  * @brief gets length of node value from the dictionary using key
    215  * @param dict dictionary
    216  * @param key node key
    217  * @return length of the node value, 0 is returned in case when key does not
    218  * exist in the dictionary.
    219  *
    220  * XXX: currently unused.
    221  */
    222 size_t
    223 saslc__dict_get_len(saslc__dict_t *dict, const char *key)
    224 {
    225 	saslc__dict_node_t *node;
    226 
    227 	node = saslc__dict_get_node_by_key(dict, key);
    228 	return node != NULL ? node->value_len : 0;
    229 }
    230 
    231 /**
    232  * @brief creates and allocates dictionary
    233  * @return pointer to new dictionary, NULL is returned on allocation failure
    234  */
    235 saslc__dict_t *
    236 saslc__dict_create(void)
    237 {
    238 	saslc__dict_t *head;
    239 	int i;
    240 
    241 	head = calloc(HASH_SIZE, sizeof(*head));
    242 	if (head == NULL)
    243 		return NULL;
    244 
    245 	for (i = 0; i < HASH_SIZE; i++)
    246 		LIST_INIT(head + i);
    247 
    248 	return head;
    249 }
    250 
    251 /**
    252  * @brief inserts node into dictionary
    253  * @param dict dictionary
    254  * @param key node key
    255  * @param val node value
    256  * @return
    257  * DICT_OK - on success,
    258  * DICT_KEYINVALID - if node key is illegal,
    259  * DICT_VALBAD - if node value is illegal,
    260  * DICT_KEYEXISTS - if node with the same key already exists in the
    261  * dictionary,
    262  * DICT_NOMEM - on allocation failure
    263  */
    264 saslc__dict_result_t
    265 saslc__dict_insert(saslc__dict_t *dict, const char *key, const char *val)
    266 {
    267 	char *d_key, *d_val;
    268 	saslc__dict_node_t *node;
    269 
    270 	if (key == NULL || saslc__dict_valid_key(key) == false) {
    271 		saslc__msg_dbg("%s: invalid key: %s", __func__,
    272 		    key ? key : "<null>");
    273 		return DICT_KEYINVALID;
    274 	}
    275 	if (val == NULL) {
    276 		saslc__msg_dbg("%s: NULL value for key %s", __func__, key);
    277 		return DICT_VALBAD;
    278 	}
    279 	/* check if key exists in dictionary */
    280 	if (saslc__dict_get(dict, key) != NULL) {
    281 		saslc__msg_dbg("%s: key exists (ignoring): %s", __func__, key);
    282 		return DICT_KEYEXISTS;
    283 	}
    284 	if ((d_key = strdup(key)) == NULL)
    285 		goto nomem;
    286 
    287 	if ((d_val = strdup(val)) == NULL) {
    288 		free(d_key);
    289 		goto nomem;
    290 	}
    291 	if ((node = calloc(1, sizeof(*node))) == NULL) {
    292 		free(d_val);
    293 		free(d_key);
    294 		goto nomem;
    295 	}
    296 	dict = saslc__dict_hash(dict, key);
    297 	if (!LIST_EMPTY(dict))
    298 		saslc__msg_dbg("%s: hash collision: '%s' vs '%s'\n",
    299 		    __func__, key, LIST_FIRST(dict)->key);
    300 
    301 	saslc__msg_dbg("%s: %s=\"%s\"", __func__, d_key, d_val);
    302 	LIST_INSERT_HEAD(dict, node, nodes);
    303 	node->key = d_key;
    304 	node->value = d_val;
    305 	node->value_len = strlen(node->value);
    306 	return DICT_OK;
    307  nomem:
    308 	saslc__msg_dbg("%s: %s", __func__, strerror(errno));
    309 	return DICT_NOMEM;
    310 }
    311