github.com/safing/portbase@v0.19.5/rng/get.go (about)

     1  package rng
     2  
     3  import (
     4  	"encoding/binary"
     5  	"errors"
     6  	"io"
     7  	"math"
     8  	"time"
     9  )
    10  
    11  const (
    12  	reseedAfterSeconds = 600     // ten minutes
    13  	reseedAfterBytes   = 1048576 // one megabyte
    14  )
    15  
    16  var (
    17  	// Reader provides a global instance to read from the RNG.
    18  	Reader io.Reader
    19  
    20  	rngBytesRead uint64
    21  	rngLastFeed  = time.Now()
    22  )
    23  
    24  // reader provides an io.Reader interface.
    25  type reader struct{}
    26  
    27  func init() {
    28  	Reader = reader{}
    29  }
    30  
    31  func checkEntropy() (err error) {
    32  	if !rngReady {
    33  		return errors.New("RNG is not ready yet")
    34  	}
    35  	if rngBytesRead > reseedAfterBytes ||
    36  		int(time.Since(rngLastFeed).Seconds()) > reseedAfterSeconds {
    37  		select {
    38  		case r := <-rngFeeder:
    39  			rng.Reseed(r)
    40  			rngBytesRead = 0
    41  			rngLastFeed = time.Now()
    42  		case <-time.After(1 * time.Second):
    43  			return errors.New("failed to get new entropy")
    44  		}
    45  	}
    46  	return nil
    47  }
    48  
    49  // Read reads random bytes into the supplied byte slice.
    50  func Read(b []byte) (n int, err error) {
    51  	rngLock.Lock()
    52  	defer rngLock.Unlock()
    53  
    54  	if err := checkEntropy(); err != nil {
    55  		return 0, err
    56  	}
    57  
    58  	return copy(b, rng.PseudoRandomData(uint(len(b)))), nil
    59  }
    60  
    61  // Read implements the io.Reader interface.
    62  func (r reader) Read(b []byte) (n int, err error) {
    63  	return Read(b)
    64  }
    65  
    66  // Bytes allocates a new byte slice of given length and fills it with random data.
    67  func Bytes(n int) ([]byte, error) {
    68  	rngLock.Lock()
    69  	defer rngLock.Unlock()
    70  
    71  	if err := checkEntropy(); err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	return rng.PseudoRandomData(uint(n)), nil
    76  }
    77  
    78  // Number returns a random number from 0 to (incl.) max.
    79  func Number(max uint64) (uint64, error) {
    80  	secureLimit := math.MaxUint64 - (math.MaxUint64 % max)
    81  	max++
    82  
    83  	for {
    84  		randomBytes, err := Bytes(8)
    85  		if err != nil {
    86  			return 0, err
    87  		}
    88  
    89  		candidate := binary.LittleEndian.Uint64(randomBytes)
    90  		if candidate < secureLimit {
    91  			return candidate % max, nil
    92  		}
    93  	}
    94  }