gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/rand/rng.go (about)

     1  // Copyright 2023 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package rand implements a cryptographically secure pseudorandom number
    16  // generator.
    17  package rand
    18  
    19  import (
    20  	"encoding/binary"
    21  	"fmt"
    22  	"io"
    23  )
    24  
    25  // RNG exposes convenience functions based on a cryptographically secure
    26  // io.Reader.
    27  type RNG struct {
    28  	Reader io.Reader
    29  }
    30  
    31  // RNGFrom returns a new RNG. r must be a cryptographically secure io.Reader.
    32  func RNGFrom(r io.Reader) RNG {
    33  	return RNG{Reader: r}
    34  }
    35  
    36  // Uint16 is analogous to the standard library's math/rand.Uint16.
    37  func (rg *RNG) Uint16() uint16 {
    38  	var data [2]byte
    39  	if _, err := rg.Reader.Read(data[:]); err != nil {
    40  		panic(fmt.Sprintf("Read() failed: %v", err))
    41  	}
    42  	return binary.NativeEndian.Uint16(data[:])
    43  }
    44  
    45  // Uint32 is analogous to the standard library's math/rand.Uint32.
    46  func (rg *RNG) Uint32() uint32 {
    47  	var data [4]byte
    48  	if _, err := rg.Reader.Read(data[:]); err != nil {
    49  		panic(fmt.Sprintf("Read() failed: %v", err))
    50  	}
    51  	return binary.NativeEndian.Uint32(data[:])
    52  }
    53  
    54  // Int63n is analogous to the standard library's math/rand.Int63n.
    55  func (rg *RNG) Int63n(n int64) int64 {
    56  	// Based on Go's rand package implementation, but using
    57  	// cryptographically secure random numbers.
    58  	if n <= 0 {
    59  		panic(fmt.Sprintf("n must be positive, but got %d", n))
    60  	}
    61  
    62  	// This can be done quickly when n is a power of 2.
    63  	if n&(n-1) == 0 {
    64  		return int64(rg.Uint64()) & (n - 1)
    65  	}
    66  
    67  	// The naive approach would be to return rg.Int63()%n, but we need the
    68  	// random number to be fair. It shouldn't be biased towards certain
    69  	// results, but simple modular math can be very biased. For example, if
    70  	// n is 40% of the maximum int64, then the output values of rg.Int63
    71  	// map to return values as follows:
    72  	//
    73  	//  - The first 40% of values map to themselves.
    74  	//  - The second 40% map to themselves - maximum int64.
    75  	//  - The remaining 20% map to the themselves - 2 * (maximum int64),
    76  	//    i.e. the first half of possible output values.
    77  	//
    78  	// And thus 60% of results map the first half of possible output
    79  	// values, and 40% map the second half. Oops!
    80  	//
    81  	// We use the same trick as Go to deal with this: shave off the last
    82  	// segment (the 20% in our example) to make the RNG more fair.
    83  	//
    84  	// In the worst case, n is just over half of maximum int64, meaning
    85  	// that the upper half of rg.Int63 return values are bad. So each call
    86  	// to rg.Int63 has, at worst, a 50% chance of needing a retry.
    87  	maximum := int64((1 << 63) - 1 - (1<<63)%uint64(n))
    88  	ret := rg.Int63()
    89  	for ret > maximum {
    90  		ret = rg.Int63()
    91  	}
    92  	return ret % n
    93  }
    94  
    95  // Int63 is analogous to the standard library's math/rand.Int63.
    96  func (rg *RNG) Int63() int64 {
    97  	return ((1 << 63) - 1) & int64(rg.Uint64())
    98  }
    99  
   100  // Uint64 is analogous to the standard library's math/rand.Uint64.
   101  func (rg *RNG) Uint64() uint64 {
   102  	var data [8]byte
   103  	if _, err := rg.Reader.Read(data[:]); err != nil {
   104  		panic(fmt.Sprintf("Read() failed: %v", err))
   105  	}
   106  	return binary.NativeEndian.Uint64(data[:])
   107  }
   108  
   109  // Uint32 is analogous to the standard library's math/rand.Uint32.
   110  func Uint32() uint32 {
   111  	rng := RNG{Reader: Reader}
   112  	return rng.Uint32()
   113  }
   114  
   115  // Int63n is analogous to the standard library's math/rand.Int63n.
   116  func Int63n(n int64) int64 {
   117  	rng := RNG{Reader: Reader}
   118  	return rng.Int63n(n)
   119  }
   120  
   121  // Int63 is analogous to the standard library's math/rand.Int63.
   122  func Int63() int64 {
   123  	rng := RNG{Reader: Reader}
   124  	return rng.Int63()
   125  }
   126  
   127  // Uint64 is analogous to the standard library's math/rand.Uint64.
   128  func Uint64() uint64 {
   129  	rng := RNG{Reader: Reader}
   130  	return rng.Uint64()
   131  }