summaryrefslogtreecommitdiff
path: root/shared/nm-glib-aux/nm-hash-utils.c
blob: c2df5a88a53b53d5530ffd75f0e4ace8d5eb0bd4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
// SPDX-License-Identifier: LGPL-2.1+
/*
 * Copyright (C) 2017 Red Hat, Inc.
 */

#include "nm-default.h"

#include "nm-hash-utils.h"

#include <stdint.h>

#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);
}