glib-aux: add nm_random_u64_range() helper

(cherry picked from commit fb1d2da979)
This commit is contained in:
Thomas Haller 2023-01-27 12:28:54 +01:00 committed by Beniamino Galvani
parent 5d95c20787
commit f12ad37c36
3 changed files with 135 additions and 0 deletions

View file

@ -447,3 +447,53 @@ again_getrandom:
return nm_utils_fd_read_loop_exact(fd, p, n, FALSE);
}
guint64
nm_random_u64_range_full(guint64 begin, guint64 end, gboolean crypto_bytes)
{
gboolean bad_crypto_bytes = FALSE;
guint64 remainder;
guint64 maxvalue;
guint64 x;
guint64 m;
/* Returns a random #guint64 equally distributed in the range [@begin..@end-1].
*
* The function always set errno. It either sets it to zero or to EAGAIN
* (if crypto_bytes were requested but not obtained). In any case, the function
* will always return a random number in the requested range (worst case, it's
* not crypto_bytes despite being requested). Check errno if you care. */
if (begin >= end) {
/* systemd's random_u64_range(0) is an alias for random_u64_range((uint64_t)-1).
* Not for us. It's a caller error to request an element from an empty range. */
return nm_assert_unreachable_val(begin);
}
m = end - begin;
if (m == 1) {
x = 0;
goto out;
}
remainder = G_MAXUINT64 % m;
maxvalue = G_MAXUINT64 - remainder;
do
if (crypto_bytes) {
if (nm_random_get_crypto_bytes(&x, sizeof(x)) < 0) {
/* Cannot get good crypto numbers. We will try our best, but fail
* and set errno below. */
crypto_bytes = FALSE;
bad_crypto_bytes = TRUE;
continue;
}
} else
nm_random_get_bytes(&x, sizeof(x));
while (x >= maxvalue);
out:
errno = bad_crypto_bytes ? EAGAIN : 0;
return begin + (x % m);
}

View file

@ -16,4 +16,39 @@ nm_random_get_bytes(void *p, size_t n)
int nm_random_get_crypto_bytes(void *p, size_t n);
static inline guint32
nm_random_u32(void)
{
guint32 v;
nm_random_get_bytes(&v, sizeof(v));
return v;
}
static inline guint64
nm_random_u64(void)
{
guint64 v;
nm_random_get_bytes(&v, sizeof(v));
return v;
}
static inline bool
nm_random_bool(void)
{
guint8 ch;
nm_random_get_bytes(&ch, sizeof(ch));
return ch % 2u;
}
guint64 nm_random_u64_range_full(guint64 begin, guint64 end, gboolean crypto_bytes);
static inline guint64
nm_random_u64_range(guint64 end)
{
return nm_random_u64_range_full(0, end, FALSE);
}
#endif /* __NM_RANDOM_UTILS_H__ */

View file

@ -137,6 +137,55 @@ test_nmhash(void)
/*****************************************************************************/
static void
test_nm_random(void)
{
int i_run;
for (i_run = 0; i_run < 1000; i_run++) {
guint64 begin;
guint64 end;
guint64 m;
guint64 x;
m = nmtst_get_rand_uint64();
m = m >> (nmtst_get_rand_uint32() % 64);
if (m == 0)
continue;
switch (nmtst_get_rand_uint32() % 4) {
case 0:
begin = 0;
break;
case 1:
begin = nmtst_get_rand_uint64() % 1000;
break;
case 2:
begin = ((G_MAXUINT64 - m) - 500) + (nmtst_get_rand_uint64() % 1000);
break;
default:
begin = nmtst_get_rand_uint64() % (G_MAXUINT64 - m);
break;
}
end = (begin + m) - 10 + (nmtst_get_rand_uint64() % 5);
if (begin >= end)
continue;
if (begin == 0 && nmtst_get_rand_bool())
x = nm_random_u64_range(end);
else
x = nm_random_u64_range_full(begin, end, nmtst_get_rand_bool());
g_assert_cmpuint(x, >=, begin);
g_assert_cmpuint(x, <, end);
}
}
/*****************************************************************************/
static const char *
_make_strv_foo(void)
{
@ -2417,6 +2466,7 @@ main(int argc, char **argv)
g_test_add_func("/general/test_inet_utils", test_inet_utils);
g_test_add_func("/general/test_garray", test_garray);
g_test_add_func("/general/test_nm_prioq", test_nm_prioq);
g_test_add_func("/general/test_nm_random", test_nm_random);
return g_test_run();
}