540 lines
14 KiB
C++
540 lines
14 KiB
C++
/***
|
|
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
|
|
|
|
File: AuRandomDevice.cpp
|
|
Date: 2021-9-3
|
|
Author: Reece
|
|
***/
|
|
#include <AuroraRuntime.hpp>
|
|
#include "AuRandomDevice.hpp"
|
|
|
|
namespace Aurora::RNG
|
|
{
|
|
static const double kDblEpsilon = 2.2204460492503131e-16;
|
|
|
|
RandomDevice::RandomDevice() :
|
|
IO::Adapters::RandomStreamReader(AuUnsafeRaiiToShared(this))
|
|
{
|
|
|
|
}
|
|
|
|
RandomDevice::RandomDevice(const RandomDef &def) :
|
|
IO::Adapters::RandomStreamReader(AuUnsafeRaiiToShared(this))
|
|
{
|
|
this->Init(def);
|
|
}
|
|
|
|
void RandomDevice::Init(const RandomDef &def)
|
|
{
|
|
this->def_ = def;
|
|
|
|
// Gross...
|
|
if (!def.bSecure)
|
|
{
|
|
if (def.seed)
|
|
{
|
|
this->fast_ = AuMove(WELL_SeedRand(def.seed.value()));
|
|
}
|
|
else if (def.seed64)
|
|
{
|
|
this->fast_ = AuMove(WELL_SeedRand64(def.seed64.value()));
|
|
}
|
|
else if (def.seedMassive)
|
|
{
|
|
this->fast_ = AuMove(WELL_SeedRandBig64(def.seedMassive.value()));
|
|
}
|
|
else
|
|
{
|
|
RNG::RngFillArray<false>(this->fast_.state);
|
|
}
|
|
}
|
|
// secure rng requires no init -> we just passthrough to the global ReadSecureRNG function
|
|
}
|
|
|
|
void RandomDevice::Read(Memory::MemoryViewWrite view)
|
|
{
|
|
if (!view)
|
|
{
|
|
SysPushErrorArg();
|
|
return;
|
|
}
|
|
|
|
if (this->def_.bSecure)
|
|
{
|
|
ReadSecureRNG(view);
|
|
}
|
|
else
|
|
{
|
|
WELL_NextBytes(&this->fast_, view.ptr, AuUInt32(view.length));
|
|
}
|
|
}
|
|
|
|
AuString RandomDevice::NextString(AuUInt32 uLength, ERngStringCharacters type)
|
|
{
|
|
AuString ret(uLength, '\00');
|
|
NextString(ret.data(), AuUInt32(ret.size()), type);
|
|
return ret;
|
|
}
|
|
|
|
static double RngConvertToDecimal(AuUInt64 uLargeInt)
|
|
{
|
|
AuUInt64 qwValue = (uLargeInt & 0xFFFFFFFFFFFFFull) | 0x3FF0000000000000ull;
|
|
return *(double *)(&qwValue) - 1.0;
|
|
}
|
|
|
|
void RandomDevice::NextString(char *pString, AuUInt32 uLength, ERngStringCharacters type)
|
|
{
|
|
static AuPair<const char *, int> rngSequence[static_cast<int>(ERngStringCharacters::eEnumCount)] =
|
|
{
|
|
{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", 52},
|
|
{"abcdefghijklmnopqrstuvwxyz", 26},
|
|
{"ABCDEFGHIJKLMNOPQRSTUVWXYZ", 26},
|
|
{"1234567890", 10},
|
|
{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", 62},
|
|
{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890=-+!$%^*.[];:", 75}
|
|
};
|
|
|
|
if (!pString)
|
|
{
|
|
SysPushErrorArg();
|
|
return;
|
|
}
|
|
|
|
Read(AuMemoryViewWrite { pString, uLength });
|
|
|
|
if (!ERngStringCharactersIsValid(type))
|
|
{
|
|
SysPushErrorArg("NextString was called with an invalid ERngStringCharacters {}", (AuUInt)type);
|
|
type = ERngStringCharacters::eAlphaNumericCharacters; // guess we cant just return false
|
|
}
|
|
|
|
const auto &pair = rngSequence[static_cast<int>(type)];
|
|
|
|
for (auto i = 0u; i < uLength; i++)
|
|
{
|
|
auto uUpperBound = AuRoundUpPow2(pair.second);
|
|
AuUInt8 uNext {};
|
|
auto uWord = reinterpret_cast<const AuUInt8 *>(pString)[i];
|
|
while ((uNext = (uWord & (uUpperBound - 1))) >= pair.second)
|
|
{
|
|
uWord = AuFnv1a32Runtime(&uWord, sizeof(uWord));
|
|
}
|
|
pString[i] = pair.first[uNext];
|
|
}
|
|
}
|
|
|
|
AuUInt8 RandomDevice::NextByte()
|
|
{
|
|
if (this->def_.bSecure)
|
|
{
|
|
AuUInt8 uRet {};
|
|
ReadSecureRNG({ &uRet, 1 });
|
|
return uRet;
|
|
}
|
|
else
|
|
{
|
|
return AuUInt8(WELL_NextLong(&this->fast_) & 0xFF);
|
|
}
|
|
}
|
|
|
|
bool RandomDevice::NextBoolean()
|
|
{
|
|
if (this->def_.bSecure)
|
|
{
|
|
AuUInt8 uRet {};
|
|
ReadSecureRNG({ &uRet, 1 });
|
|
return bool(uRet & 0x1);
|
|
}
|
|
else
|
|
{
|
|
return bool(WELL_NextLong(&this->fast_) & 0x1);
|
|
}
|
|
}
|
|
|
|
AuUInt32 RandomDevice::NextU32()
|
|
{
|
|
if (this->def_.bSecure)
|
|
{
|
|
AuUInt32 uRet {};
|
|
ReadSecureRNG({ &uRet, 4 });
|
|
return uRet;
|
|
}
|
|
else
|
|
{
|
|
return WELL_NextLong(&this->fast_);
|
|
}
|
|
}
|
|
|
|
AuUInt64 RandomDevice::NextU64()
|
|
{
|
|
if (this->def_.bSecure)
|
|
{
|
|
AuUInt64 uRet {};
|
|
ReadSecureRNG({ &uRet, 8 });
|
|
return uRet;
|
|
}
|
|
else
|
|
{
|
|
return NextFillTmpl<AuUInt64>();
|
|
}
|
|
}
|
|
|
|
AuInt32 RandomDevice::NextI32Range(AuInt32 uMin, AuInt32 uMax)
|
|
{
|
|
auto uRange = uMax - uMin;
|
|
auto uMassiveWord = NextU32();
|
|
auto uUpperBound = AuRoundUpPow2(uRange + 1);
|
|
AuUInt32 uNext {};
|
|
while ((uNext = (uMassiveWord & (uUpperBound - 1))) > uRange)
|
|
{
|
|
uMassiveWord = AuFnv1a32Runtime(&uMassiveWord, sizeof(uMassiveWord));
|
|
}
|
|
return uMin + uNext;
|
|
}
|
|
|
|
AuUInt32 RandomDevice::NextU32Range(AuUInt32 uMin, AuUInt32 uMax)
|
|
{
|
|
auto uRange = uMax - uMin;
|
|
auto uMassiveWord = NextU32();
|
|
auto uUpperBound = AuRoundUpPow2(uRange + 1);
|
|
AuUInt32 uNext {};
|
|
while ((uNext = (uMassiveWord & (uUpperBound - 1))) > uRange)
|
|
{
|
|
uMassiveWord = AuFnv1a32Runtime(&uMassiveWord, sizeof(uMassiveWord));
|
|
}
|
|
return uMin + uNext;
|
|
}
|
|
|
|
// Source: https://stackoverflow.com/a/5016867
|
|
bool RandomDevice::DecGeometric(int x)
|
|
{
|
|
if (x <= 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
auto r = this->NextByte();
|
|
|
|
if (x < 8)
|
|
{
|
|
return (r & ((1 << x) - 1)) == 0;
|
|
}
|
|
else if (r != 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
x -= 8;
|
|
}
|
|
}
|
|
|
|
// Source: https://stackoverflow.com/a/5016867
|
|
double RandomDevice::UniformFloatInRange(double udMin, double udMax)
|
|
{
|
|
#if defined(AURNG_USE_GARBAGE_DECIMALS)
|
|
return (double(this->NextU32()) * (double(1.0) / double(AuNumericLimits<AuUInt32>::max()))) * udMax;
|
|
#elif defined(AURNG_USE_UNIFORM_DECIMALS)
|
|
union
|
|
{
|
|
double f;
|
|
AuUInt64 u;
|
|
} convert;
|
|
|
|
convert.f = udMin;
|
|
auto uABits = convert.u;
|
|
convert.f = udMax;
|
|
auto uBBits = convert.u;
|
|
|
|
auto uMask = uBBits - uABits;
|
|
uMask |= uMask >> 1;
|
|
uMask |= uMask >> 2;
|
|
uMask |= uMask >> 4;
|
|
uMask |= uMask >> 8;
|
|
uMask |= uMask >> 16;
|
|
uMask |= uMask >> 32;
|
|
|
|
int bExp {};
|
|
frexp(udMax, &bExp);
|
|
|
|
while (true)
|
|
{
|
|
int iXExp {};
|
|
double x;
|
|
|
|
auto uXBits = this->NextU64();
|
|
uXBits &= uMask;
|
|
uXBits += uABits;
|
|
if (uXBits >= uBBits)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
convert.u = uXBits;
|
|
x = convert.f;
|
|
frexp(x, &iXExp);
|
|
|
|
if (DecGeometric(bExp - iXExp))
|
|
{
|
|
return x;
|
|
}
|
|
}
|
|
#else
|
|
#if !defined(AURNG_USE_FAST_DECIMALS)
|
|
#define AURNG_USE_FAST_DECIMALS
|
|
return 0.0;
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
AuList<AuInt32> RandomDevice::NextArrayI32Range(AuUInt32 uCount, AuInt32 iMin, AuInt32 iMax)
|
|
{
|
|
AuList<AuInt32> ret;
|
|
AuList<AuUInt32> rngBytes;
|
|
|
|
rngBytes.resize(uCount);
|
|
ret.resize(uCount);
|
|
this->Read(rngBytes);
|
|
|
|
auto uRange = iMax - iMin;
|
|
auto uUpperBound = AuRoundUpPow2(uRange + 1);
|
|
for (AU_ITERATE_N(uIndex, rngBytes.size()))
|
|
{
|
|
auto uMassiveWord = rngBytes[uIndex];
|
|
AuUInt32 uNext {};
|
|
while ((uNext = (uMassiveWord & (uUpperBound - 1))) > uRange)
|
|
{
|
|
uMassiveWord = AuFnv1a32Runtime(&uMassiveWord, sizeof(uMassiveWord));
|
|
}
|
|
|
|
ret[uIndex] = iMin + uNext;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
AuList<AuUInt32> RandomDevice::NextArrayU32Range(AuUInt32 uCount, AuUInt32 uMin, AuUInt32 uMax)
|
|
{
|
|
AuList<AuUInt32> ret;
|
|
AuList<AuUInt32> rngBytes;
|
|
|
|
ret.resize(uCount);
|
|
rngBytes.resize(uCount);
|
|
this->Read(rngBytes);
|
|
|
|
auto uRange = uMax - uMin;
|
|
auto uUpperBound = AuRoundUpPow2(uRange + 1);
|
|
|
|
for (AU_ITERATE_N(uIndex, rngBytes.size()))
|
|
{
|
|
auto uMassiveWord = rngBytes[uIndex];
|
|
AuUInt32 uNext {};
|
|
while ((uNext = (uMassiveWord & (uUpperBound - 1))) > uRange)
|
|
{
|
|
uMassiveWord = AuFnv1a32Runtime(&uMassiveWord, sizeof(uMassiveWord));
|
|
}
|
|
|
|
ret[uIndex] = uMin + uNext;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
AuList<double> RandomDevice::NextArrayDoubleRange(AuUInt32 uCount, double dMin, double dMax)
|
|
{
|
|
#if defined(AURNG_USE_FAST_DECIMALS)
|
|
AuList<double> ret;
|
|
AuList<AuUInt64> rngBytes;
|
|
|
|
ret.resize(uCount);
|
|
rngBytes.resize(uCount);
|
|
this->Read(rngBytes);
|
|
|
|
double dRange = dMax - dMin;
|
|
|
|
for (AU_ITERATE_N(uIndex, rngBytes.size()))
|
|
{
|
|
double dValue = RngConvertToDecimal(rngBytes[uIndex]);
|
|
dValue *= dRange;
|
|
dValue += dMin;
|
|
ret[uIndex] = dValue;
|
|
}
|
|
|
|
return ret;
|
|
#else
|
|
AuList<double> ret;
|
|
ret.resize(uCount);
|
|
|
|
for (AU_ITERATE_N(uIndex, uCount))
|
|
{
|
|
ret[uIndex] = this->NextNumber(dMin, dMax);
|
|
}
|
|
|
|
return ret;
|
|
#endif
|
|
}
|
|
|
|
AuList<AuInt32> RandomDevice::NextArrayI32(AuUInt32 uCount)
|
|
{
|
|
AuList<AuInt32> ret;
|
|
ret.resize(uCount);
|
|
this->Read(ret);
|
|
return ret;
|
|
}
|
|
|
|
AuList<AuUInt32> RandomDevice::NextArrayU32(AuUInt32 uCount)
|
|
{
|
|
AuList<AuUInt32> ret;
|
|
ret.resize(uCount);
|
|
this->Read(ret);
|
|
return ret;
|
|
}
|
|
|
|
AuList<double> RandomDevice::NextArrayDouble(AuUInt32 uCount)
|
|
{
|
|
AuList<double> ret;
|
|
ret.resize(uCount);
|
|
this->Read(ret);
|
|
return ret;
|
|
}
|
|
|
|
AuList<double> RandomDevice::NextArrayDecimals(AuUInt32 uCount)
|
|
{
|
|
#if defined(AURNG_USE_FAST_DECIMALS)
|
|
AuList<double> ret;
|
|
AuList<AuUInt64> rngBytes;
|
|
|
|
ret.resize(uCount);
|
|
rngBytes.resize(uCount);
|
|
this->Read(rngBytes);
|
|
|
|
for (AU_ITERATE_N(uIndex, rngBytes.size()))
|
|
{
|
|
ret[uIndex] = RngConvertToDecimal(rngBytes[uIndex]);
|
|
}
|
|
|
|
return ret;
|
|
#else
|
|
AuList<double> ret;
|
|
ret.resize(uCount);
|
|
|
|
for (AU_ITERATE_N(uIndex, uCount))
|
|
{
|
|
ret[uIndex] = this->NextDecimal();
|
|
}
|
|
|
|
return ret;
|
|
#endif
|
|
}
|
|
|
|
double RandomDevice::NextDecimal()
|
|
{
|
|
#if defined(AURNG_USE_FAST_DECIMALS)
|
|
return RngConvertToDecimal(this->NextU64());
|
|
#elif defined(AURNG_USE_UNIFORM_DECIMALS)
|
|
return this->UniformFloatInRange(kDblEpsilon, 1.0 + kDblEpsilon) - kDblEpsilon;
|
|
#elif defined(AURNG_USE_GARBAGE_DECIMALS)
|
|
return this->UniformFloatInRange(0, 1.0);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
AuUInt32 RandomDevice::NextIndex(AuUInt32 uCount /* = max + 1*/)
|
|
{
|
|
auto uMassiveWord = NextU32();
|
|
auto uUpperBound = AuRoundUpPow2(uCount);
|
|
AuUInt32 uNext {};
|
|
while ((uNext = (uMassiveWord & (uUpperBound - 1))) >= uCount)
|
|
{
|
|
uMassiveWord = AuFnv1a32Runtime(&uMassiveWord, sizeof(uMassiveWord));
|
|
}
|
|
return uNext;
|
|
}
|
|
|
|
double RandomDevice::NextNumber(double dMin, double dMax)
|
|
{
|
|
auto dRange = dMax - dMin;
|
|
#if defined(AURNG_USE_FAST_DECIMALS)
|
|
return (this->NextDecimal() * dRange) + dMin;
|
|
#else
|
|
// potentially not deterministic, thanks to runtime/platform deviations
|
|
return this->UniformFloatInRange(kDblEpsilon, dRange + kDblEpsilon) + dMin - kDblEpsilon;
|
|
#endif
|
|
}
|
|
|
|
AuList<uuids::uuid> RandomDevice::NextArrayUUIDs(AuUInt32 uCount)
|
|
{
|
|
AuList<uuids::uuid> ret;
|
|
AuList<AuUInt8> rngBytes;
|
|
|
|
ret.resize(uCount);
|
|
rngBytes.resize(uCount * 16);
|
|
this->Read(rngBytes);
|
|
|
|
for (AU_ITERATE_N(uIndex, uCount))
|
|
{
|
|
auto pBytes = rngBytes.data() + (uIndex * 16);
|
|
|
|
pBytes[8] &= 0xBF;
|
|
pBytes[8] |= 0x80;
|
|
|
|
pBytes[6] &= 0x4F;
|
|
pBytes[6] |= 0x40;
|
|
|
|
ret[uIndex] = uuids::uuid { pBytes, pBytes + 16 };
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
uuids::uuid RandomDevice::NextUUID()
|
|
{
|
|
AuUInt8 bytes[16];
|
|
this->Read(bytes);
|
|
|
|
bytes[8] &= 0xBF;
|
|
bytes[8] |= 0x80;
|
|
|
|
bytes[6] &= 0x4F;
|
|
bytes[6] |= 0x40;
|
|
|
|
return uuids::uuid { bytes, bytes + 16 };
|
|
}
|
|
|
|
AuMemoryViewRead RandomDevice::ToSeed()
|
|
{
|
|
if (this->def_.bSecure)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
return this->fast_.state;
|
|
}
|
|
|
|
IO::IStreamReader *RandomDevice::ToStreamReader()
|
|
{
|
|
return this;
|
|
}
|
|
|
|
AUKN_SYM IRandomDevice *RandomNew(const Aurora::RNG::RandomDef &def)
|
|
{
|
|
auto pDevice = _new RandomDevice();
|
|
if (!pDevice)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
pDevice->Init(def);
|
|
|
|
return pDevice;
|
|
}
|
|
|
|
AUKN_SYM void RandomRelease(IRandomDevice *pDevice)
|
|
{
|
|
AuSafeDelete<RandomDevice *>(pDevice);
|
|
}
|
|
|
|
AUROXTL_INTERFACE_SOO_SRC(Random, RandomDevice, (const RandomDef &, def))
|
|
} |