@cryptotaxi247 / netdata-1 / commits / 8af00b9db

random numbers No 3 (#18940)

prefer arc4 functions; use arc4 for unbiased number < UINT32_MAX; use bitmasking when the max is power of two

Costa Tsaousis committed Nov 5, 2024 at 09:27 UTC 8af00b9dbe3d3a668144a43bcc01bcdb766109af
3 files changed +57 -50
CMakeLists.txt
+1
@@ -389,6 +389,7 @@ check_function_exists(close_range HAVE_CLOSE_RANGE)
389 check_function_exists(backtrace HAVE_BACKTRACE)
390
391 check_function_exists(arc4random_buf HAVE_ARC4RANDOM_BUF)
392 +check_function_exists(arc4random_uniform HAVE_ARC4RANDOM_UNIFORM)
393 check_function_exists(getrandom HAVE_GETRANDOM)
394
395 #
packaging/cmake/config.cmake.h.in
+1
@@ -70,6 +70,7 @@
70 #cmakedefine HAVE_LIBCURL
71
72 #cmakedefine HAVE_ARC4RANDOM_BUF
73 +#cmakedefine HAVE_ARC4RANDOM_UNIFORM
74 #cmakedefine HAVE_RAND_S
75 #cmakedefine HAVE_GETRANDOM
76
src/libnetdata/os/random.c
+55 -50
@@ -15,7 +15,7 @@ static __attribute__((constructor)) void random_seed() {
15 srandom(seed);
16 }
17
18 -static void random_get_bytes(void *buf, size_t bytes) {
18 +static inline void random_bytes(void *buf, size_t bytes) {
19 spinlock_lock(&random_lock);
20 while (bytes > 0) {
21 if (bytes >= sizeof(uint32_t)) {
@@ -45,10 +45,10 @@ static void random_get_bytes(void *buf, size_t bytes) {
45
46 #if defined(HAVE_GETRANDOM)
47 #include <sys/random.h>
48 -void inline getrandom_get_bytes(void *buf, size_t buflen) {
48 +static inline void getrandom_bytes(void *buf, size_t bytes) {
49 ssize_t result;
50 - while (buflen > 0) {
51 - result = getrandom(buf, buflen, 0);
50 + while (bytes > 0) {
51 + result = getrandom(buf, bytes, 0);
52 if (result == -1) {
53 if (errno == EINTR) {
54 // Interrupted, retry
@@ -59,19 +59,19 @@ void inline getrandom_get_bytes(void *buf, size_t buflen) {
59 continue;
60 } else {
61 // fallback to RAND_bytes
62 - random_get_bytes(buf, buflen);
62 + random_bytes(buf, bytes);
63 return;
64 }
65 }
66 buf = (uint8_t *)buf + result;
67 - buflen -= result;
67 + bytes -= result;
68 }
69 }
70 #endif // HAVE_GETRANDOM
71 #endif // !HAVE_ARC4RANDOM_BUF && !HAVE_RAND_S
72
73 #if defined(HAVE_RAND_S)
74 -static inline void rand_s_get_bytes(void *buf, size_t bytes) {
74 +static inline void rand_s_bytes(void *buf, size_t bytes) {
75 while (bytes > 0) {
76 if (bytes >= sizeof(unsigned int)) {
77 unsigned int temp;
@@ -103,17 +103,20 @@ static inline void rand_s_get_bytes(void *buf, size_t bytes) {
103 #endif
104
105 inline void os_random_bytes(void *buf, size_t bytes) {
106 +#if defined(HAVE_ARC4RANDOM_BUF)
107 + arc4random_buf(buf, bytes);
108 +#else
109 +
110 if(RAND_bytes((unsigned char *)buf, bytes) == 1)
111 return;
112
109 -#if defined(HAVE_ARC4RANDOM_BUF)
110 - arc4random_buf(buf, bytes);
111 -#elif defined(HAVE_GETRANDOM)
112 - getrandom_get_bytes(buf, bytes);
113 +#if defined(HAVE_GETRANDOM)
114 + getrandom_bytes(buf, bytes);
115 #elif defined(HAVE_RAND_S)
114 - rand_s_get_bytes(buf, bytes);
116 + rand_s_bytes(buf, bytes);
117 #else
116 - random_get_bytes(buf, bytes);
118 + random_bytes(buf, bytes);
119 +#endif
120 #endif
121 }
122
@@ -145,51 +148,53 @@ uint64_t os_random64(void) {
148 return value;
149 }
150
148 -#define MAX_RETRIES 10 // Limit retries to avoid an infinite loop
151 +/*
152 + * Rejection Sampling
153 + * To reduce bias, we can use rejection sampling without creating an infinite loop.
154 + * This technique works by discarding values that would introduce bias, but limiting
155 + * the number of retries to avoid infinite loops.
156 +*/
157 +
158 +// Calculate an upper limit so that the range evenly divides into max.
159 +// Any values greater than this limit would introduce bias, so we discard them.
160 +#define MAX_RETRIES 10
161 +#define os_random_rejection_sampling_X(type, type_max, func, max) \
162 + ({ \
163 + size_t retries = 0; \
164 + type value, upper_limit = type_max - (type_max % (max)); \
165 + while ((value = func()) >= upper_limit && retries++ < MAX_RETRIES); \
166 + value % (max); \
167 + })
168 +
169 uint64_t os_random(uint64_t max) {
170 if (max <= 1) return 0;
171
152 - uint64_t value;
153 - uint64_t upper_limit;
154 - int retries = 0;
155 -
156 - /*
157 - * Rejection Sampling
158 - * To reduce bias, we can use rejection sampling without creating an infinite loop.
159 - * This technique works by discarding values that would introduce bias, but limiting
160 - * the number of retries to avoid infinite loops.
161 - */
162 -
163 - // Calculate an upper limit so that the range evenly divides into max.
164 - // Any values greater than this limit would introduce bias, so we discard them.
172 +#if defined(HAVE_ARC4RANDOM_UNIFORM)
173 + if(max <= UINT32_MAX)
174 + // this is not biased
175 + return arc4random_uniform(max);
176 +#endif
177
166 - if (max <= UINT8_MAX)
167 - upper_limit = UINT8_MAX - (UINT8_MAX % max);
168 - else if (max <= UINT16_MAX)
169 - upper_limit = UINT16_MAX - (UINT16_MAX % max);
170 - else if (max <= UINT32_MAX)
171 - upper_limit = UINT32_MAX - (UINT32_MAX % max);
172 - else
173 - upper_limit = UINT64_MAX - (UINT64_MAX % max);
178 + if ((max & (max - 1)) == 0) {
179 + // max is a power of 2
180 + // use bitmasking to directly generate an unbiased random number
181
175 - do {
176 - // Generate a random number with the appropriate number of bits
182 if (max <= UINT8_MAX)
178 - value = os_random8();
183 + return os_random8() & (max - 1);
184 else if (max <= UINT16_MAX)
180 - value = os_random16();
185 + return os_random16() & (max - 1);
186 else if (max <= UINT32_MAX)
182 - value = os_random32();
187 + return os_random32() & (max - 1);
188 else
184 - value = os_random64();
185 -
186 - // Retry if the generated value is biased (i.e., exceeds upper_limit)
187 - if (value < upper_limit)
188 - return value % max; // Value is unbiased, return directly
189 -
190 - retries++;
191 - } while (retries < MAX_RETRIES);
189 + return os_random64() & (max - 1);
190 + }
191
193 - // Fallback to modulo after MAX_RETRIES, accepting minor bias
194 - return value % max;
192 + if (max <= UINT8_MAX)
193 + return os_random_rejection_sampling_X(uint8_t, UINT8_MAX, os_random8, max);
194 + else if (max <= UINT16_MAX)
195 + return os_random_rejection_sampling_X(uint16_t, UINT16_MAX, os_random16, max);
196 + else if (max <= UINT32_MAX)
197 + return os_random_rejection_sampling_X(uint32_t, UINT32_MAX, os_random32, max);
198 + else
199 + return os_random_rejection_sampling_X(uint64_t, UINT64_MAX, os_random64, max);
200 }