summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2023-01-27 12:28:54 +0100
committerThomas Haller <thaller@redhat.com>2023-01-30 10:48:53 +0100
commitfb1d2da97927c2415773901a2548010e78575db8 (patch)
tree6600aa6d53a60cd2d2cace203d11f4bdb67c7d38
parent00affc7b6f268d54f0205e325806ba9ab7e3da22 (diff)
downloadNetworkManager-fb1d2da97927c2415773901a2548010e78575db8.tar.gz
glib-aux: add nm_random_u64_range() helper
-rw-r--r--src/libnm-glib-aux/nm-random-utils.c50
-rw-r--r--src/libnm-glib-aux/nm-random-utils.h35
-rw-r--r--src/libnm-glib-aux/tests/test-shared-general.c50
3 files changed, 135 insertions, 0 deletions
diff --git a/src/libnm-glib-aux/nm-random-utils.c b/src/libnm-glib-aux/nm-random-utils.c
index 93eee7c4d3..8930ed491b 100644
--- a/src/libnm-glib-aux/nm-random-utils.c
+++ b/src/libnm-glib-aux/nm-random-utils.c
@@ -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);
+}
diff --git a/src/libnm-glib-aux/nm-random-utils.h b/src/libnm-glib-aux/nm-random-utils.h
index ab8aee1b03..729d71a4d7 100644
--- a/src/libnm-glib-aux/nm-random-utils.h
+++ b/src/libnm-glib-aux/nm-random-utils.h
@@ -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__ */
diff --git a/src/libnm-glib-aux/tests/test-shared-general.c b/src/libnm-glib-aux/tests/test-shared-general.c
index 7503dc9b9f..3eaca5474a 100644
--- a/src/libnm-glib-aux/tests/test-shared-general.c
+++ b/src/libnm-glib-aux/tests/test-shared-general.c
@@ -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();
}