Make Rand use a provided Mersenne Twister.

This implementation is loosely inspired by the original paper on the
Mersenne Twister, and borrows code from a public-domain implementation of
it, adapting it to fit the style of Telodendria's code, and fixing a few
bugs regarding the size of the data type used.

Neither C nor POSIX provide a good, thread-safe pseudorandom number
generator. The OpenBSD linker started complaining about the use of
rand_r(), and no standard alternative presented itself as worthy of
consideration, so I finally decided it was time to roll my own PRNG.
This commit is contained in:
Jordan Bancino 2023-04-25 21:28:55 +00:00
parent 098eed44a0
commit d933d12e1b
5 changed files with 198 additions and 48 deletions

View file

@ -23,12 +23,80 @@
*/ */
#include <Rand.h> #include <Rand.h>
#include <Int.h>
#include <Util.h> #include <Util.h>
#include <stdlib.h> #include <stdlib.h>
#include <pthread.h> #include <pthread.h>
#include <unistd.h> #include <unistd.h>
#define RAND_STATE_VECTOR_LENGTH 624
#define RAND_STATE_VECTOR_M 397
#define RAND_UPPER_MASK 0x80000000
#define RAND_LOWER_MASK 0x7FFFFFFF
#define RAND_TEMPER_B 0x9D2C5680
#define RAND_TEMPER_C 0xEFC60000
typedef struct RandState
{
UInt32 mt[RAND_STATE_VECTOR_LENGTH];
int index;
} RandState;
static void
RandSeed(RandState *state, UInt32 seed)
{
state->mt[0] = seed & 0xFFFFFFFF;
for (state->index = 1; state->index < RAND_STATE_VECTOR_LENGTH; state->index++)
{
state->mt[state->index] = (6069 * state->mt[state->index - 1]) & 0xFFFFFFFF;
}
}
static UInt32
RandGenerate(RandState *state)
{
static const UInt32 mag[2] = { 0x0, 0x9908B0DF };
UInt32 result;
if (state->index >= RAND_STATE_VECTOR_LENGTH || state->index < 0)
{
int kk;
if (state->index >= RAND_STATE_VECTOR_LENGTH + 1 || state->index < 0)
{
RandSeed(state, 4357);
}
for (kk = 0; kk < RAND_STATE_VECTOR_LENGTH - RAND_STATE_VECTOR_M; kk++)
{
result = (state->mt[kk] & RAND_UPPER_MASK) | (state->mt[kk + 1] & RAND_LOWER_MASK);
state->mt[kk] = state->mt[kk + RAND_STATE_VECTOR_M] ^ (result >> 1) ^ mag[result & 0x1];
}
for (; kk < RAND_STATE_VECTOR_LENGTH - 1; kk++)
{
result = (state->mt[kk] & RAND_UPPER_MASK) | (state->mt[kk + 1] & RAND_LOWER_MASK);
state->mt[kk] = state->mt[kk + (RAND_STATE_VECTOR_M - RAND_STATE_VECTOR_LENGTH)] ^ (result >> 1) ^ mag[result & 0x1];
}
result = (state->mt[RAND_STATE_VECTOR_LENGTH - 1] & RAND_UPPER_MASK) | (state->mt[0] & RAND_LOWER_MASK);
state->mt[RAND_STATE_VECTOR_LENGTH - 1] = state->mt[RAND_STATE_VECTOR_M - 1] ^ (result >> 1) ^ mag[result & 0x1];
state->index = 0;
}
result = state->mt[state->index++];
result ^= (result >> 11);
result ^= (result << 7) & RAND_TEMPER_B;
result ^= (result << 15) & RAND_TEMPER_C;
result ^= (result >> 18);
return result;
}
/* Generate random numbers using rejection sampling. The basic idea is /* Generate random numbers using rejection sampling. The basic idea is
* to "reroll" if a number happens to be outside the range. However * to "reroll" if a number happens to be outside the range. However
* this could be extremely inefficient. * this could be extremely inefficient.
@ -44,20 +112,23 @@
void void
RandIntN(int *buf, size_t size, unsigned int max) RandIntN(int *buf, size_t size, unsigned int max)
{ {
static pthread_mutex_t seedLock = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t stateLock = PTHREAD_MUTEX_INITIALIZER;
static unsigned int seed = 0; static UInt32 seed = 0;
int tmp; static RandState state;
/* Limit the range to banish all previously biased results */ /* Limit the range to banish all previously biased results */
const int allowed = RAND_MAX - RAND_MAX % max; const int allowed = RAND_MAX - RAND_MAX % max;
int tmp;
size_t i; size_t i;
pthread_mutex_lock(&seedLock); pthread_mutex_lock(&stateLock);
if (!seed) if (!seed)
{ {
/* Generate a seed from the system time, PID, and TID */ /* Generate a seed from the system time, PID, and TID */
seed = UtilServerTs() ^ getpid() ^ (unsigned long) pthread_self(); seed = UtilServerTs() ^ getpid() ^ (unsigned long) pthread_self();
RandSeed(&state, seed);
} }
/* Generate {size} random numbers. */ /* Generate {size} random numbers. */
@ -66,13 +137,13 @@ RandIntN(int *buf, size_t size, unsigned int max)
/* Most of the time, this will take about 1 loop */ /* Most of the time, this will take about 1 loop */
do do
{ {
tmp = rand_r(&seed); tmp = RandGenerate(&state);
} while (tmp >= allowed); } while (tmp > allowed);
/* Since a generated number here is never in the biased range,
* we can now safely use modulo. */
buf[i] = tmp % max; buf[i] = tmp % max;
} }
pthread_mutex_unlock(&seedLock);
pthread_mutex_unlock(&stateLock);
} }
/* Generate just 1 random number */ /* Generate just 1 random number */

