blob: 76f71d37d1995b73740efb14533a68382d2a79b6 [file] [log] [blame]
Ed Warnickecb9cada2015-12-08 15:45:58 -07001/*
2 * Copyright (c) 2015 Cisco and/or its affiliates.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at:
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15/*
16 Copyright (c) 2001-2005 Eliot Dresselhaus
17
18 Permission is hereby granted, free of charge, to any person obtaining
19 a copy of this software and associated documentation files (the
20 "Software"), to deal in the Software without restriction, including
21 without limitation the rights to use, copy, modify, merge, publish,
22 distribute, sublicense, and/or sell copies of the Software, and to
23 permit persons to whom the Software is furnished to do so, subject to
24 the following conditions:
25
26 The above copyright notice and this permission notice shall be
27 included in all copies or substantial portions of the Software.
28
29 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
31 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
32 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
33 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
34 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
35 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36*/
37
38#include <vppinfra/hash.h>
39#include <vppinfra/error.h>
40#include <vppinfra/mem.h>
41#include <vppinfra/byte_order.h> /* for clib_arch_is_big_endian */
42
Dave Barachc3799992016-08-15 11:12:27 -040043always_inline void
44zero_pair (hash_t * h, hash_pair_t * p)
45{
Dave Barachb7b92992018-10-17 10:38:51 -040046 clib_memset (p, 0, hash_pair_bytes (h));
Dave Barachc3799992016-08-15 11:12:27 -040047}
Ed Warnickecb9cada2015-12-08 15:45:58 -070048
Dave Barachc3799992016-08-15 11:12:27 -040049always_inline void
50init_pair (hash_t * h, hash_pair_t * p)
51{
Dave Barachb7b92992018-10-17 10:38:51 -040052 clib_memset (p->value, ~0, hash_value_bytes (h));
Dave Barachc3799992016-08-15 11:12:27 -040053}
Ed Warnickecb9cada2015-12-08 15:45:58 -070054
55always_inline hash_pair_union_t *
Dave Barachc3799992016-08-15 11:12:27 -040056get_pair (void *v, uword i)
Ed Warnickecb9cada2015-12-08 15:45:58 -070057{
Dave Barachc3799992016-08-15 11:12:27 -040058 hash_t *h = hash_header (v);
59 hash_pair_t *p;
Ed Warnickecb9cada2015-12-08 15:45:58 -070060 ASSERT (i < vec_len (v));
61 p = v;
62 p += i << h->log2_pair_size;
63 return (hash_pair_union_t *) p;
64}
65
Dave Barachc3799992016-08-15 11:12:27 -040066always_inline void
67set_is_user (void *v, uword i, uword is_user)
Ed Warnickecb9cada2015-12-08 15:45:58 -070068{
Dave Barachc3799992016-08-15 11:12:27 -040069 hash_t *h = hash_header (v);
70 uword i0 = i / BITS (h->is_user[0]);
71 uword i1 = (uword) 1 << (i % BITS (h->is_user[0]));
Ed Warnickecb9cada2015-12-08 15:45:58 -070072 if (is_user)
73 h->is_user[i0] |= i1;
74 else
75 h->is_user[i0] &= ~i1;
76}
77
Dave Barachc3799992016-08-15 11:12:27 -040078static u8 *hash_format_pair_default (u8 * s, va_list * args);
Ed Warnickecb9cada2015-12-08 15:45:58 -070079
Damjan Mariondae1c7e2020-10-17 13:32:25 +020080__clib_export uword
Dave Barachc3799992016-08-15 11:12:27 -040081hash_memory (void *p, word n_bytes, uword state)
Ed Warnickecb9cada2015-12-08 15:45:58 -070082{
Damjan Marion87997682022-03-26 00:57:50 +010083 uword last[3] = {};
84 uwordu *q = p;
85 u64 a, b, c, n;
Ed Warnickecb9cada2015-12-08 15:45:58 -070086
Damjan Marion87997682022-03-26 00:57:50 +010087 a = b = (uword_bits == 64) ? 0x9e3779b97f4a7c13LL : 0x9e3779b9;
88 c = state;
89 n = n_bytes;
90
91 while (n >= 3 * sizeof (uword))
92 {
93 a += q[0];
94 b += q[1];
95 c += q[2];
96 hash_mix (a, b, c);
97 n -= 3 * sizeof (uword);
98 q += 3;
99 }
100
101 c += n_bytes;
102
103 if (n > 0)
104 {
105 clib_memcpy_fast (&last, q, n);
106 a += last[0];
107 b += last[1];
108 c += last[2];
109 }
110
111 hash_mix (a, b, c);
112
113 return c;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700114}
115
Dave Barachc3799992016-08-15 11:12:27 -0400116always_inline uword
117hash_uword (uword x)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700118{
Damjan Marion87997682022-03-26 00:57:50 +0100119 uword a, b, c;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700120
Damjan Marion87997682022-03-26 00:57:50 +0100121 a = b = (uword_bits == 64) ? 0x9e3779b97f4a7c13LL : 0x9e3779b9;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700122 c = 0;
123 a += x;
Damjan Marion87997682022-03-26 00:57:50 +0100124 hash_mix (a, b, c);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700125 return c;
126}
Ed Warnickecb9cada2015-12-08 15:45:58 -0700127
128/* Call sum function. Hash code will be sum function value
129 modulo the prime length of the hash table. */
Dave Barachc3799992016-08-15 11:12:27 -0400130always_inline uword
131key_sum (hash_t * h, uword key)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700132{
133 uword sum;
134 switch (pointer_to_uword ((void *) h->key_sum))
135 {
136 case KEY_FUNC_NONE:
137 sum = hash_uword (key);
138 break;
139
140 case KEY_FUNC_POINTER_UWORD:
Dave Barachc3799992016-08-15 11:12:27 -0400141 sum = hash_uword (*uword_to_pointer (key, uword *));
Ed Warnickecb9cada2015-12-08 15:45:58 -0700142 break;
143
144 case KEY_FUNC_POINTER_U32:
Dave Barachc3799992016-08-15 11:12:27 -0400145 sum = hash_uword (*uword_to_pointer (key, u32 *));
Ed Warnickecb9cada2015-12-08 15:45:58 -0700146 break;
147
148 case KEY_FUNC_STRING:
Dave Barachc3799992016-08-15 11:12:27 -0400149 sum = string_key_sum (h, key);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700150 break;
151
Ole Troan73710c72018-06-04 22:27:49 +0200152 case KEY_FUNC_MEM:
153 sum = mem_key_sum (h, key);
154 break;
155
Ed Warnickecb9cada2015-12-08 15:45:58 -0700156 default:
157 sum = h->key_sum (h, key);
158 break;
159 }
160
161 return sum;
162}
163
Dave Barachc3799992016-08-15 11:12:27 -0400164always_inline uword
165key_equal1 (hash_t * h, uword key1, uword key2, uword e)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700166{
167 switch (pointer_to_uword ((void *) h->key_equal))
168 {
169 case KEY_FUNC_NONE:
170 break;
171
172 case KEY_FUNC_POINTER_UWORD:
Dave Barachc3799992016-08-15 11:12:27 -0400173 e =
174 *uword_to_pointer (key1, uword *) == *uword_to_pointer (key2,
175 uword *);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700176 break;
177
178 case KEY_FUNC_POINTER_U32:
Dave Barachc3799992016-08-15 11:12:27 -0400179 e = *uword_to_pointer (key1, u32 *) == *uword_to_pointer (key2, u32 *);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700180 break;
181
182 case KEY_FUNC_STRING:
Dave Barachc3799992016-08-15 11:12:27 -0400183 e = string_key_equal (h, key1, key2);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700184 break;
185
Ole Troan73710c72018-06-04 22:27:49 +0200186 case KEY_FUNC_MEM:
187 e = mem_key_equal (h, key1, key2);
188 break;
189
Ed Warnickecb9cada2015-12-08 15:45:58 -0700190 default:
191 e = h->key_equal (h, key1, key2);
192 break;
193 }
194 return e;
195}
196
197/* Compares two keys: returns 1 if equal, 0 if not. */
Dave Barachc3799992016-08-15 11:12:27 -0400198always_inline uword
199key_equal (hash_t * h, uword key1, uword key2)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700200{
201 uword e = key1 == key2;
202 if (CLIB_DEBUG > 0 && key1 == key2)
203 ASSERT (key_equal1 (h, key1, key2, e));
Dave Barachc3799992016-08-15 11:12:27 -0400204 if (!e)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700205 e = key_equal1 (h, key1, key2, e);
206 return e;
207}
208
209static hash_pair_union_t *
Dave Barachc3799992016-08-15 11:12:27 -0400210get_indirect (void *v, hash_pair_indirect_t * pi, uword key)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700211{
Dave Barachc3799992016-08-15 11:12:27 -0400212 hash_t *h = hash_header (v);
213 hash_pair_t *p0, *p1;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700214
215 p0 = p1 = pi->pairs;
216 if (h->log2_pair_size > 0)
217 p1 = hash_forward (h, p0, indirect_pair_get_len (pi));
218 else
219 p1 += vec_len (p0);
220
221 while (p0 < p1)
222 {
223 if (key_equal (h, p0->key, key))
224 return (hash_pair_union_t *) p0;
225 p0 = hash_forward1 (h, p0);
226 }
227
228 return (hash_pair_union_t *) 0;
229}
230
231static hash_pair_union_t *
Dave Barachc3799992016-08-15 11:12:27 -0400232set_indirect_is_user (void *v, uword i, hash_pair_union_t * p, uword key)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700233{
Dave Barachc3799992016-08-15 11:12:27 -0400234 hash_t *h = hash_header (v);
235 hash_pair_t *q;
236 hash_pair_indirect_t *pi = &p->indirect;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700237 uword log2_bytes = 0;
238
239 if (h->log2_pair_size == 0)
240 q = vec_new (hash_pair_t, 2);
241 else
242 {
243 log2_bytes = 1 + hash_pair_log2_bytes (h);
Dave Barach6f6f34f2016-08-08 13:05:31 -0400244 q = clib_mem_alloc (1ULL << log2_bytes);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700245 }
Dave Barach178cf492018-11-13 16:34:13 -0500246 clib_memcpy_fast (q, &p->direct, hash_pair_bytes (h));
Ed Warnickecb9cada2015-12-08 15:45:58 -0700247
248 pi->pairs = q;
249 if (h->log2_pair_size > 0)
250 indirect_pair_set (pi, log2_bytes, 2);
251
252 set_is_user (v, i, 0);
253
254 /* First element is used by existing pair, second will be used by caller. */
255 q = hash_forward1 (h, q);
256 q->key = key;
257 init_pair (h, q);
258 return (hash_pair_union_t *) q;
259}
260
261static hash_pair_union_t *
Dave Barachc3799992016-08-15 11:12:27 -0400262set_indirect (void *v, hash_pair_indirect_t * pi, uword key,
Ed Warnickecb9cada2015-12-08 15:45:58 -0700263 uword * found_key)
264{
Dave Barachc3799992016-08-15 11:12:27 -0400265 hash_t *h = hash_header (v);
266 hash_pair_t *new_pair;
267 hash_pair_union_t *q;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700268
269 q = get_indirect (v, pi, key);
270 if (q)
271 {
272 *found_key = 1;
273 return q;
274 }
275
276 if (h->log2_pair_size == 0)
277 vec_add2 (pi->pairs, new_pair, 1);
278 else
279 {
280 uword len, new_len, log2_bytes;
281
282 len = indirect_pair_get_len (pi);
283 log2_bytes = indirect_pair_get_log2_bytes (pi);
284
285 new_len = len + 1;
Dave Barach6f6f34f2016-08-08 13:05:31 -0400286 if (new_len * hash_pair_bytes (h) > (1ULL << log2_bytes))
Ed Warnickecb9cada2015-12-08 15:45:58 -0700287 {
Damjan Marion299571a2022-03-19 00:07:52 +0100288 pi->pairs = clib_mem_realloc (pi->pairs, 1ULL << (log2_bytes + 1));
Ed Warnickecb9cada2015-12-08 15:45:58 -0700289 log2_bytes++;
290 }
291
292 indirect_pair_set (pi, log2_bytes, new_len);
293 new_pair = pi->pairs + (len << h->log2_pair_size);
294 }
295 new_pair->key = key;
296 init_pair (h, new_pair);
297 *found_key = 0;
298 return (hash_pair_union_t *) new_pair;
299}
300
Dave Barachc3799992016-08-15 11:12:27 -0400301static void
302unset_indirect (void *v, uword i, hash_pair_t * q)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700303{
Dave Barachc3799992016-08-15 11:12:27 -0400304 hash_t *h = hash_header (v);
305 hash_pair_union_t *p = get_pair (v, i);
306 hash_pair_t *e;
307 hash_pair_indirect_t *pi = &p->indirect;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700308 uword len, is_vec;
309
310 is_vec = h->log2_pair_size == 0;
311
Dave Barachc3799992016-08-15 11:12:27 -0400312 ASSERT (!hash_is_user (v, i));
Ed Warnickecb9cada2015-12-08 15:45:58 -0700313 len = is_vec ? vec_len (pi->pairs) : indirect_pair_get_len (pi);
314 e = hash_forward (h, pi->pairs, len - 1);
315 ASSERT (q >= pi->pairs && q <= e);
316
317 /* We have two or fewer pairs and we are delete one pair.
318 Make indirect pointer direct and free indirect memory. */
319 if (len <= 2)
320 {
Dave Barachc3799992016-08-15 11:12:27 -0400321 hash_pair_t *r = pi->pairs;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700322
323 if (len == 2)
324 {
Dave Barach178cf492018-11-13 16:34:13 -0500325 clib_memcpy_fast (p, q == r ? hash_forward1 (h, r) : r,
326 hash_pair_bytes (h));
Ed Warnickecb9cada2015-12-08 15:45:58 -0700327 set_is_user (v, i, 1);
328 }
329 else
330 zero_pair (h, &p->direct);
331
332 if (is_vec)
333 vec_free (r);
334 else if (r)
335 clib_mem_free (r);
336 }
337 else
338 {
339 /* If deleting a pair we need to keep non-null pairs together. */
340 if (q < e)
Dave Barach178cf492018-11-13 16:34:13 -0500341 clib_memcpy_fast (q, e, hash_pair_bytes (h));
Ed Warnickecb9cada2015-12-08 15:45:58 -0700342 else
343 zero_pair (h, q);
344 if (is_vec)
Damjan Marion8bea5892022-04-04 22:40:45 +0200345 vec_dec_len (pi->pairs, 1);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700346 else
Dave Barachc3799992016-08-15 11:12:27 -0400347 indirect_pair_set (pi, indirect_pair_get_log2_bytes (pi), len - 1);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700348 }
349}
350
Dave Barachc3799992016-08-15 11:12:27 -0400351enum lookup_opcode
352{
Ed Warnickecb9cada2015-12-08 15:45:58 -0700353 GET = 1,
354 SET = 2,
355 UNSET = 3,
356};
357
Dave Barachc3799992016-08-15 11:12:27 -0400358static hash_pair_t *
359lookup (void *v, uword key, enum lookup_opcode op,
360 void *new_value, void *old_value)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700361{
Dave Barachc3799992016-08-15 11:12:27 -0400362 hash_t *h = hash_header (v);
363 hash_pair_union_t *p = 0;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700364 uword found_key = 0;
Benoît Ganne1a3e08a2021-02-11 19:46:43 +0100365 uword value_bytes;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700366 uword i;
367
Dave Barachc3799992016-08-15 11:12:27 -0400368 if (!v)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700369 return 0;
370
371 i = key_sum (h, key) & (_vec_len (v) - 1);
372 p = get_pair (v, i);
Benoît Ganne1a3e08a2021-02-11 19:46:43 +0100373 value_bytes = hash_value_bytes (h);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700374
375 if (hash_is_user (v, i))
376 {
377 found_key = key_equal (h, p->direct.key, key);
378 if (found_key)
379 {
380 if (op == UNSET)
381 {
382 set_is_user (v, i, 0);
Benoît Ganne1a3e08a2021-02-11 19:46:43 +0100383 if (old_value && value_bytes)
384 clib_memcpy_fast (old_value, p->direct.value, value_bytes);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700385 zero_pair (h, &p->direct);
386 }
387 }
388 else
389 {
390 if (op == SET)
391 p = set_indirect_is_user (v, i, p, key);
392 else
393 p = 0;
394 }
395 }
396 else
397 {
Dave Barachc3799992016-08-15 11:12:27 -0400398 hash_pair_indirect_t *pi = &p->indirect;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700399
400 if (op == SET)
401 {
Dave Barachc3799992016-08-15 11:12:27 -0400402 if (!pi->pairs)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700403 {
404 p->direct.key = key;
405 set_is_user (v, i, 1);
406 }
407 else
408 p = set_indirect (v, pi, key, &found_key);
409 }
410 else
411 {
412 p = get_indirect (v, pi, key);
413 found_key = p != 0;
414 if (found_key && op == UNSET)
415 {
Benoît Ganne1a3e08a2021-02-11 19:46:43 +0100416 if (old_value && value_bytes)
417 clib_memcpy_fast (old_value, &p->direct.value, value_bytes);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700418
419 unset_indirect (v, i, &p->direct);
420
421 /* Nullify p (since it's just been deleted).
Dave Barachc3799992016-08-15 11:12:27 -0400422 Otherwise we might be tempted to play with it. */
Ed Warnickecb9cada2015-12-08 15:45:58 -0700423 p = 0;
424 }
425 }
426 }
427
Benoît Ganne1a3e08a2021-02-11 19:46:43 +0100428 if (op == SET && p != 0 && value_bytes)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700429 {
430 /* Save away old value for caller. */
431 if (old_value && found_key)
Benoît Ganne1a3e08a2021-02-11 19:46:43 +0100432 clib_memcpy_fast (old_value, &p->direct.value, value_bytes);
433 clib_memcpy_fast (&p->direct.value, new_value, value_bytes);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700434 }
435
436 if (op == SET)
437 h->elts += !found_key;
438 if (op == UNSET)
439 h->elts -= found_key;
440
441 return &p->direct;
442}
443
444/* Fetch value of key. */
Damjan Mariondae1c7e2020-10-17 13:32:25 +0200445__clib_export uword *
Dave Barachc3799992016-08-15 11:12:27 -0400446_hash_get (void *v, uword key)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700447{
Dave Barachc3799992016-08-15 11:12:27 -0400448 hash_t *h = hash_header (v);
449 hash_pair_t *p;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700450
451 /* Don't even search table if its empty. */
Dave Barachc3799992016-08-15 11:12:27 -0400452 if (!v || h->elts == 0)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700453 return 0;
454
455 p = lookup (v, key, GET, 0, 0);
Dave Barachc3799992016-08-15 11:12:27 -0400456 if (!p)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700457 return 0;
458 if (h->log2_pair_size == 0)
459 return &p->key;
460 else
461 return &p->value[0];
462}
463
Damjan Mariondae1c7e2020-10-17 13:32:25 +0200464__clib_export hash_pair_t *
Dave Barachc3799992016-08-15 11:12:27 -0400465_hash_get_pair (void *v, uword key)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700466{
Dave Barachc3799992016-08-15 11:12:27 -0400467 return lookup (v, key, GET, 0, 0);
468}
469
Damjan Marion4a251d02021-05-06 17:28:12 +0200470__clib_export hash_pair_t *
471hash_next (void *v, hash_next_t *hn)
Dave Barachc3799992016-08-15 11:12:27 -0400472{
473 hash_t *h = hash_header (v);
474 hash_pair_t *p;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700475
476 while (1)
477 {
478 if (hn->i == 0 && hn->j == 0)
479 {
480 /* Save flags. */
481 hn->f = h->flags;
482
483 /* Prevent others from re-sizing hash table. */
484 h->flags |=
Dave Barachc3799992016-08-15 11:12:27 -0400485 (HASH_FLAG_NO_AUTO_GROW
486 | HASH_FLAG_NO_AUTO_SHRINK | HASH_FLAG_HASH_NEXT_IN_PROGRESS);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700487 }
488 else if (hn->i >= hash_capacity (v))
489 {
490 /* Restore flags. */
491 h->flags = hn->f;
Dave Barachb7b92992018-10-17 10:38:51 -0400492 clib_memset (hn, 0, sizeof (hn[0]));
Ed Warnickecb9cada2015-12-08 15:45:58 -0700493 return 0;
494 }
495
496 p = hash_forward (h, v, hn->i);
497 if (hash_is_user (v, hn->i))
498 {
499 hn->i++;
500 return p;
501 }
502 else
503 {
Dave Barachc3799992016-08-15 11:12:27 -0400504 hash_pair_indirect_t *pi = (void *) p;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700505 uword n;
506
507 if (h->log2_pair_size > 0)
508 n = indirect_pair_get_len (pi);
509 else
510 n = vec_len (pi->pairs);
511
512 if (hn->j >= n)
513 {
514 hn->i++;
515 hn->j = 0;
516 }
517 else
518 return hash_forward (h, pi->pairs, hn->j++);
519 }
520 }
521}
522
523/* Remove key from table. */
Damjan Mariondae1c7e2020-10-17 13:32:25 +0200524__clib_export void *
Dave Barachc3799992016-08-15 11:12:27 -0400525_hash_unset (void *v, uword key, void *old_value)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700526{
Dave Barachc3799992016-08-15 11:12:27 -0400527 hash_t *h;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700528
Dave Barachc3799992016-08-15 11:12:27 -0400529 if (!v)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700530 return v;
531
532 (void) lookup (v, key, UNSET, 0, old_value);
533
534 h = hash_header (v);
Dave Barachc3799992016-08-15 11:12:27 -0400535 if (!(h->flags & HASH_FLAG_NO_AUTO_SHRINK))
Ed Warnickecb9cada2015-12-08 15:45:58 -0700536 {
537 /* Resize when 1/4 full. */
538 if (h->elts > 32 && 4 * (h->elts + 1) < vec_len (v))
539 v = hash_resize (v, vec_len (v) / 2);
540 }
541
542 return v;
543}
544
Damjan Mariondae1c7e2020-10-17 13:32:25 +0200545__clib_export void *
Dave Barachc3799992016-08-15 11:12:27 -0400546_hash_create (uword elts, hash_t * h_user)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700547{
Dave Barachc3799992016-08-15 11:12:27 -0400548 hash_t *h;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700549 uword log2_pair_size;
Dave Barachc3799992016-08-15 11:12:27 -0400550 void *v;
Damjan Marione4fa1d22022-04-11 18:41:49 +0200551 vec_attr_t va = { .hdr_sz = sizeof (h[0]), .align = sizeof (hash_pair_t) };
Ed Warnickecb9cada2015-12-08 15:45:58 -0700552
553 /* Size of hash is power of 2 >= ELTS and larger than
554 number of bits in is_user bitmap elements. */
555 elts = clib_max (elts, BITS (h->is_user[0]));
Dave Barach6f6f34f2016-08-08 13:05:31 -0400556 elts = 1ULL << max_log2 (elts);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700557
558 log2_pair_size = 1;
559 if (h_user)
560 log2_pair_size = h_user->log2_pair_size;
561
Damjan Marione4fa1d22022-04-11 18:41:49 +0200562 va.elt_sz = (1 << log2_pair_size) * sizeof (hash_pair_t),
563 v = _vec_alloc_internal (elts, &va);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700564 h = hash_header (v);
565
566 if (h_user)
Damjan Marion2b702da2022-03-17 17:54:48 +0100567 {
568 h[0] = h_user[0];
569 h->is_user = 0;
570 }
Ed Warnickecb9cada2015-12-08 15:45:58 -0700571
Damjan Marion2b702da2022-03-17 17:54:48 +0100572 vec_validate_aligned (
573 h->is_user, ((elts / BITS (h->is_user[0])) * sizeof (h->is_user[0])) - 1,
574 CLIB_CACHE_LINE_BYTES);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700575 h->log2_pair_size = log2_pair_size;
576 h->elts = 0;
577
578 /* Default flags to never shrinking hash tables.
579 Shrinking tables can cause "jackpot" cases. */
Dave Barachc3799992016-08-15 11:12:27 -0400580 if (!h_user)
581 h->flags = HASH_FLAG_NO_AUTO_SHRINK;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700582
Dave Barachc3799992016-08-15 11:12:27 -0400583 if (!h->format_pair)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700584 {
585 h->format_pair = hash_format_pair_default;
586 h->format_pair_arg = 0;
587 }
588
589 return v;
590}
591
Damjan Mariondae1c7e2020-10-17 13:32:25 +0200592__clib_export void *
Dave Barachc3799992016-08-15 11:12:27 -0400593_hash_free (void *v)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700594{
Dave Barachc3799992016-08-15 11:12:27 -0400595 hash_t *h = hash_header (v);
596 hash_pair_union_t *p;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700597 uword i;
598
Dave Barachc3799992016-08-15 11:12:27 -0400599 if (!v)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700600 return v;
601
602 /* We zero all freed memory in case user would be tempted to use it. */
603 for (i = 0; i < hash_capacity (v); i++)
604 {
605 if (hash_is_user (v, i))
606 continue;
607 p = get_pair (v, i);
608 if (h->log2_pair_size == 0)
609 vec_free (p->indirect.pairs);
610 else if (p->indirect.pairs)
611 clib_mem_free (p->indirect.pairs);
612 }
613
Damjan Marion2b702da2022-03-17 17:54:48 +0100614 vec_free (h->is_user);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700615 vec_free_header (h);
616
617 return 0;
618}
619
Dave Barachc3799992016-08-15 11:12:27 -0400620static void *
621hash_resize_internal (void *old, uword new_size, uword free_old)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700622{
Dave Barachc3799992016-08-15 11:12:27 -0400623 void *new;
624 hash_pair_t *p;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700625
626 new = 0;
627 if (new_size > 0)
628 {
Dave Barachc3799992016-08-15 11:12:27 -0400629 hash_t *h = old ? hash_header (old) : 0;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700630 new = _hash_create (new_size, h);
Dave Barachc3799992016-08-15 11:12:27 -0400631 /* *INDENT-OFF* */
Ed Warnickecb9cada2015-12-08 15:45:58 -0700632 hash_foreach_pair (p, old, {
633 new = _hash_set3 (new, p->key, &p->value[0], 0);
634 });
Dave Barachc3799992016-08-15 11:12:27 -0400635 /* *INDENT-ON* */
Ed Warnickecb9cada2015-12-08 15:45:58 -0700636 }
637
638 if (free_old)
639 hash_free (old);
640 return new;
641}
642
benkerddb8d652021-12-14 15:31:14 +0000643__clib_export void *
Dave Barachc3799992016-08-15 11:12:27 -0400644hash_resize (void *old, uword new_size)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700645{
Dave Barachc3799992016-08-15 11:12:27 -0400646 return hash_resize_internal (old, new_size, 1);
647}
Ed Warnickecb9cada2015-12-08 15:45:58 -0700648
Damjan Mariondae1c7e2020-10-17 13:32:25 +0200649__clib_export void *
Dave Barachc3799992016-08-15 11:12:27 -0400650hash_dup (void *old)
651{
652 return hash_resize_internal (old, vec_len (old), 0);
653}
654
Damjan Mariondae1c7e2020-10-17 13:32:25 +0200655__clib_export void *
Dave Barachc3799992016-08-15 11:12:27 -0400656_hash_set3 (void *v, uword key, void *value, void *old_value)
657{
658 hash_t *h;
659
660 if (!v)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700661 v = hash_create (0, sizeof (uword));
662
663 h = hash_header (v);
664 (void) lookup (v, key, SET, value, old_value);
665
Dave Barachc3799992016-08-15 11:12:27 -0400666 if (!(h->flags & HASH_FLAG_NO_AUTO_GROW))
Ed Warnickecb9cada2015-12-08 15:45:58 -0700667 {
668 /* Resize when 3/4 full. */
669 if (4 * (h->elts + 1) > 3 * vec_len (v))
670 v = hash_resize (v, 2 * vec_len (v));
671 }
672
673 return v;
674}
675
Damjan Mariondae1c7e2020-10-17 13:32:25 +0200676__clib_export uword
Dave Barachc3799992016-08-15 11:12:27 -0400677vec_key_sum (hash_t * h, uword key)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700678{
Dave Barachc3799992016-08-15 11:12:27 -0400679 void *v = uword_to_pointer (key, void *);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700680 return hash_memory (v, vec_len (v) * h->user, 0);
681}
682
Damjan Mariondae1c7e2020-10-17 13:32:25 +0200683__clib_export uword
Dave Barachc3799992016-08-15 11:12:27 -0400684vec_key_equal (hash_t * h, uword key1, uword key2)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700685{
Dave Barachc3799992016-08-15 11:12:27 -0400686 void *v1 = uword_to_pointer (key1, void *);
687 void *v2 = uword_to_pointer (key2, void *);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700688 uword l1 = vec_len (v1);
689 uword l2 = vec_len (v2);
690 return l1 == l2 && 0 == memcmp (v1, v2, l1 * h->user);
691}
692
Damjan Mariondae1c7e2020-10-17 13:32:25 +0200693__clib_export u8 *
Dave Barachc3799992016-08-15 11:12:27 -0400694vec_key_format_pair (u8 * s, va_list * args)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700695{
Dave Barachc3799992016-08-15 11:12:27 -0400696 void *CLIB_UNUSED (user_arg) = va_arg (*args, void *);
697 void *v = va_arg (*args, void *);
698 hash_pair_t *p = va_arg (*args, hash_pair_t *);
699 hash_t *h = hash_header (v);
700 void *u = uword_to_pointer (p->key, void *);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700701 int i;
702
Dave Barachc3799992016-08-15 11:12:27 -0400703 switch (h->user)
704 {
705 case 1:
Ed Warnickecb9cada2015-12-08 15:45:58 -0700706 s = format (s, "%v", u);
707 break;
708
Dave Barachc3799992016-08-15 11:12:27 -0400709 case 2:
710 {
711 u16 *w = u;
712 for (i = 0; i < vec_len (w); i++)
713 s = format (s, "0x%x, ", w[i]);
714 break;
715 }
Ed Warnickecb9cada2015-12-08 15:45:58 -0700716
Dave Barachc3799992016-08-15 11:12:27 -0400717 case 4:
718 {
719 u32 *w = u;
720 for (i = 0; i < vec_len (w); i++)
721 s = format (s, "0x%x, ", w[i]);
722 break;
723 }
Ed Warnickecb9cada2015-12-08 15:45:58 -0700724
Dave Barachc3799992016-08-15 11:12:27 -0400725 case 8:
726 {
727 u64 *w = u;
728 for (i = 0; i < vec_len (w); i++)
729 s = format (s, "0x%Lx, ", w[i]);
730 break;
731 }
Ed Warnickecb9cada2015-12-08 15:45:58 -0700732
Dave Barachc3799992016-08-15 11:12:27 -0400733 default:
Ed Warnickecb9cada2015-12-08 15:45:58 -0700734 s = format (s, "0x%U", format_hex_bytes, u, vec_len (u) * h->user);
735 break;
Dave Barachc3799992016-08-15 11:12:27 -0400736 }
Ed Warnickecb9cada2015-12-08 15:45:58 -0700737
738 if (hash_value_bytes (h) > 0)
739 s = format (s, " -> 0x%wx", p->value[0]);
740
741 return s;
742}
743
Damjan Mariondae1c7e2020-10-17 13:32:25 +0200744__clib_export uword
Dave Barachc3799992016-08-15 11:12:27 -0400745mem_key_sum (hash_t * h, uword key)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700746{
Dave Barachc3799992016-08-15 11:12:27 -0400747 uword *v = uword_to_pointer (key, void *);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700748 return hash_memory (v, h->user, 0);
749}
750
Damjan Mariondae1c7e2020-10-17 13:32:25 +0200751__clib_export uword
Dave Barachc3799992016-08-15 11:12:27 -0400752mem_key_equal (hash_t * h, uword key1, uword key2)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700753{
Dave Barachc3799992016-08-15 11:12:27 -0400754 void *v1 = uword_to_pointer (key1, void *);
755 void *v2 = uword_to_pointer (key2, void *);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700756 return v1 && v2 && 0 == memcmp (v1, v2, h->user);
757}
758
Dave Barachc3799992016-08-15 11:12:27 -0400759uword
760string_key_sum (hash_t * h, uword key)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700761{
Dave Barachc3799992016-08-15 11:12:27 -0400762 char *v = uword_to_pointer (key, char *);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700763 return hash_memory (v, strlen (v), 0);
764}
765
Dave Barachc3799992016-08-15 11:12:27 -0400766uword
767string_key_equal (hash_t * h, uword key1, uword key2)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700768{
Dave Barachc3799992016-08-15 11:12:27 -0400769 void *v1 = uword_to_pointer (key1, void *);
770 void *v2 = uword_to_pointer (key2, void *);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700771 return v1 && v2 && 0 == strcmp (v1, v2);
772}
773
Dave Barachc3799992016-08-15 11:12:27 -0400774u8 *
775string_key_format_pair (u8 * s, va_list * args)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700776{
Dave Barachc3799992016-08-15 11:12:27 -0400777 void *CLIB_UNUSED (user_arg) = va_arg (*args, void *);
778 void *v = va_arg (*args, void *);
779 hash_pair_t *p = va_arg (*args, hash_pair_t *);
780 hash_t *h = hash_header (v);
781 void *u = uword_to_pointer (p->key, void *);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700782
783 s = format (s, "%s", u);
784
785 if (hash_value_bytes (h) > 0)
Dave Barachc3799992016-08-15 11:12:27 -0400786 s =
787 format (s, " -> 0x%8U", format_hex_bytes, &p->value[0],
788 hash_value_bytes (h));
Ed Warnickecb9cada2015-12-08 15:45:58 -0700789
790 return s;
791}
792
Dave Barachc3799992016-08-15 11:12:27 -0400793static u8 *
794hash_format_pair_default (u8 * s, va_list * args)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700795{
Dave Barachc3799992016-08-15 11:12:27 -0400796 void *CLIB_UNUSED (user_arg) = va_arg (*args, void *);
797 void *v = va_arg (*args, void *);
798 hash_pair_t *p = va_arg (*args, hash_pair_t *);
799 hash_t *h = hash_header (v);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700800
801 s = format (s, "0x%08x", p->key);
802 if (hash_value_bytes (h) > 0)
Dave Barachc3799992016-08-15 11:12:27 -0400803 s =
804 format (s, " -> 0x%8U", format_hex_bytes, &p->value[0],
805 hash_value_bytes (h));
Ed Warnickecb9cada2015-12-08 15:45:58 -0700806 return s;
807}
808
Damjan Mariondae1c7e2020-10-17 13:32:25 +0200809__clib_export uword
Dave Barachc3799992016-08-15 11:12:27 -0400810hash_bytes (void *v)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700811{
812 uword i, bytes;
Dave Barachc3799992016-08-15 11:12:27 -0400813 hash_t *h = hash_header (v);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700814
Dave Barachc3799992016-08-15 11:12:27 -0400815 if (!v)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700816 return 0;
817
Damjan Marion5c45d1c2022-03-17 15:32:56 +0100818 bytes = vec_mem_size (v);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700819
820 for (i = 0; i < hash_capacity (v); i++)
821 {
Dave Barachc3799992016-08-15 11:12:27 -0400822 if (!hash_is_user (v, i))
Ed Warnickecb9cada2015-12-08 15:45:58 -0700823 {
Dave Barachc3799992016-08-15 11:12:27 -0400824 hash_pair_union_t *p = get_pair (v, i);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700825 if (h->log2_pair_size > 0)
Dave Barachc3799992016-08-15 11:12:27 -0400826 bytes += 1 << indirect_pair_get_log2_bytes (&p->indirect);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700827 else
Damjan Marion5c45d1c2022-03-17 15:32:56 +0100828 bytes += vec_mem_size (p->indirect.pairs);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700829 }
830 }
831 return bytes;
832}
833
Damjan Marion4a251d02021-05-06 17:28:12 +0200834__clib_export u8 *
835format_hash (u8 *s, va_list *va)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700836{
Dave Barachc3799992016-08-15 11:12:27 -0400837 void *v = va_arg (*va, void *);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700838 int verbose = va_arg (*va, int);
Dave Barachc3799992016-08-15 11:12:27 -0400839 hash_pair_t *p;
840 hash_t *h = hash_header (v);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700841 uword i;
842
843 s = format (s, "hash %p, %wd elts, capacity %wd, %wd bytes used,\n",
Dave Barachc3799992016-08-15 11:12:27 -0400844 v, hash_elts (v), hash_capacity (v), hash_bytes (v));
Ed Warnickecb9cada2015-12-08 15:45:58 -0700845
846 {
Dave Barachc3799992016-08-15 11:12:27 -0400847 uword *occupancy = 0;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700848
849 /* Count number of buckets with each occupancy. */
850 for (i = 0; i < hash_capacity (v); i++)
851 {
852 uword j;
853
854 if (hash_is_user (v, i))
855 {
856 j = 1;
857 }
858 else
859 {
Dave Barachc3799992016-08-15 11:12:27 -0400860 hash_pair_union_t *p = get_pair (v, i);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700861 if (h->log2_pair_size > 0)
862 j = indirect_pair_get_len (&p->indirect);
863 else
864 j = vec_len (p->indirect.pairs);
865 }
866
867 vec_validate (occupancy, j);
868 occupancy[j]++;
869 }
870
871 s = format (s, " profile ");
872 for (i = 0; i < vec_len (occupancy); i++)
873 s = format (s, "%wd%c", occupancy[i],
874 i + 1 == vec_len (occupancy) ? '\n' : ' ');
875
876 s = format (s, " lookup # of compares: ");
877 for (i = 1; i < vec_len (occupancy); i++)
878 s = format (s, "%wd: .%03d%c", i,
879 (1000 * i * occupancy[i]) / hash_elts (v),
880 i + 1 == vec_len (occupancy) ? '\n' : ' ');
881
882 vec_free (occupancy);
883 }
884
885 if (verbose)
886 {
Dave Barachc3799992016-08-15 11:12:27 -0400887 /* *INDENT-OFF* */
Ed Warnickecb9cada2015-12-08 15:45:58 -0700888 hash_foreach_pair (p, v, {
889 s = format (s, " %U\n", h->format_pair, h->format_pair_arg, v, p);
890 });
Dave Barachc3799992016-08-15 11:12:27 -0400891 /* *INDENT-ON* */
Ed Warnickecb9cada2015-12-08 15:45:58 -0700892 }
893
894 return s;
895}
896
897static uword
898unformat_hash_string_internal (unformat_input_t * input,
Dave Barachc3799992016-08-15 11:12:27 -0400899 va_list * va, int is_vec)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700900{
Dave Barachc3799992016-08-15 11:12:27 -0400901 uword *hash = va_arg (*va, uword *);
902 int *result = va_arg (*va, int *);
903 u8 *string = 0;
904 uword *p;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700905
Dave Barachc3799992016-08-15 11:12:27 -0400906 if (!unformat (input, is_vec ? "%v%_" : "%s%_", &string))
Ed Warnickecb9cada2015-12-08 15:45:58 -0700907 return 0;
908
909 p = hash_get_mem (hash, string);
910 if (p)
911 *result = *p;
912
913 vec_free (string);
914 return p ? 1 : 0;
915}
916
Damjan Mariondae1c7e2020-10-17 13:32:25 +0200917__clib_export uword
Ed Warnickecb9cada2015-12-08 15:45:58 -0700918unformat_hash_vec_string (unformat_input_t * input, va_list * va)
Dave Barachc3799992016-08-15 11:12:27 -0400919{
920 return unformat_hash_string_internal (input, va, /* is_vec */ 1);
921}
Ed Warnickecb9cada2015-12-08 15:45:58 -0700922
Damjan Mariondae1c7e2020-10-17 13:32:25 +0200923__clib_export uword
Ed Warnickecb9cada2015-12-08 15:45:58 -0700924unformat_hash_string (unformat_input_t * input, va_list * va)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700925{
Dave Barachc3799992016-08-15 11:12:27 -0400926 return unformat_hash_string_internal (input, va, /* is_vec */ 0);
927}
928
Damjan Marion4a251d02021-05-06 17:28:12 +0200929__clib_export clib_error_t *
Dave Barachc3799992016-08-15 11:12:27 -0400930hash_validate (void *v)
931{
932 hash_t *h = hash_header (v);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700933 uword i, j;
Dave Barachc3799992016-08-15 11:12:27 -0400934 uword *keys = 0;
935 clib_error_t *error = 0;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700936
937#define CHECK(x) if ((error = ERROR_ASSERT (x))) goto done;
938
939 for (i = 0; i < hash_capacity (v); i++)
940 {
Dave Barachc3799992016-08-15 11:12:27 -0400941 hash_pair_union_t *pu = get_pair (v, i);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700942
943 if (hash_is_user (v, i))
944 {
945 CHECK (pu->direct.key != 0);
946 vec_add1 (keys, pu->direct.key);
947 }
948 else
949 {
Dave Barachc3799992016-08-15 11:12:27 -0400950 hash_pair_t *p;
951 hash_pair_indirect_t *pi = &pu->indirect;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700952 uword n;
953
954 n = h->log2_pair_size > 0
Dave Barachc3799992016-08-15 11:12:27 -0400955 ? indirect_pair_get_len (pi) : vec_len (pi->pairs);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700956
957 for (p = pi->pairs; n-- > 0; p = hash_forward1 (h, p))
958 {
959 /* Assert key uniqueness. */
960 for (j = 0; j < vec_len (keys); j++)
961 CHECK (keys[j] != p->key);
962 vec_add1 (keys, p->key);
963 }
964 }
965 }
966
967 CHECK (vec_len (keys) == h->elts);
968
969 vec_free (keys);
Dave Barachc3799992016-08-15 11:12:27 -0400970done:
Ed Warnickecb9cada2015-12-08 15:45:58 -0700971 return error;
972}
Dave Barachc3799992016-08-15 11:12:27 -0400973
974/*
975 * fd.io coding-style-patch-verification: ON
976 *
977 * Local Variables:
978 * eval: (c-set-style "gnu")
979 * End:
980 */