File: rand.c

package info (click to toggle)
critnib 1.1-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, sid, trixie
  • size: 188 kB
  • sloc: ansic: 1,414; sh: 100; makefile: 3
file content (134 lines) | stat: -rw-r--r-- 2,453 bytes parent folder | download
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
// SPDX-License-Identifier: BSD-3-Clause
/* Copyright 2019, Intel Corporation */

/*
 * rand.c -- random utils
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>

#include "rand.h"

#ifdef _WIN32
# include <bcrypt.h>
# include <process.h>
#else
# include <sys/syscall.h>
# include <pthread.h>
# ifdef __APPLE__
# include <sys/random.h>
# endif
#endif

/*
 * hash64 -- a u64 -> u64 hash
 */
uint64_t
hash64(uint64_t x)
{
	x += 0x9e3779b97f4a7c15;
	x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
	x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
	return x ^ (x >> 31);
}

/*
 * xoshiro256** random generator
 *
 * Fastest available good PRNG as of 2018 (sub-nanosecond per entry), produces
 * much better output than old stuff like rand() or Mersenne's Twister.
 *
 * By David Blackman and Sebastiano Vigna; PD/CC0 2018.
 *
 * It has a period of 2²⁵⁶-1, excluding all-zero state; it must always get
 * initialized to avoid that zero.
 */

static inline uint64_t rotl(const uint64_t x, int k)
{
	/* optimized to a single instruction on x86 */
	return (x << k) | (x >> (64 - k));
}

/*
 * rnd64_r -- return 64-bits of randomness
 */
uint64_t
rnd64_r(rng_t *state)
{
	uint64_t *s = (void *)state;

	const uint64_t result = rotl(s[1] * 5, 7) * 9;
	const uint64_t t = s[1] << 17;

	s[2] ^= s[0];
	s[3] ^= s[1];
	s[1] ^= s[2];
	s[0] ^= s[3];

	s[2] ^= t;

	s[3] = rotl(s[3], 45);

	return result;
}

/*
 * randomize_r -- initialize random generator
 *
 * Seed of 0 means random.
 */
void
randomize_r(rng_t *state, uint64_t seed)
{
	if (!seed) {
#ifdef SYS_getrandom
		/* We want getentropy() but ancient Red Hat lacks it. */
		if (syscall(SYS_getrandom, state, sizeof(rng_t), 0)
			== sizeof(rng_t)) {
			return; /* nofail, but ENOSYS on kernel < 3.16 */
		}
#elif _WIN32
#pragma comment(lib, "Bcrypt.lib")
		if (BCryptGenRandom(NULL, (PUCHAR)state, sizeof(rng_t),
			BCRYPT_USE_SYSTEM_PREFERRED_RNG)) {
			return;
		}
#else
		if (!getentropy(state, sizeof(rng_t)))
			return;
#endif
		// best effort fallback
		seed = (uint64_t)pthread_self();
	}

	uint64_t *s = (void *)state;
	s[0] = hash64(seed);
	s[1] = hash64(s[0]);
	s[2] = hash64(s[1]);
	s[3] = hash64(s[2]);
}

static rng_t global_rng;

/*
 * rnd64 -- global state version of rnd64_t
 */
uint64_t
rnd64(void)
{
	return rnd64_r(&global_rng);
}

/*
 * randomize -- initialize global RNG
 */
void
randomize(uint64_t seed)
{
	randomize_r(&global_rng, seed);
}