/src/curl/lib/u8_strset.c
Line | Count | Source |
1 | | /*************************************************************************** |
2 | | * _ _ ____ _ |
3 | | * Project ___| | | | _ \| | |
4 | | * / __| | | | |_) | | |
5 | | * | (__| |_| | _ <| |___ |
6 | | * \___|\___/|_| \_\_____| |
7 | | * |
8 | | * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. |
9 | | * |
10 | | * This software is licensed as described in the file COPYING, which |
11 | | * you should have received as part of this distribution. The terms |
12 | | * are also available at https://curl.se/docs/copyright.html. |
13 | | * |
14 | | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
15 | | * copies of the Software, and permit persons to whom the Software is |
16 | | * furnished to do so, under the terms of the COPYING file. |
17 | | * |
18 | | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
19 | | * KIND, either express or implied. |
20 | | * |
21 | | * SPDX-License-Identifier: curl |
22 | | * |
23 | | ***************************************************************************/ |
24 | | #include "curl_setup.h" |
25 | | |
26 | | #include "u8_strset.h" |
27 | | #include "curlx/strdup.h" |
28 | | |
29 | | /* random patterns for API verification */ |
30 | | #ifdef DEBUGBUILD |
31 | 90.4k | #define CURL_U8_STRSET_MAGIC 0x7117e783 |
32 | | #endif |
33 | | |
34 | | #define CURL_U8_STRSET_DEBUG 0 |
35 | | |
36 | 62.2k | #define CURL_SWAP(a, b) (((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b))) |
37 | | |
38 | 2.03M | #define CURL_U8_STRSET_SLOT_IDX(s, i) (uint8_t)((i) & (s)->slotmask) |
39 | 375k | #define CURL_U8_SLOT_CNT(i) (uint16_t)((i) ? (1U << (i)) : 0) |
40 | 91.0k | #define CURL_U8_SLOT_MASK(i) (uint8_t)((1U << (i)) - 1) |
41 | 369k | #define CURL_U8_STRSET_SLOT_CNT(s) CURL_U8_SLOT_CNT((s)->slotbits) |
42 | | |
43 | | /* A hashset for tuples (id, string) using Robin Hood Hashing. |
44 | | * <https://www.cs.cornell.edu/courses/JavaAndDS/files/hashing_RobinHood.pdf> |
45 | | * The basic idea here to handle collisions by robbing "rich" entries and |
46 | | * giving to the "poor": |
47 | | * - We have an array: (id, string) are ideally placed at index "id % size". |
48 | | * - If slot at index is already occupied, we have a collision. |
49 | | * - A simple collision strategy would look at the next index, and the |
50 | | * next until finding an empty slot. |
51 | | * - The drawback is that this may lead to many checks on lookups, as it |
52 | | * will need to also look at subsequent slots until it finds the match. |
53 | | * The amount of lookups is the "probe sequence length" (psl) and this |
54 | | * may vary greatly between entries. |
55 | | * - Robin Hood Hashing balances the 'psl's of all entries more evenly: |
56 | | * - psl == 0 means an entry is in exactly the right slot |
57 | | * - psl == 1 means it is in the slot right after. psl == 2 is the slot |
58 | | * after that, etc. |
59 | | * - when inserting a new entry, track its psl. Finding a slot where |
60 | | * the existing entry has a lower psl makes a swap. Put the new entry |
61 | | * and its psl there, take the previous entry and its psl and find |
62 | | * the next best slot for the previous entry. */ |
63 | | void Curl_u8_strset_init(struct u8_strset *set) |
64 | 90.4k | { |
65 | | #if defined(__GNUC__) && __GNUC__ >= 13 |
66 | | #pragma GCC diagnostic push |
67 | | #pragma GCC diagnostic ignored "-Warray-bounds" |
68 | | #endif |
69 | 90.4k | memset(set, 0, sizeof(*set)); |
70 | | #if defined(__GNUC__) && __GNUC__ >= 13 |
71 | | #pragma GCC diagnostic pop |
72 | | #endif |
73 | 90.4k | set->data = set->sdata; |
74 | 90.4k | set->ids = set->sids; |
75 | 90.4k | set->psl = set->spsl; |
76 | 90.4k | set->slotbits = CURL_U8_STRSET_START_BITS; |
77 | 90.4k | set->slotmask = CURL_U8_SLOT_MASK(set->slotbits); |
78 | 90.4k | set->count = 0; |
79 | 90.4k | #ifdef DEBUGBUILD |
80 | 90.4k | set->init = CURL_U8_STRSET_MAGIC; |
81 | 90.4k | #endif |
82 | 90.4k | } |
83 | | |
84 | | void Curl_u8_strset_clear(struct u8_strset *set) |
85 | 30.1k | { |
86 | 30.1k | uint16_t i; |
87 | 30.1k | DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); |
88 | 276k | for(i = 0; i < CURL_U8_STRSET_SLOT_CNT(set); ++i) |
89 | 246k | curlx_safefree(set->data[i]); |
90 | | |
91 | 30.1k | if(set->data != set->sdata) |
92 | 554 | curlx_safefree(set->data); |
93 | 30.1k | Curl_u8_strset_init(set); |
94 | 30.1k | } |
95 | | |
96 | | static void u8_strset_addn(struct u8_strset *set, uint8_t id, char *val) |
97 | 98.3k | { |
98 | 98.3k | uint8_t i = CURL_U8_STRSET_SLOT_IDX(set, id); |
99 | 98.3k | uint8_t psl = 0; |
100 | 226k | while(set->data[i]) { |
101 | 128k | if(psl > set->psl[i]) { /* SWAP */ |
102 | 31.1k | char *tmpdata; |
103 | 31.1k | tmpdata = set->data[i]; |
104 | 31.1k | set->data[i] = val; |
105 | 31.1k | val = tmpdata; |
106 | 31.1k | CURL_SWAP(set->psl[i], psl); |
107 | 31.1k | CURL_SWAP(set->ids[i], id); |
108 | 31.1k | } |
109 | 128k | i = CURL_U8_STRSET_SLOT_IDX(set, i + 1); |
110 | 128k | ++psl; |
111 | 128k | } |
112 | 98.3k | set->ids[i] = id; |
113 | 98.3k | set->data[i] = val; |
114 | 98.3k | set->psl[i] = psl; |
115 | 98.3k | ++set->count; |
116 | 98.3k | } |
117 | | |
118 | | static bool u8_strset_grow(struct u8_strset *set) |
119 | 589 | { |
120 | 589 | uint8_t *prev_ids, nslotbits; |
121 | 589 | uint16_t prev_slots; |
122 | 589 | char **prev_data; |
123 | 589 | size_t nslots; |
124 | 589 | void *d; |
125 | | |
126 | 589 | if(set->slotbits >= 8) |
127 | 0 | return FALSE; |
128 | 589 | nslotbits = (uint8_t)(set->slotbits + 1); |
129 | | #if CURL_U8_STRSET_DEBUG |
130 | | curl_mfprintf(stderr, "u8_strset_grow from %d to %d\n", |
131 | | set->slotbits, nslotbits); |
132 | | #endif |
133 | 589 | nslots = CURL_U8_SLOT_CNT(nslotbits); |
134 | 589 | d = curlx_calloc(1, (nslots * sizeof(char *)) + (2 * nslots)); |
135 | 589 | if(!d) |
136 | 0 | return FALSE; |
137 | | |
138 | 589 | prev_data = set->data; |
139 | 589 | prev_ids = set->ids; |
140 | 589 | prev_slots = set->slotbits; |
141 | | #if defined(__GNUC__) && __GNUC__ >= 13 |
142 | | #pragma GCC diagnostic push |
143 | | #pragma GCC diagnostic ignored "-Wanalyzer-allocation-size" |
144 | | #endif |
145 | 589 | set->data = (char **)d; |
146 | | #if defined(__GNUC__) && __GNUC__ >= 13 |
147 | | #pragma GCC diagnostic pop |
148 | | #endif |
149 | 589 | set->ids = (uint8_t *)d + (nslots * sizeof(char *)); |
150 | 589 | set->psl = set->ids + nslots; |
151 | 589 | set->slotbits = nslotbits; |
152 | 589 | set->slotmask = CURL_U8_SLOT_MASK(set->slotbits); |
153 | 589 | set->count = 0; |
154 | | /* re-add previous entries */ |
155 | 589 | { |
156 | 589 | uint16_t i; |
157 | 5.62k | for(i = 0; i < CURL_U8_SLOT_CNT(prev_slots); ++i) { |
158 | 5.04k | if(prev_data[i]) |
159 | 5.04k | u8_strset_addn(set, prev_ids[i], prev_data[i]); |
160 | 5.04k | } |
161 | 589 | } |
162 | 589 | if(prev_data != set->sdata) |
163 | 35 | curlx_free(prev_data); |
164 | 589 | return TRUE; |
165 | 589 | } |
166 | | |
167 | | static bool u8_strset_get_index(struct u8_strset *set, |
168 | | uint8_t id, uint8_t *pindex) |
169 | 745k | { |
170 | 745k | uint8_t i = CURL_U8_STRSET_SLOT_IDX(set, id); |
171 | 745k | uint8_t psl = 0; |
172 | 1.80M | while(set->data[i] && (psl <= set->psl[i])) { |
173 | 1.15M | if(set->ids[i] == id) { |
174 | 98.8k | *pindex = i; |
175 | | #if CURL_U8_STRSET_DEBUG |
176 | | curl_mfprintf(stderr, "u8_strset_index %d=%s\n", id, set->data[i]); |
177 | | #endif |
178 | 98.8k | return TRUE; |
179 | 98.8k | } |
180 | 1.06M | i = CURL_U8_STRSET_SLOT_IDX(set, i + 1); |
181 | 1.06M | ++psl; |
182 | 1.06M | } |
183 | | #if CURL_U8_STRSET_DEBUG |
184 | | curl_mfprintf(stderr, "u8_strset_index %d not found\n", id); |
185 | | #endif |
186 | 646k | *pindex = 0; |
187 | 646k | return FALSE; |
188 | 745k | } |
189 | | |
190 | | #ifdef UNITTESTS |
191 | | /* @unittest 3211 */ |
192 | | uint16_t Curl_u8_strset_count(struct u8_strset *set) |
193 | 0 | { |
194 | 0 | return set->count; |
195 | 0 | } |
196 | | #endif |
197 | | |
198 | | const char *Curl_u8_strset_get(struct u8_strset *set, uint8_t id) |
199 | 500k | { |
200 | 500k | uint8_t i; |
201 | 500k | DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); |
202 | 500k | if(u8_strset_get_index(set, id, &i)) |
203 | 97.2k | return set->data[i]; |
204 | 402k | return NULL; |
205 | 500k | } |
206 | | |
207 | | CURLcode Curl_u8_strset_setn(struct u8_strset *set, |
208 | | uint8_t id, char *str) |
209 | 94.4k | { |
210 | 94.4k | uint8_t i; |
211 | | |
212 | 94.4k | DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); |
213 | | #if CURL_U8_STRSET_DEBUG |
214 | | curl_mfprintf(stderr, "u8_strset_setn %d=%s\n", id, str); |
215 | | #endif |
216 | 94.4k | if(!str) { |
217 | 185 | Curl_u8_strset_unset(set, id); |
218 | 185 | return CURLE_OK; |
219 | 185 | } |
220 | | |
221 | 94.2k | if(u8_strset_get_index(set, id, &i)) { |
222 | | /* `id` is in set, replace value */ |
223 | 1.01k | curlx_free(set->data[i]); |
224 | 1.01k | set->data[i] = str; |
225 | 1.01k | return CURLE_OK; |
226 | 1.01k | } |
227 | | /* `id` not in set yet, grow if full */ |
228 | 93.2k | if((set->count >= CURL_U8_STRSET_SLOT_CNT(set)) && !u8_strset_grow(set)) { |
229 | 0 | curlx_free(str); |
230 | 0 | return CURLE_OUT_OF_MEMORY; |
231 | 0 | } |
232 | | |
233 | 93.2k | u8_strset_addn(set, id, str); |
234 | 93.2k | return CURLE_OK; |
235 | 93.2k | } |
236 | | |
237 | | CURLcode Curl_u8_strset_setx(struct u8_strset *set, |
238 | | uint8_t id, const char *str, size_t slen) |
239 | 93.7k | { |
240 | 93.7k | char *val; |
241 | | |
242 | 93.7k | DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); |
243 | 93.7k | if(!str) { |
244 | 0 | Curl_u8_strset_unset(set, id); |
245 | 0 | return CURLE_OK; |
246 | 0 | } |
247 | | |
248 | 93.7k | val = curlx_memdup0(str, slen); |
249 | 93.7k | if(!val) |
250 | 0 | return CURLE_OUT_OF_MEMORY; |
251 | 93.7k | return Curl_u8_strset_setn(set, id, val); |
252 | 93.7k | } |
253 | | |
254 | | /* @unittest 3211 */ |
255 | | UNITTEST CURLcode u8_strset_set(struct u8_strset *set, |
256 | | uint8_t id, const char *str); |
257 | | UNITTEST CURLcode u8_strset_set(struct u8_strset *set, |
258 | | uint8_t id, const char *str) |
259 | 0 | { |
260 | 0 | return Curl_u8_strset_setx(set, id, str, str ? strlen(str) : 0); |
261 | 0 | } |
262 | | |
263 | | static void u8_strset_unset(struct u8_strset *set, uint8_t id, bool zero) |
264 | 151k | { |
265 | 151k | uint8_t i, j; |
266 | | |
267 | 151k | DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); |
268 | 151k | if(u8_strset_get_index(set, id, &i)) { |
269 | | /* `id` is in set */ |
270 | 557 | if(zero) |
271 | 520 | curlx_strzero(set->data[i]); |
272 | 557 | curlx_safefree(set->data[i]); |
273 | 557 | set->ids[i] = set->psl[i] = 0; |
274 | 557 | --set->count; |
275 | 557 | j = CURL_U8_STRSET_SLOT_IDX(set, i + 1); |
276 | | /* shift all entries with positive psl "down" */ |
277 | 1.68k | while(set->data[j] && set->psl[j]) { |
278 | 1.12k | set->data[i] = set->data[j]; |
279 | 1.12k | set->ids[i] = set->ids[j]; |
280 | 1.12k | set->psl[i] = (uint8_t)(set->psl[j] - 1); |
281 | 1.12k | set->data[j] = NULL; |
282 | 1.12k | set->ids[j] = set->psl[j] = 0; |
283 | 1.12k | i = j; |
284 | 1.12k | j = CURL_U8_STRSET_SLOT_IDX(set, i + 1); |
285 | 1.12k | } |
286 | 557 | } |
287 | 151k | } |
288 | | |
289 | | void Curl_u8_strset_unset(struct u8_strset *set, uint8_t id) |
290 | 555 | { |
291 | 555 | u8_strset_unset(set, id, FALSE); |
292 | 555 | } |
293 | | |
294 | | void Curl_u8_strset_unset0(struct u8_strset *set, uint8_t id) |
295 | 150k | { |
296 | 150k | u8_strset_unset(set, id, TRUE); |
297 | 150k | } |
298 | | |
299 | | CURLcode Curl_u8_strset_copy(struct u8_strset *dest, struct u8_strset *src) |
300 | 0 | { |
301 | 0 | CURLcode result = CURLE_OK; |
302 | 0 | uint16_t i; |
303 | |
|
304 | 0 | DEBUGASSERT(src->init == CURL_U8_STRSET_MAGIC); |
305 | 0 | Curl_u8_strset_clear(dest); |
306 | 0 | for(i = 0; !result && (i < CURL_U8_STRSET_SLOT_CNT(src)); ++i) { |
307 | 0 | if(src->data[i]) |
308 | 0 | result = u8_strset_set(dest, src->ids[i], src->data[i]); |
309 | 0 | } |
310 | 0 | return result; |
311 | 0 | } |