github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/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  	"fmt"
    21  	"io"
    22  
    23  	"github.com/metacubex/gvisor/pkg/binary"
    24  )
    25  
    26  // RNG exposes convenience functions based on a cryptographically secure
    27  // io.Reader.
    28  type RNG struct {
    29  	Reader io.Reader
    30  }
    31  
    32  // RNGFrom returns a new RNG. r must be a cryptographically secure io.Reader.
    33  func RNGFrom(r io.Reader) RNG {
    34  	return RNG{Reader: r}
    35  }
    36  
    37  // Uint16 is analogous to the standard library's math/rand.Uint16.
    38  func (rg *RNG) Uint16() uint16 {
    39  	var data [2]byte
    40  	if _, err := rg.Reader.Read(data[:]); err != nil {
    41  		panic(fmt.Sprintf("Read() failed: %v", err))
    42  	}
    43  	return binary.NativeEndian.Uint16(data[:])
    44  }
    45  
    46  // Uint32 is analogous to the standard library's math/rand.Uint32.
    47  func (rg *RNG) Uint32() uint32 {
    48  	var data [4]byte
    49  	if _, err := rg.Reader.Read(data[:]); err != nil {
    50  		panic(fmt.Sprintf("Read() failed: %v", err))
    51  	}
    52  	return binary.NativeEndian.Uint32(data[:])
    53  }
    54  
    55  // Int63n is analogous to the standard library's math/rand.Int63n.
    56  func (rg *RNG) Int63n(n int64) int64 {
    57  	// Based on Go's rand package implementation, but using
    58  	// cryptographically secure random numbers.
    59  	if n <= 0 {
    60  		panic(fmt.Sprintf("n must be positive, but got %d", n))
    61  	}
    62  
    63  	// This can be done quickly when n is a power of 2.
    64  	if n&(n-1) == 0 {
    65  		return int64(rg.Uint64()) & (n - 1)
    66  	}
    67  
    68  	// The naive approach would be to return rg.Int63()%n, but we need the
    69  	// random number to be fair. It shouldn't be biased towards certain
    70  	// results, but simple modular math can be very biased. For example, if
    71  	// n is 40% of the maximum int64, then the output values of rg.Int63
    72  	// map to return values as follows:
    73  	//
    74  	//  - The first 40% of values map to themselves.
    75  	//  - The second 40% map to themselves - maximum int64.
    76  	//  - The remaining 20% map to the themselves - 2 * (maximum int64),
    77  	//    i.e. the first half of possible output values.
    78  	//
    79  	// And thus 60% of results map the the first half of possible output
    80  	// values, and 40% map the second half. Oops!
    81  	//
    82  	// We use the same trick as Go to deal with this: shave off the last
    83  	// segment (the 20% in our example) to make the RNG more fair.
    84  	//
    85  	// In the worst case, n is just over half of maximum int64, meaning
    86  	// that the upper half of rg.Int63 return values are bad. So each call
    87  	// to rg.Int63 has, at worst, a 50% chance of needing a retry.
    88  	maximum := int64((1 << 63) - 1 - (1<<63)%uint64(n))
    89  	ret := rg.Int63()
    90  	for ret > maximum {
    91  		ret = rg.Int63()
    92  	}
    93  	return ret % n
    94  }
    95  
    96  // Int63 is analogous to the standard library's math/rand.Int63.
    97  func (rg *RNG) Int63() int64 {
    98  	return ((1 << 63) - 1) & int64(rg.Uint64())
    99  }
   100  
   101  // Uint64 is analogous to the standard library's math/rand.Uint64.
   102  func (rg *RNG) Uint64() uint64 {
   103  	var data [8]byte
   104  	if _, err := rg.Reader.Read(data[:]); err != nil {
   105  		panic(fmt.Sprintf("Read() failed: %v", err))
   106  	}
   107  	return binary.NativeEndian.Uint64(data[:])
   108  }
   109  
   110  // Uint32 is analogous to the standard library's math/rand.Uint32.
   111  func Uint32() uint32 {
   112  	rng := RNG{Reader: Reader}
   113  	return rng.Uint32()
   114  }
   115  
   116  // Int63n is analogous to the standard library's math/rand.Int63n.
   117  func Int63n(n int64) int64 {
   118  	rng := RNG{Reader: Reader}
   119  	return rng.Int63n(n)
   120  }
   121  
   122  // Int63 is analogous to the standard library's math/rand.Int63.
   123  func Int63() int64 {
   124  	rng := RNG{Reader: Reader}
   125  	return rng.Int63()
   126  }
   127  
   128  // Uint64 is analogous to the standard library's math/rand.Uint64.
   129  func Uint64() uint64 {
   130  	rng := RNG{Reader: Reader}
   131  	return rng.Uint64()
   132  }