1'use strict'; 2 3const { 4 MathMin, 5 NumberIsNaN, 6 NumberIsSafeInteger 7} = primordials; 8 9const { AsyncWrap, Providers } = internalBinding('async_wrap'); 10const { kMaxLength } = require('buffer'); 11const { randomBytes: _randomBytes } = internalBinding('crypto'); 12const { 13 ERR_INVALID_ARG_TYPE, 14 ERR_INVALID_CALLBACK, 15 ERR_OUT_OF_RANGE 16} = require('internal/errors').codes; 17const { validateNumber } = require('internal/validators'); 18const { isArrayBufferView } = require('internal/util/types'); 19const { FastBuffer } = require('internal/buffer'); 20 21const kMaxUint32 = 2 ** 32 - 1; 22const kMaxPossibleLength = MathMin(kMaxLength, kMaxUint32); 23 24function assertOffset(offset, elementSize, length) { 25 validateNumber(offset, 'offset'); 26 offset *= elementSize; 27 28 const maxLength = MathMin(length, kMaxPossibleLength); 29 if (NumberIsNaN(offset) || offset > maxLength || offset < 0) { 30 throw new ERR_OUT_OF_RANGE('offset', `>= 0 && <= ${maxLength}`, offset); 31 } 32 33 return offset >>> 0; // Convert to uint32. 34} 35 36function assertSize(size, elementSize, offset, length) { 37 validateNumber(size, 'size'); 38 size *= elementSize; 39 40 if (NumberIsNaN(size) || size > kMaxPossibleLength || size < 0) { 41 throw new ERR_OUT_OF_RANGE('size', 42 `>= 0 && <= ${kMaxPossibleLength}`, size); 43 } 44 45 if (size + offset > length) { 46 throw new ERR_OUT_OF_RANGE('size + offset', `<= ${length}`, size + offset); 47 } 48 49 return size >>> 0; // Convert to uint32. 50} 51 52function randomBytes(size, callback) { 53 size = assertSize(size, 1, 0, Infinity); 54 if (callback !== undefined && typeof callback !== 'function') 55 throw new ERR_INVALID_CALLBACK(callback); 56 57 const buf = new FastBuffer(size); 58 59 if (!callback) return handleError(_randomBytes(buf, 0, size), buf); 60 61 const wrap = new AsyncWrap(Providers.RANDOMBYTESREQUEST); 62 wrap.ondone = (ex) => { // Retains buf while request is in flight. 63 if (ex) return callback.call(wrap, ex); 64 callback.call(wrap, null, buf); 65 }; 66 67 _randomBytes(buf, 0, size, wrap); 68} 69 70function randomFillSync(buf, offset = 0, size) { 71 if (!isArrayBufferView(buf)) { 72 throw new ERR_INVALID_ARG_TYPE('buf', 'ArrayBufferView', buf); 73 } 74 75 const elementSize = buf.BYTES_PER_ELEMENT || 1; 76 77 offset = assertOffset(offset, elementSize, buf.byteLength); 78 79 if (size === undefined) { 80 size = buf.byteLength - offset; 81 } else { 82 size = assertSize(size, elementSize, offset, buf.byteLength); 83 } 84 85 return handleError(_randomBytes(buf, offset, size), buf); 86} 87 88function randomFill(buf, offset, size, callback) { 89 if (!isArrayBufferView(buf)) { 90 throw new ERR_INVALID_ARG_TYPE('buf', 'ArrayBufferView', buf); 91 } 92 93 const elementSize = buf.BYTES_PER_ELEMENT || 1; 94 95 if (typeof offset === 'function') { 96 callback = offset; 97 offset = 0; 98 size = buf.bytesLength; 99 } else if (typeof size === 'function') { 100 callback = size; 101 size = buf.byteLength - offset; 102 } else if (typeof callback !== 'function') { 103 throw new ERR_INVALID_CALLBACK(callback); 104 } 105 106 offset = assertOffset(offset, elementSize, buf.byteLength); 107 108 if (size === undefined) { 109 size = buf.byteLength - offset; 110 } else { 111 size = assertSize(size, elementSize, offset, buf.byteLength); 112 } 113 114 const wrap = new AsyncWrap(Providers.RANDOMBYTESREQUEST); 115 wrap.ondone = (ex) => { // Retains buf while request is in flight. 116 if (ex) return callback.call(wrap, ex); 117 callback.call(wrap, null, buf); 118 }; 119 120 _randomBytes(buf, offset, size, wrap); 121} 122 123// Largest integer we can read from a buffer. 124// e.g.: Buffer.from("ff".repeat(6), "hex").readUIntBE(0, 6); 125const RAND_MAX = 0xFFFF_FFFF_FFFF; 126 127// Generates an integer in [min, max) range where min is inclusive and max is 128// exclusive. 129function randomInt(min, max, callback) { 130 // Detect optional min syntax 131 // randomInt(max) 132 // randomInt(max, callback) 133 const minNotSpecified = typeof max === 'undefined' || 134 typeof max === 'function'; 135 136 if (minNotSpecified) { 137 callback = max; 138 max = min; 139 min = 0; 140 } 141 142 const isSync = typeof callback === 'undefined'; 143 if (!isSync && typeof callback !== 'function') { 144 throw new ERR_INVALID_CALLBACK(callback); 145 } 146 if (!NumberIsSafeInteger(min)) { 147 throw new ERR_INVALID_ARG_TYPE('min', 'a safe integer', min); 148 } 149 if (!NumberIsSafeInteger(max)) { 150 throw new ERR_INVALID_ARG_TYPE('max', 'a safe integer', max); 151 } 152 if (max <= min) { 153 throw new ERR_OUT_OF_RANGE( 154 'max', `greater than the value of "min" (${min})`, max 155 ); 156 } 157 158 // First we generate a random int between [0..range) 159 const range = max - min; 160 161 if (!(range <= RAND_MAX)) { 162 throw new ERR_OUT_OF_RANGE(`max${minNotSpecified ? '' : ' - min'}`, 163 `<= ${RAND_MAX}`, range); 164 } 165 166 const excess = RAND_MAX % range; 167 const randLimit = RAND_MAX - excess; 168 169 if (isSync) { 170 // Sync API 171 while (true) { 172 const x = randomBytes(6).readUIntBE(0, 6); 173 // If x > (maxVal - (maxVal % range)), we will get "modulo bias" 174 if (x > randLimit) { 175 // Try again 176 continue; 177 } 178 const n = (x % range) + min; 179 return n; 180 } 181 } else { 182 // Async API 183 const pickAttempt = () => { 184 randomBytes(6, (err, bytes) => { 185 if (err) return callback(err); 186 const x = bytes.readUIntBE(0, 6); 187 // If x > (maxVal - (maxVal % range)), we will get "modulo bias" 188 if (x > randLimit) { 189 // Try again 190 return pickAttempt(); 191 } 192 const n = (x % range) + min; 193 callback(null, n); 194 }); 195 }; 196 197 pickAttempt(); 198 } 199} 200 201function handleError(ex, buf) { 202 if (ex) throw ex; 203 return buf; 204} 205 206module.exports = { 207 randomBytes, 208 randomFill, 209 randomFillSync, 210 randomInt 211}; 212