current commit
260925.9 · crypto: use internal random buffer bounds
'use strict';

const common = require('../common');
if (!common.hasCrypto)
  common.skip('missing crypto');

const assert = require('assert');
const { randomFill, randomFillSync } = require('crypto');

const typedArrays = [
  Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array,
  Int32Array, Uint32Array, Float32Array, Float64Array,
  BigInt64Array, BigUint64Array,
];
if (globalThis.Float16Array !== undefined)
  typedArrays.push(globalThis.Float16Array);

const factories = [];
for (const Backing of [ArrayBuffer, SharedArrayBuffer]) {
  for (const Type of [...typedArrays, Buffer, DataView]) {
    const elementSize = Type.BYTES_PER_ELEMENT || 1;
    factories.push({
      name: `${Type.name} on ${Backing.name}`,
      elementSize,
      make(length = 64) {
        const backing = new Backing(length + 64);
        const input = Type === Buffer ? Buffer.from(backing, 32, length) :
          new Type(backing, 32, length / elementSize);
        return { input, backing, bytes: new Uint8Array(backing), start: 32, length };
      },
    });
  }
  factories.push({
    name: Backing.name,
    elementSize: 1,
    make(length = 64) {
      const input = new Backing(length);
      return { input, backing: input, bytes: new Uint8Array(input), start: 0, length };
    },
  });
}

function shadowMetadata(target, mode) {
  const values = {
    byteLength: mode === 'zero' ? 0 : 4096,
    length: mode === 'zero' ? 0 : 4096,
    BYTES_PER_ELEMENT: mode === 'zero' ? 0 : 4096,
    buffer: new ArrayBuffer(0),
    byteOffset: mode === 'zero' ? 0 : 4096,
    [Symbol.toStringTag]: 'NotAnArrayBufferView',
  };
  for (const key of Reflect.ownKeys(values)) {
    Object.defineProperty(target, key, mode === 'getters' ?
      { get: common.mustNotCall(`Unexpected ${String(key)} getter`) } :
      { value: values[key] });
  }
}

function checkFilled({ input, bytes, start }, result, offset, size, label) {
  assert.strictEqual(result, input, label);
  assert(bytes.subarray(0, start + offset).every((byte) => byte === 0), label);
  assert(bytes.subarray(start + offset + size).every((byte) => byte === 0), label);
  // Every nonempty test range contains at least 32 random bytes. Detect a
  // no-op without assuming that any particular generated byte is nonzero.
  if (size !== 0)
    assert(bytes.subarray(start + offset, start + offset + size).some((byte) => byte !== 0), label);
}

for (const factory of factories) {
  const { elementSize } = factory;
  const ranges = [
    { args: [], offset: 0, size: 64 },
    { args: [16 / elementSize], offset: 16, size: 48 },
    { args: [16 / elementSize, 32 / elementSize], offset: 16, size: 32 },
    { args: [16 / elementSize, undefined], offset: 16, size: 48 },
    { args: [64 / elementSize], offset: 64, size: 0 },
  ];
  for (const mode of ['getters', 'zero', 'inflated']) {
    for (const { args, offset, size } of ranges) {
      for (const async of [false, true]) {
        const value = factory.make();
        shadowMetadata(value.input, mode);
        if (value.backing !== value.input)
          shadowMetadata(value.backing, mode);
        const label = `${factory.name}, ${mode}, ${async ? 'async' : 'sync'}, ${args}`;
        if (async) {
          assert.strictEqual(randomFill(value.input, ...args, common.mustSucceed((result) => {
            checkFilled(value, result, offset, size, label);
          })), undefined);
        } else {
          checkFilled(value, randomFillSync(value.input, ...args), offset, size, label);
        }
      }
    }
  }

  // Inflated public bounds must not let an invalid range reach the native job.
  // Keep the real element width here so each range exceeds the actual buffer.
  const units = 64 / elementSize;
  for (const args of [[units + 1], [0, units + 1], [units, 1]]) {
    const { input } = factory.make();
    Object.defineProperties(input, {
      byteLength: { value: 4096 },
      length: { value: 4096 },
    });
    assert.throws(() => randomFillSync(input, ...args), { code: 'ERR_OUT_OF_RANGE' });
    assert.throws(() => randomFill(input, ...args, common.mustNotCall()), {
      code: 'ERR_OUT_OF_RANGE',
    });
  }

  for (const args of [[], [0], [0, 0], [0, undefined]]) {
    const value = factory.make(0);
    shadowMetadata(value.input, 'inflated');
    checkFilled(value, randomFillSync(value.input, ...args), 0, 0, factory.name);
    assert.strictEqual(randomFill(value.input, ...args, common.mustSucceed((result) => {
      checkFilled(value, result, 0, 0, factory.name);
    })), undefined);
  }
}