// SPDX-License-Identifier: LGPL-2.1+ /* * Copyright (C) 2017 Red Hat, Inc. */ #include "nm-default.h" #include "nm-hash-utils.h" #include #include "nm-shared-utils.h" #include "nm-random-utils.h" /*****************************************************************************/ #define HASH_KEY_SIZE 16u #define HASH_KEY_SIZE_GUINT ((HASH_KEY_SIZE + sizeof (guint) - 1) / sizeof (guint)) G_STATIC_ASSERT (sizeof (guint) * HASH_KEY_SIZE_GUINT >= HASH_KEY_SIZE); static const guint8 *volatile global_seed = NULL; static const guint8 * _get_hash_key_init (void) { /* the returned hash is aligned to guin64, hence, it is safe * to use it as guint* or guint64* pointer. */ static union { guint8 v8[HASH_KEY_SIZE]; guint _align_as_uint; guint32 _align_as_uint32; guint64 _align_as_uint64; } g_arr; const guint8 *g; again: g = g_atomic_pointer_get (&global_seed); if (!G_UNLIKELY (g)) { static gsize g_lock; uint64_t h; union { guint vuint; guint8 v8[HASH_KEY_SIZE]; guint8 _extra_entropy[3 * HASH_KEY_SIZE]; } t_arr; nm_utils_random_bytes (&t_arr, sizeof (t_arr)); /* We only initialize one random hash key. So we can spend some effort * of getting this right. For one, we collect more random bytes than * necessary. * * Then, the first guint of the seed should have all the entropy that we could * obtain in sizeof(t_arr). For that, siphash(t_arr) and xor the first guint * with hash. * The first guint is especially interesting for nm_hash_static() below that * doesn't use siphash itself. */ h = c_siphash_hash (t_arr.v8, (const guint8 *) &t_arr, sizeof (t_arr)); if (sizeof (h) > sizeof (guint)) t_arr.vuint = t_arr.vuint ^ ((guint) (h & G_MAXUINT)) ^ ((guint) (h >> 32)); else t_arr.vuint = t_arr.vuint ^ ((guint) (h & G_MAXUINT)); if (!g_once_init_enter (&g_lock)) { /* lost a race. The random key is already initialized. */ goto again; } memcpy (g_arr.v8, t_arr.v8, HASH_KEY_SIZE); g = g_arr.v8; g_atomic_pointer_set (&global_seed, g); g_once_init_leave (&g_lock, 1); } nm_assert (g == g_arr.v8); return g; } #define _get_hash_key() \ ({ \ const guint8 *_g; \ \ _g = g_atomic_pointer_get (&global_seed); \ if (G_UNLIKELY (!_g)) \ _g = _get_hash_key_init (); \ _g; \ }) guint nm_hash_static (guint static_seed) { /* Note that we only xor the static_seed with the first guint of the key. * * We don't use siphash, which would mix the bits better with _get_hash_key(). * Note that nm_hash_static() isn't used to hash the static_seed. Instead, it * is used to get a unique hash value in a static context. That means, every * caller is responsible to choose a static_seed that is sufficiently * distinct from all other callers. In other words, static_seed should be a * unique constant with good entropy. * * Note that _get_hash_key_init() already xored the first guint of the * key with the siphash of the entire static key. That means, even if * we got bad randomness for the first guint, the first guint is also * mixed with the randomness of the entire random key. * * Also, ensure that we don't return zero (like for nm_hash_complete()). */ return ((*((const guint *) _get_hash_key ())) ^ static_seed) ?: 3679500967u; } void nm_hash_siphash42_init (CSipHash *h, guint static_seed) { const guint8 *g; union { guint64 _align_as_uint64; guint arr[HASH_KEY_SIZE_GUINT]; } seed; nm_assert (h); g = _get_hash_key (); memcpy (&seed, g, HASH_KEY_SIZE); seed.arr[0] ^= static_seed; c_siphash_init (h, (const guint8 *) &seed); } guint nm_hash_str (const char *str) { NMHashState h; if (!str) return nm_hash_static (1867854211u); nm_hash_init (&h, 1867854211u); nm_hash_update_str (&h, str); return nm_hash_complete (&h); } guint nm_str_hash (gconstpointer str) { return nm_hash_str (str); } guint nm_hash_ptr (gconstpointer ptr) { NMHashState h; if (!ptr) return nm_hash_static (2907677551u); nm_hash_init (&h, 2907677551u); nm_hash_update (&h, &ptr, sizeof (ptr)); return nm_hash_complete (&h); } guint nm_direct_hash (gconstpointer ptr) { return nm_hash_ptr (ptr); } /*****************************************************************************/ guint nm_pstr_hash (gconstpointer p) { const char *const*s = p; if (!s) return nm_hash_static (101061439u); return nm_hash_str (*s); } gboolean nm_pstr_equal (gconstpointer a, gconstpointer b) { const char *const*s1 = a; const char *const*s2 = b; return (s1 == s2) || ( s1 && s2 && nm_streq0 (*s1, *s2)); } guint nm_pint_hash (gconstpointer p) { const int *s = p; if (!s) return nm_hash_static (298377461u); return nm_hash_val (1208815757u, *s); } gboolean nm_pint_equals (gconstpointer a, gconstpointer b) { const int *s1 = a; const int *s2 = a; return s1 == s2 || (s1 && s2 && *s1 == *s2); } guint nm_pdirect_hash (gconstpointer p) { const void *const*s = p; if (!s) return nm_hash_static (1852748873u); return nm_direct_hash (*s); } gboolean nm_pdirect_equal (gconstpointer a, gconstpointer b) { const void *const*s1 = a; const void *const*s2 = b; return (s1 == s2) || ( s1 && s2 && *s1 == *s2); } guint nm_ppdirect_hash (gconstpointer p) { const void *const*const*s = p; if (!s) return nm_hash_static (396534869u); if (!*s) return nm_hash_static (1476102263u); return nm_direct_hash (**s); } gboolean nm_ppdirect_equal (gconstpointer a, gconstpointer b) { const void *const*const*s1 = a; const void *const*const*s2 = b; if (s1 == s2) return TRUE; if (!s1 || !s2) return FALSE; if (*s1 == *s2) return TRUE; if (!*s1 || !*s2) return FALSE; return **s1 == **s2; } /*****************************************************************************/ guint nm_gbytes_hash (gconstpointer p) { GBytes *ptr = (GBytes *) p; gconstpointer arr; gsize len; arr = g_bytes_get_data (ptr, &len); return nm_hash_mem (792701303u, arr, len); } guint nm_pgbytes_hash (gconstpointer p) { GBytes *const*ptr = p; gconstpointer arr; gsize len; arr = g_bytes_get_data (*ptr, &len); return nm_hash_mem (1470631313u, arr, len); } gboolean nm_pgbytes_equal (gconstpointer a, gconstpointer b) { GBytes *const*ptr_a = a; GBytes *const*ptr_b = b; return g_bytes_equal (*ptr_a, *ptr_b); }