common/entropy/entropy.go

183 lines
4.5 KiB
Go

package entropy
import (
crip "crypto/rand"
"encoding/binary"
"math/rand"
"sync"
"time"
"nullprogram.com/x/rng"
"git.tcp.direct/kayos/common/pool"
)
type randPool struct {
sync.Pool
}
func (p *randPool) Get() *rand.Rand {
return p.Pool.Get().(*rand.Rand)
}
func (p *randPool) Put(r *rand.Rand) {
p.Pool.Put(r)
}
var (
lolXD = randPool{
Pool: sync.Pool{
New: func() interface{} {
sm64 := new(rng.SplitMix64)
sm64.Seed(GetCryptoSeed())
prng := rand.New(sm64) //nolint:gosec
return prng
},
},
}
hardLocc = &sync.Mutex{}
sharedRand *rand.Rand
getSharedRand = &sync.Once{}
)
func setSharedRand() {
hardLocc.Lock()
sharedRand = lolXD.Get()
hardLocc.Unlock()
}
func AcquireRand() *rand.Rand {
return lolXD.Get()
}
func ReleaseRand(r *rand.Rand) {
lolXD.Put(r)
r = nil
}
// RandomStrChoice returns a random item from an input slice of strings.
func RandomStrChoice(choice []string) string {
if len(choice) > 0 {
return choice[RNGUint32()%uint32(len(choice))]
}
return ""
}
// GetCryptoSeed returns a random int64 derived from crypto/rand.
// This can be used as a seed for various PRNGs.
func GetCryptoSeed() int64 {
var seed int64
_ = binary.Read(crip.Reader, binary.BigEndian, &seed)
return seed
}
// GetOptimizedRand returns a pointer to a *new* rand.Rand which uses GetCryptoSeed to seed an rng.SplitMix64.
// Does not use the global/shared instance of a splitmix64 rng, but instead creates a new one.
func GetOptimizedRand() *rand.Rand {
r := new(rng.SplitMix64)
r.Seed(GetCryptoSeed())
return rand.New(r) //nolint:gosec
}
// GetSharedRand returns a pointer to our shared optimized rand.Rand which uses crypto/rand to seed a splitmix64 rng.
// WARNING - RACY - This is not thread safe, and should only be used in a single-threaded context.
func GetSharedRand() *rand.Rand {
getSharedRand.Do(func() {
setSharedRand()
})
return sharedRand
}
// RNGUint32 returns a random uint32 using crypto/rand and splitmix64.
func RNGUint32() uint32 {
r := lolXD.Get()
ui := r.Uint32()
lolXD.Put(r)
return ui
}
/*
RNG returns integer with a maximum amount of 'n' using a global/shared instance of a splitmix64 rng.
- Benchmark_FastRandStr5-24 25205089 47.03 ns/op
- Benchmark_FastRandStr25-24 7113620 169.8 ns/op
- Benchmark_FastRandStr55-24 3520297 340.7 ns/op
- Benchmark_FastRandStr500-24 414966 2837 ns/op
- Benchmark_FastRandStr55555-24 3717 315229 ns/op
*/
func RNG(n int) int {
r := lolXD.Get()
i := r.Intn(n)
lolXD.Put(r)
return i
}
// OneInA generates a random number with a maximum of 'million' (input int).
// If the resulting random number is equal to 1, then the result is true.
func OneInA(million int) bool {
if million == 1 {
return true
}
return RNG(million) == 1
}
// RandSleepMS sleeps for a random period of time with a maximum of n milliseconds.
func RandSleepMS(n int) {
time.Sleep(time.Duration(RNG(n)) * time.Millisecond)
}
// characters used for the gerneration of random strings.
const charset = "abcdefghijklmnopqrstuvwxyz1234567890"
const charsetWithUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"
var strBufs = pool.NewBufferFactory()
// RandStr generates a random alphanumeric string with a max length of size.
// Alpha charset used is a-z all lowercase.
func RandStr(size int) string {
return randStr(false, size)
}
// RandStrWithUpper generates a random alphanumeric string with a max length of size.
// Alpha charset used is a-Z mixed case.
func RandStrWithUpper(size int) string {
return randStr(true, size)
}
/*
randStr is an overoptimized (read: dummy fast) random string generator.
using byte buffers gives us a solid 2 alloc/op indefinitely.
using string builders gives us a linear increase in alloc/op, up to 22 alloc/op at 55,555 characters.
at 55,555 characters, we are at:
~57,000 bytes per op with byte builders
vs
~210,000 bytes per op with string builders.
this is felt significantly at ranges as low as 500 chars, where we get:
8 alloc/op and >1,000 bytes/op with string builders
vs
2 alloc/op and ~500 bytes/op with byte buffers.
*/
func randStr(upper bool, size int) string {
buf := strBufs.Get()
r := lolXD.Get()
for i := 0; i != size; i++ {
ui32 := int(r.Uint32())
switch upper {
case true:
_ = buf.WriteByte(charsetWithUpper[ui32%len(charsetWithUpper)])
case false:
_ = buf.WriteByte(charset[ui32%len(charset)])
}
}
lolXD.Put(r)
s := buf.String()
strBufs.MustPut(buf)
return s
}