/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: AuRandomDevice.cpp Date: 2021-9-3 Author: Reece ***/ #include #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(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 rngSequence[static_cast(ERngStringCharacters::eEnumCount)] = { {"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", 52}, {"abcdefghijklmnopqrstuvwxyz", 26}, {"ABCDEFGHIJKLMNOPQRSTUVWXYZ", 26}, {"1234567890", 10}, {"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", 62}, {"abcdefghijklmnopqrstuvwxyz1234567890", 36}, {"ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", 36}, {"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(type)]; for (auto i = 0u; i < uLength; i++) { auto uUpperBound = AuRoundUpPow2(pair.second); AuUInt8 uNext {}; auto uWord = reinterpret_cast(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(); } } 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::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 RandomDevice::NextArrayI32Range(AuUInt32 uCount, AuInt32 iMin, AuInt32 iMax) { AuList ret; AuList 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 RandomDevice::NextArrayU32Range(AuUInt32 uCount, AuUInt32 uMin, AuUInt32 uMax) { AuList ret; AuList 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 RandomDevice::NextArrayDoubleRange(AuUInt32 uCount, double dMin, double dMax) { #if defined(AURNG_USE_FAST_DECIMALS) AuList ret; AuList 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 ret; ret.resize(uCount); for (AU_ITERATE_N(uIndex, uCount)) { ret[uIndex] = this->NextNumber(dMin, dMax); } return ret; #endif } AuList RandomDevice::NextArrayI32(AuUInt32 uCount) { AuList ret; ret.resize(uCount); this->Read(ret); return ret; } AuList RandomDevice::NextArrayU32(AuUInt32 uCount) { AuList ret; ret.resize(uCount); this->Read(ret); return ret; } AuList RandomDevice::NextArrayDouble(AuUInt32 uCount) { AuList ret; ret.resize(uCount); this->Read(ret); return ret; } AuList RandomDevice::NextArrayDecimals(AuUInt32 uCount) { #if defined(AURNG_USE_FAST_DECIMALS) AuList ret; AuList 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 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 RandomDevice::NextArrayUUIDs(AuUInt32 uCount) { AuList ret; AuList 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(pDevice); } AUROXTL_INTERFACE_SOO_SRC(Random, RandomDevice, (const RandomDef &, def)) }