• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  MathMin,
5  NumberIsNaN,
6  NumberIsSafeInteger
7} = primordials;
8
9const { AsyncWrap, Providers } = internalBinding('async_wrap');
10const {
11  Buffer,
12  kMaxLength,
13} = require('buffer');
14const {
15  randomBytes: _randomBytes,
16  secureBuffer,
17} = internalBinding('crypto');
18const {
19  codes: {
20    ERR_INVALID_ARG_TYPE,
21    ERR_INVALID_CALLBACK,
22    ERR_OUT_OF_RANGE,
23    ERR_OPERATION_FAILED,
24  }
25} = require('internal/errors');
26const {
27  validateBoolean,
28  validateNumber,
29  validateObject,
30} = require('internal/validators');
31const { isArrayBufferView } = require('internal/util/types');
32const { FastBuffer } = require('internal/buffer');
33
34const kMaxInt32 = 2 ** 31 - 1;
35const kMaxPossibleLength = MathMin(kMaxLength, kMaxInt32);
36
37function assertOffset(offset, elementSize, length) {
38  validateNumber(offset, 'offset');
39  offset *= elementSize;
40
41  const maxLength = MathMin(length, kMaxPossibleLength);
42  if (NumberIsNaN(offset) || offset > maxLength || offset < 0) {
43    throw new ERR_OUT_OF_RANGE('offset', `>= 0 && <= ${maxLength}`, offset);
44  }
45
46  return offset >>> 0;  // Convert to uint32.
47}
48
49function assertSize(size, elementSize, offset, length) {
50  validateNumber(size, 'size');
51  size *= elementSize;
52
53  if (NumberIsNaN(size) || size > kMaxPossibleLength || size < 0) {
54    throw new ERR_OUT_OF_RANGE('size',
55                               `>= 0 && <= ${kMaxPossibleLength}`, size);
56  }
57
58  if (size + offset > length) {
59    throw new ERR_OUT_OF_RANGE('size + offset', `<= ${length}`, size + offset);
60  }
61
62  return size >>> 0;  // Convert to uint32.
63}
64
65function randomBytes(size, callback) {
66  size = assertSize(size, 1, 0, Infinity);
67  if (callback !== undefined && typeof callback !== 'function')
68    throw new ERR_INVALID_CALLBACK(callback);
69
70  const buf = new FastBuffer(size);
71
72  if (!callback) return handleError(_randomBytes(buf, 0, size), buf);
73
74  const wrap = new AsyncWrap(Providers.RANDOMBYTESREQUEST);
75  wrap.ondone = (ex) => {  // Retains buf while request is in flight.
76    if (ex) return callback.call(wrap, ex);
77    callback.call(wrap, null, buf);
78  };
79
80  _randomBytes(buf, 0, size, wrap);
81}
82
83function randomFillSync(buf, offset = 0, size) {
84  if (!isArrayBufferView(buf)) {
85    throw new ERR_INVALID_ARG_TYPE('buf', 'ArrayBufferView', buf);
86  }
87
88  const elementSize = buf.BYTES_PER_ELEMENT || 1;
89
90  offset = assertOffset(offset, elementSize, buf.byteLength);
91
92  if (size === undefined) {
93    size = buf.byteLength - offset;
94  } else {
95    size = assertSize(size, elementSize, offset, buf.byteLength);
96  }
97
98  return handleError(_randomBytes(buf, offset, size), buf);
99}
100
101function randomFill(buf, offset, size, callback) {
102  if (!isArrayBufferView(buf)) {
103    throw new ERR_INVALID_ARG_TYPE('buf', 'ArrayBufferView', buf);
104  }
105
106  const elementSize = buf.BYTES_PER_ELEMENT || 1;
107
108  if (typeof offset === 'function') {
109    callback = offset;
110    offset = 0;
111    size = buf.bytesLength;
112  } else if (typeof size === 'function') {
113    callback = size;
114    size = buf.byteLength - offset;
115  } else if (typeof callback !== 'function') {
116    throw new ERR_INVALID_CALLBACK(callback);
117  }
118
119  offset = assertOffset(offset, elementSize, buf.byteLength);
120
121  if (size === undefined) {
122    size = buf.byteLength - offset;
123  } else {
124    size = assertSize(size, elementSize, offset, buf.byteLength);
125  }
126
127  const wrap = new AsyncWrap(Providers.RANDOMBYTESREQUEST);
128  wrap.ondone = (ex) => {  // Retains buf while request is in flight.
129    if (ex) return callback.call(wrap, ex);
130    callback.call(wrap, null, buf);
131  };
132
133  _randomBytes(buf, offset, size, wrap);
134}
135
136// Largest integer we can read from a buffer.
137// e.g.: Buffer.from("ff".repeat(6), "hex").readUIntBE(0, 6);
138const RAND_MAX = 0xFFFF_FFFF_FFFF;
139
140// Generates an integer in [min, max) range where min is inclusive and max is
141// exclusive.
142function randomInt(min, max, callback) {
143  // Detect optional min syntax
144  // randomInt(max)
145  // randomInt(max, callback)
146  const minNotSpecified = typeof max === 'undefined' ||
147    typeof max === 'function';
148
149  if (minNotSpecified) {
150    callback = max;
151    max = min;
152    min = 0;
153  }
154
155  const isSync = typeof callback === 'undefined';
156  if (!isSync && typeof callback !== 'function') {
157    throw new ERR_INVALID_CALLBACK(callback);
158  }
159  if (!NumberIsSafeInteger(min)) {
160    throw new ERR_INVALID_ARG_TYPE('min', 'a safe integer', min);
161  }
162  if (!NumberIsSafeInteger(max)) {
163    throw new ERR_INVALID_ARG_TYPE('max', 'a safe integer', max);
164  }
165  if (max <= min) {
166    throw new ERR_OUT_OF_RANGE(
167      'max', `greater than the value of "min" (${min})`, max
168    );
169  }
170
171  // First we generate a random int between [0..range)
172  const range = max - min;
173
174  if (!(range <= RAND_MAX)) {
175    throw new ERR_OUT_OF_RANGE(`max${minNotSpecified ? '' : ' - min'}`,
176                               `<= ${RAND_MAX}`, range);
177  }
178
179  // For (x % range) to produce an unbiased value greater than or equal to 0 and
180  // less than range, x must be drawn randomly from the set of integers greater
181  // than or equal to 0 and less than randLimit.
182  const randLimit = RAND_MAX - (RAND_MAX % range);
183
184  if (isSync) {
185    // Sync API
186    while (true) {
187      const x = randomBytes(6).readUIntBE(0, 6);
188      if (x >= randLimit) {
189        // Try again.
190        continue;
191      }
192      return (x % range) + min;
193    }
194  } else {
195    // Async API
196    const pickAttempt = () => {
197      randomBytes(6, (err, bytes) => {
198        if (err) return callback(err);
199        const x = bytes.readUIntBE(0, 6);
200        if (x >= randLimit) {
201          // Try again.
202          return pickAttempt();
203        }
204        const n = (x % range) + min;
205        callback(null, n);
206      });
207    };
208
209    pickAttempt();
210  }
211}
212
213function handleError(ex, buf) {
214  if (ex) throw ex;
215  return buf;
216}
217
218// Implements an RFC 4122 version 4 random UUID.
219// To improve performance, random data is generated in batches
220// large enough to cover kBatchSize UUID's at a time. The uuidData
221// and uuid buffers are reused. Each call to randomUUID() consumes
222// 16 bytes from the buffer.
223
224const kHexDigits = [
225  48, 49, 50, 51, 52, 53, 54, 55,
226  56, 57, 97, 98, 99, 100, 101, 102,
227];
228
229const kBatchSize = 128;
230let uuidData;
231let uuidNotBuffered;
232let uuid;
233let uuidBatch = 0;
234
235function getBufferedUUID() {
236  if (uuidData === undefined) {
237    uuidData = secureBuffer(16 * kBatchSize);
238    if (uuidData === undefined)
239      throw new ERR_OPERATION_FAILED('Out of memory');
240  }
241
242  if (uuidBatch === 0) randomFillSync(uuidData);
243  uuidBatch = (uuidBatch + 1) % kBatchSize;
244  return uuidData.slice(uuidBatch * 16, (uuidBatch * 16) + 16);
245}
246
247function randomUUID(options) {
248  if (options !== undefined)
249    validateObject(options, 'options');
250  const {
251    disableEntropyCache = false,
252  } = { ...options };
253
254  validateBoolean(disableEntropyCache, 'options.disableEntropyCache');
255
256  if (uuid === undefined) {
257    uuid = Buffer.alloc(36, '-');
258    uuid[14] = 52; // '4', identifies the UUID version
259  }
260
261  let uuidBuf;
262  if (!disableEntropyCache) {
263    uuidBuf = getBufferedUUID();
264  } else {
265    uuidBuf = uuidNotBuffered;
266    if (uuidBuf === undefined)
267      uuidBuf = uuidNotBuffered = secureBuffer(16);
268    if (uuidBuf === undefined)
269      throw new ERR_OPERATION_FAILED('Out of memory');
270    randomFillSync(uuidBuf);
271  }
272
273  // Variant byte: 10xxxxxx (variant 1)
274  uuidBuf[8] = (uuidBuf[8] & 0x3f) | 0x80;
275
276  // This function is structured the way it is for performance.
277  // The uuid buffer stores the serialization of the random
278  // bytes from uuidData.
279  // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
280  let n = 0;
281  uuid[0] = kHexDigits[uuidBuf[n] >> 4];
282  uuid[1] = kHexDigits[uuidBuf[n++] & 0xf];
283  uuid[2] = kHexDigits[uuidBuf[n] >> 4];
284  uuid[3] = kHexDigits[uuidBuf[n++] & 0xf];
285  uuid[4] = kHexDigits[uuidBuf[n] >> 4];
286  uuid[5] = kHexDigits[uuidBuf[n++] & 0xf];
287  uuid[6] = kHexDigits[uuidBuf[n] >> 4];
288  uuid[7] = kHexDigits[uuidBuf[n++] & 0xf];
289  // -
290  uuid[9] = kHexDigits[uuidBuf[n] >> 4];
291  uuid[10] = kHexDigits[uuidBuf[n++] & 0xf];
292  uuid[11] = kHexDigits[uuidBuf[n] >> 4];
293  uuid[12] = kHexDigits[uuidBuf[n++] & 0xf];
294  // -
295  // 4, uuid[14] is set already...
296  uuid[15] = kHexDigits[uuidBuf[n++] & 0xf];
297  uuid[16] = kHexDigits[uuidBuf[n] >> 4];
298  uuid[17] = kHexDigits[uuidBuf[n++] & 0xf];
299  // -
300  uuid[19] = kHexDigits[uuidBuf[n] >> 4];
301  uuid[20] = kHexDigits[uuidBuf[n++] & 0xf];
302  uuid[21] = kHexDigits[uuidBuf[n] >> 4];
303  uuid[22] = kHexDigits[uuidBuf[n++] & 0xf];
304  // -
305  uuid[24] = kHexDigits[uuidBuf[n] >> 4];
306  uuid[25] = kHexDigits[uuidBuf[n++] & 0xf];
307  uuid[26] = kHexDigits[uuidBuf[n] >> 4];
308  uuid[27] = kHexDigits[uuidBuf[n++] & 0xf];
309  uuid[28] = kHexDigits[uuidBuf[n] >> 4];
310  uuid[29] = kHexDigits[uuidBuf[n++] & 0xf];
311  uuid[30] = kHexDigits[uuidBuf[n] >> 4];
312  uuid[31] = kHexDigits[uuidBuf[n++] & 0xf];
313  uuid[32] = kHexDigits[uuidBuf[n] >> 4];
314  uuid[33] = kHexDigits[uuidBuf[n++] & 0xf];
315  uuid[34] = kHexDigits[uuidBuf[n] >> 4];
316  uuid[35] = kHexDigits[uuidBuf[n] & 0xf];
317
318  return uuid.latin1Slice(0, 36);
319}
320
321module.exports = {
322  randomBytes,
323  randomFill,
324  randomFillSync,
325  randomInt,
326  randomUUID,
327};
328