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 }