1 /*
2 * nghttp2 - HTTP/2 C Library
3 *
4 * Copyright (c) 2017 ngtcp2 contributors
5 * Copyright (c) 2012 nghttp2 contributors
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining
8 * a copy of this software and associated documentation files (the
9 * "Software"), to deal in the Software without restriction, including
10 * without limitation the rights to use, copy, modify, merge, publish,
11 * distribute, sublicense, and/or sell copies of the Software, and to
12 * permit persons to whom the Software is furnished to do so, subject to
13 * the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be
16 * included in all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 */
26 #include "nghttp2_map.h"
27
28 #include <string.h>
29 #include <assert.h>
30 #include <stdio.h>
31
32 #include "nghttp2_helper.h"
33
34 #define NGHTTP2_INITIAL_TABLE_LENBITS 4
35
nghttp2_map_init(nghttp2_map * map,nghttp2_mem * mem)36 void nghttp2_map_init(nghttp2_map *map, nghttp2_mem *mem) {
37 map->mem = mem;
38 map->tablelen = 0;
39 map->tablelenbits = 0;
40 map->table = NULL;
41 map->size = 0;
42 }
43
nghttp2_map_free(nghttp2_map * map)44 void nghttp2_map_free(nghttp2_map *map) {
45 if (!map) {
46 return;
47 }
48
49 nghttp2_mem_free(map->mem, map->table);
50 }
51
nghttp2_map_each_free(nghttp2_map * map,int (* func)(void * data,void * ptr),void * ptr)52 void nghttp2_map_each_free(nghttp2_map *map, int (*func)(void *data, void *ptr),
53 void *ptr) {
54 uint32_t i;
55 nghttp2_map_bucket *bkt;
56
57 for (i = 0; i < map->tablelen; ++i) {
58 bkt = &map->table[i];
59
60 if (bkt->data == NULL) {
61 continue;
62 }
63
64 func(bkt->data, ptr);
65 }
66 }
67
nghttp2_map_each(nghttp2_map * map,int (* func)(void * data,void * ptr),void * ptr)68 int nghttp2_map_each(nghttp2_map *map, int (*func)(void *data, void *ptr),
69 void *ptr) {
70 int rv;
71 uint32_t i;
72 nghttp2_map_bucket *bkt;
73
74 if (map->size == 0) {
75 return 0;
76 }
77
78 for (i = 0; i < map->tablelen; ++i) {
79 bkt = &map->table[i];
80
81 if (bkt->data == NULL) {
82 continue;
83 }
84
85 rv = func(bkt->data, ptr);
86 if (rv != 0) {
87 return rv;
88 }
89 }
90
91 return 0;
92 }
93
hash(nghttp2_map_key_type key)94 static uint32_t hash(nghttp2_map_key_type key) {
95 return (uint32_t)key * 2654435769u;
96 }
97
h2idx(uint32_t hash,uint32_t bits)98 static size_t h2idx(uint32_t hash, uint32_t bits) {
99 return hash >> (32 - bits);
100 }
101
distance(uint32_t tablelen,uint32_t tablelenbits,nghttp2_map_bucket * bkt,size_t idx)102 static size_t distance(uint32_t tablelen, uint32_t tablelenbits,
103 nghttp2_map_bucket *bkt, size_t idx) {
104 return (idx - h2idx(bkt->hash, tablelenbits)) & (tablelen - 1);
105 }
106
map_bucket_swap(nghttp2_map_bucket * bkt,uint32_t * phash,nghttp2_map_key_type * pkey,void ** pdata)107 static void map_bucket_swap(nghttp2_map_bucket *bkt, uint32_t *phash,
108 nghttp2_map_key_type *pkey, void **pdata) {
109 uint32_t h = bkt->hash;
110 nghttp2_map_key_type key = bkt->key;
111 void *data = bkt->data;
112
113 bkt->hash = *phash;
114 bkt->key = *pkey;
115 bkt->data = *pdata;
116
117 *phash = h;
118 *pkey = key;
119 *pdata = data;
120 }
121
map_bucket_set_data(nghttp2_map_bucket * bkt,uint32_t hash,nghttp2_map_key_type key,void * data)122 static void map_bucket_set_data(nghttp2_map_bucket *bkt, uint32_t hash,
123 nghttp2_map_key_type key, void *data) {
124 bkt->hash = hash;
125 bkt->key = key;
126 bkt->data = data;
127 }
128
nghttp2_map_print_distance(nghttp2_map * map)129 void nghttp2_map_print_distance(nghttp2_map *map) {
130 uint32_t i;
131 size_t idx;
132 nghttp2_map_bucket *bkt;
133
134 for (i = 0; i < map->tablelen; ++i) {
135 bkt = &map->table[i];
136
137 if (bkt->data == NULL) {
138 fprintf(stderr, "@%u <EMPTY>\n", i);
139 continue;
140 }
141
142 idx = h2idx(bkt->hash, map->tablelenbits);
143 fprintf(stderr, "@%u hash=%08x key=%d base=%zu distance=%zu\n", i,
144 bkt->hash, bkt->key, idx,
145 distance(map->tablelen, map->tablelenbits, bkt, idx));
146 }
147 }
148
insert(nghttp2_map_bucket * table,uint32_t tablelen,uint32_t tablelenbits,uint32_t hash,nghttp2_map_key_type key,void * data)149 static int insert(nghttp2_map_bucket *table, uint32_t tablelen,
150 uint32_t tablelenbits, uint32_t hash,
151 nghttp2_map_key_type key, void *data) {
152 size_t idx = h2idx(hash, tablelenbits);
153 size_t d = 0, dd;
154 nghttp2_map_bucket *bkt;
155
156 for (;;) {
157 bkt = &table[idx];
158
159 if (bkt->data == NULL) {
160 map_bucket_set_data(bkt, hash, key, data);
161 return 0;
162 }
163
164 dd = distance(tablelen, tablelenbits, bkt, idx);
165 if (d > dd) {
166 map_bucket_swap(bkt, &hash, &key, &data);
167 d = dd;
168 } else if (bkt->key == key) {
169 /* TODO This check is just a waste after first swap or if this
170 function is called from map_resize. That said, there is no
171 difference with or without this conditional in performance
172 wise. */
173 return NGHTTP2_ERR_INVALID_ARGUMENT;
174 }
175
176 ++d;
177 idx = (idx + 1) & (tablelen - 1);
178 }
179 }
180
181 /* new_tablelen must be power of 2 and new_tablelen == (1 <<
182 new_tablelenbits) must hold. */
map_resize(nghttp2_map * map,uint32_t new_tablelen,uint32_t new_tablelenbits)183 static int map_resize(nghttp2_map *map, uint32_t new_tablelen,
184 uint32_t new_tablelenbits) {
185 uint32_t i;
186 nghttp2_map_bucket *new_table;
187 nghttp2_map_bucket *bkt;
188 int rv;
189 (void)rv;
190
191 new_table =
192 nghttp2_mem_calloc(map->mem, new_tablelen, sizeof(nghttp2_map_bucket));
193 if (new_table == NULL) {
194 return NGHTTP2_ERR_NOMEM;
195 }
196
197 for (i = 0; i < map->tablelen; ++i) {
198 bkt = &map->table[i];
199 if (bkt->data == NULL) {
200 continue;
201 }
202 rv = insert(new_table, new_tablelen, new_tablelenbits, bkt->hash, bkt->key,
203 bkt->data);
204
205 assert(0 == rv);
206 }
207
208 nghttp2_mem_free(map->mem, map->table);
209 map->tablelen = new_tablelen;
210 map->tablelenbits = new_tablelenbits;
211 map->table = new_table;
212
213 return 0;
214 }
215
nghttp2_map_insert(nghttp2_map * map,nghttp2_map_key_type key,void * data)216 int nghttp2_map_insert(nghttp2_map *map, nghttp2_map_key_type key, void *data) {
217 int rv;
218
219 assert(data);
220
221 /* Load factor is 0.75 */
222 if ((map->size + 1) * 4 > map->tablelen * 3) {
223 if (map->tablelen) {
224 rv = map_resize(map, map->tablelen * 2, map->tablelenbits + 1);
225 if (rv != 0) {
226 return rv;
227 }
228 } else {
229 rv = map_resize(map, 1 << NGHTTP2_INITIAL_TABLE_LENBITS,
230 NGHTTP2_INITIAL_TABLE_LENBITS);
231 if (rv != 0) {
232 return rv;
233 }
234 }
235 }
236
237 rv = insert(map->table, map->tablelen, map->tablelenbits, hash(key), key,
238 data);
239 if (rv != 0) {
240 return rv;
241 }
242 ++map->size;
243 return 0;
244 }
245
nghttp2_map_find(nghttp2_map * map,nghttp2_map_key_type key)246 void *nghttp2_map_find(nghttp2_map *map, nghttp2_map_key_type key) {
247 uint32_t h;
248 size_t idx;
249 nghttp2_map_bucket *bkt;
250 size_t d = 0;
251
252 if (map->size == 0) {
253 return NULL;
254 }
255
256 h = hash(key);
257 idx = h2idx(h, map->tablelenbits);
258
259 for (;;) {
260 bkt = &map->table[idx];
261
262 if (bkt->data == NULL ||
263 d > distance(map->tablelen, map->tablelenbits, bkt, idx)) {
264 return NULL;
265 }
266
267 if (bkt->key == key) {
268 return bkt->data;
269 }
270
271 ++d;
272 idx = (idx + 1) & (map->tablelen - 1);
273 }
274 }
275
nghttp2_map_remove(nghttp2_map * map,nghttp2_map_key_type key)276 int nghttp2_map_remove(nghttp2_map *map, nghttp2_map_key_type key) {
277 uint32_t h;
278 size_t idx, didx;
279 nghttp2_map_bucket *bkt;
280 size_t d = 0;
281
282 if (map->size == 0) {
283 return NGHTTP2_ERR_INVALID_ARGUMENT;
284 }
285
286 h = hash(key);
287 idx = h2idx(h, map->tablelenbits);
288
289 for (;;) {
290 bkt = &map->table[idx];
291
292 if (bkt->data == NULL ||
293 d > distance(map->tablelen, map->tablelenbits, bkt, idx)) {
294 return NGHTTP2_ERR_INVALID_ARGUMENT;
295 }
296
297 if (bkt->key == key) {
298 map_bucket_set_data(bkt, 0, 0, NULL);
299
300 didx = idx;
301 idx = (idx + 1) & (map->tablelen - 1);
302
303 for (;;) {
304 bkt = &map->table[idx];
305 if (bkt->data == NULL ||
306 distance(map->tablelen, map->tablelenbits, bkt, idx) == 0) {
307 break;
308 }
309
310 map->table[didx] = *bkt;
311 map_bucket_set_data(bkt, 0, 0, NULL);
312 didx = idx;
313
314 idx = (idx + 1) & (map->tablelen - 1);
315 }
316
317 --map->size;
318
319 return 0;
320 }
321
322 ++d;
323 idx = (idx + 1) & (map->tablelen - 1);
324 }
325 }
326
nghttp2_map_clear(nghttp2_map * map)327 void nghttp2_map_clear(nghttp2_map *map) {
328 if (map->tablelen == 0) {
329 return;
330 }
331
332 memset(map->table, 0, sizeof(*map->table) * map->tablelen);
333 map->size = 0;
334 }
335
nghttp2_map_size(nghttp2_map * map)336 size_t nghttp2_map_size(nghttp2_map *map) { return map->size; }
337