• 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 { 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