View file

@ -23,41 +23,18 @@
*/ */
#include <Sha2.h> #include <Sha2.h>
#include <Memory.h> #include <Memory.h>
#include <Int.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <limits.h> #include <limits.h>
/*
* POSIX says that LONG_BIT and WORD_BIT are defined, but some notable
* non-conforming systems and compilers don't don't define it, or only
* define it in circumstances I'm unwilling to comply with (such as
* defining _GNU_SOURCE.
*
* So, unfortunately, although LONG_BIT and WORD_BIT are the most
* elegant solutions, we're forced to do this so we can check LONG_MAX
* and INT_MAX.
*/
#define BIT64_MAX 9223372036854775807
#define BIT32_MAX 2147483647
#define BIT16_MAX 32767
#if (defined(LONG_BIT) && LONG_BIT == 32) || (defined(LONG_MAX) && LONG_MAX == BIT32_MAX)
typedef unsigned long uint32_t;
#elif (defined(WORD_BIT) && WORD_BIT == 32) || (defined(INT_MAX) && INT_MAX == BIT32_MAX)
typedef unsigned int uint32_t;
#else
#error Unable to find suitable integer type for uint32_t
#endif
#define GET_UINT32(x) \ #define GET_UINT32(x) \
(((uint32_t)(x)[0] << 24) | \ (((UInt32)(x)[0] << 24) | \
((uint32_t)(x)[1] << 16) | \ ((UInt32)(x)[1] << 16) | \
((uint32_t)(x)[2] << 8) | \ ((UInt32)(x)[2] << 8) | \
((uint32_t)(x)[3])) ((UInt32)(x)[3]))
#define PUT_UINT32(dst, x) { \ #define PUT_UINT32(dst, x) { \
(dst)[0] = (x) >> 24; \ (dst)[0] = (x) >> 24; \
@ -79,8 +56,8 @@ typedef unsigned int uint32_t;
#define WW(i) (w[i] = w[i - 16] + S0(w[i - 15]) + w[i - 7] + S1(w[i - 2])) #define WW(i) (w[i] = w[i - 16] + S0(w[i - 15]) + w[i - 7] + S1(w[i - 2]))
#define ROUND(a, b, c, d, e, f, g, h, k, w) { \ #define ROUND(a, b, c, d, e, f, g, h, k, w) { \
uint32_t tmp0 = h + T0(e) + CH(e, f, g) + k + w; \ UInt32 tmp0 = h + T0(e) + CH(e, f, g) + k + w; \
uint32_t tmp1 = T1(a) + MAJ(a, b, c); \ UInt32 tmp1 = T1(a) + MAJ(a, b, c); \
h = tmp0 + tmp1; \ h = tmp0 + tmp1; \
d += tmp0; \ d += tmp0; \
} }
@ -88,7 +65,7 @@ typedef unsigned int uint32_t;
typedef struct Sha256Context typedef struct Sha256Context
{ {
size_t length; size_t length;
uint32_t state[8]; UInt32 state[8];
size_t bufLen; size_t bufLen;
unsigned char buffer[64]; unsigned char buffer[64];
} Sha256Context; } Sha256Context;
@ -96,7 +73,7 @@ typedef struct Sha256Context
static void static void
Sha256Chunk(Sha256Context * context, unsigned char chunk[64]) Sha256Chunk(Sha256Context * context, unsigned char chunk[64])
{ {
const uint32_t rk[64] = { const UInt32 rk[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
@ -110,8 +87,8 @@ Sha256Chunk(Sha256Context * context, unsigned char chunk[64])
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
}; };
uint32_t w[64]; UInt32 w[64];
uint32_t a, b, c, d, e, f, g, h; UInt32 a, b, c, d, e, f, g, h;
int i; int i;
@ -202,10 +179,10 @@ Sha256(char *str)
char *outStr; char *outStr;
unsigned char fill[64]; unsigned char fill[64];
uint32_t fillLen; UInt32 fillLen;
unsigned char buf[8]; unsigned char buf[8];
uint32_t hiLen; UInt32 hiLen;
uint32_t loLen; UInt32 loLen;
if (!str) if (!str)
{ {
@ -237,8 +214,8 @@ Sha256(char *str)
fill[0] = 0x80; fill[0] = 0x80;
fillLen = (context.bufLen < 56) ? 56 - context.bufLen : 120 - context.bufLen; fillLen = (context.bufLen < 56) ? 56 - context.bufLen : 120 - context.bufLen;
hiLen = (uint32_t) (context.length >> 29); hiLen = (UInt32) (context.length >> 29);
loLen = (uint32_t) (context.length << 3); loLen = (UInt32) (context.length << 3);
PUT_UINT32(&buf[0], hiLen); PUT_UINT32(&buf[0], hiLen);
PUT_UINT32(&buf[4], loLen); PUT_UINT32(&buf[4], loLen);

73
src/include/Int.h Normal file
View file

@ -0,0 +1,73 @@
#ifndef TELODENDRIA_INT_H
#define TELODENDRIA_INT_H
#include <limits.h>
#define BIT64_MAX 18446744073709551615
#define BIT32_MAX 4294967295
#define BIT16_MAX 65535
#define BIT8_MAX 255
#ifndef UCHAR_MAX
#error Size of char data type is unknown. Define UCHAR_MAX.
#endif
#ifndef USHRT_MAX
#error Size of short data type is unknown. Define USHRT_MAX.
#endif
#ifndef UINT_MAX
#error Size of int data type is unknown. Define UINT_MAX.
#endif
#ifndef ULONG_MAX
#error Size of long data type is unknown. Define ULONG_MAX.
#endif
#if UCHAR_MAX == BIT8_MAX
typedef signed char Int8;
typedef unsigned char UInt8;
#else
#error Unable to determine suitable data type for 8-bit integers.
#endif
#if UINT_MAX == BIT16_MAX
typedef signed int Int16;
typedef unsigned int UInt16;
#elif USHRT_MAX == BIT16_MAX
typedef signed short Int16;
typedef unsigned short UInt16;
#elif UCHAR_MAX == BIT16_MAX
typedef signed char Int16;
typedef unsigned char UInt16;
#else
#error Unable to determine suitable data type for 16-bit integers.
#endif
#if ULONG_MAX == BIT32_MAX
typedef signed long Int32;
typedef unsigned long UInt32;
#elif UINT_MAX == BIT32_MAX
typedef signed int Int32;
typedef unsigned int UInt32;
#elif USHRT_MAX == BIT32_MAX
typedef signed short Int32;
typedef unsigned short UInt32;
#elif UCHAR_MAX == BIT32_MAX
typedef signed char Int32;
typedef unsigned char UInt32;
#else
#error Unable to determine suitable data type for 32-bit integers.
#endif
/* The ANSI C standard only guarantees a data size of up to 32 bits. */
#endif

View file

@ -154,6 +154,10 @@ recipe_build() {
fi fi
fi fi
done done
if ! intcheck; then
exit 1
fi
} }
recipe_run() { recipe_run() {

25
tools/src/intcheck.c Normal file
View file

@ -0,0 +1,25 @@
#include <Int.h>
#include <stdio.h>
#define ASSERT_SIZE(type, size) \
if ((sizeof(type) * 8) != size) \
{ \
fputs(#type " is not " #size " bits.\n", stderr); \
return 1; \
}
int
main(void)
{
ASSERT_SIZE(Int8, 8);
ASSERT_SIZE(UInt8, 8);
ASSERT_SIZE(Int16, 16);
ASSERT_SIZE(UInt16, 16);
ASSERT_SIZE(Int32, 32);
ASSERT_SIZE(UInt32, 32);
return 0;
}