diff --git a/bench/crypto/random.mjs b/bench/crypto/random.mjs new file mode 100644 index 0000000000..dc7e946c2d --- /dev/null +++ b/bench/crypto/random.mjs @@ -0,0 +1,50 @@ +import crypto from "crypto"; +import { bench, run } from "../runner.mjs"; + +bench("randomInt - sync", () => { + crypto.randomInt(1000); +}); + +bench("randomInt - async", async () => { + const { promise, resolve } = Promise.withResolvers(); + crypto.randomInt(1000, () => { + resolve(); + }); + await promise; +}); + +bench("randonBytes - 32", () => { + crypto.randomBytes(32); +}); + +bench("randomBytes - 256", () => { + crypto.randomBytes(256); +}); + +const buf = Buffer.alloc(256); + +bench("randomFill - 32", async () => { + const { promise, resolve } = Promise.withResolvers(); + crypto.randomFill(buf, 0, 32, () => { + resolve(); + }); + await promise; +}); + +bench("randomFill - 256", async () => { + const { promise, resolve } = Promise.withResolvers(); + crypto.randomFill(buf, 0, 256, () => { + resolve(); + }); + await promise; +}); + +bench("randomFillSync - 32", () => { + crypto.randomFillSync(buf, 0, 32); +}); + +bench("randomFillSync - 256", () => { + crypto.randomFillSync(buf, 0, 256); +}); + +await run(); diff --git a/src/bun.js/api/ffi.zig b/src/bun.js/api/ffi.zig index caae47b9c6..af2217dee1 100644 --- a/src/bun.js/api/ffi.zig +++ b/src/bun.js/api/ffi.zig @@ -1009,10 +1009,7 @@ pub const FFI = struct { pub fn open(global: *JSGlobalObject, name_str: ZigString, object: JSC.JSValue) JSC.JSValue { JSC.markBinding(@src()); const vm = VirtualMachine.get(); - var scope = bun.AllocationScope.init(bun.default_allocator); - defer scope.deinit(); - const allocator = scope.allocator(); - var name_slice = name_str.toSlice(allocator); + var name_slice = name_str.toSlice(bun.default_allocator); defer name_slice.deinit(); if (object.isEmptyOrUndefinedOrNull() or !object.isObject()) { @@ -1044,12 +1041,12 @@ pub const FFI = struct { } var symbols = bun.StringArrayHashMapUnmanaged(Function){}; - if (generateSymbols(global, allocator, &symbols, object) catch JSC.JSValue.zero) |val| { + if (generateSymbols(global, bun.default_allocator, &symbols, object) catch JSC.JSValue.zero) |val| { // an error while validating symbols for (symbols.keys()) |key| { - allocator.free(@constCast(key)); + bun.default_allocator.free(@constCast(key)); } - symbols.clearAndFree(allocator); + symbols.clearAndFree(bun.default_allocator); return val; } if (symbols.count() == 0) { @@ -1091,10 +1088,10 @@ pub const FFI = struct { const resolved_symbol = dylib.lookup(*anyopaque, function_name) orelse { const ret = JSC.toInvalidArguments("Symbol \"{s}\" not found in \"{s}\"", .{ bun.asByteSlice(function_name), name }, global); for (symbols.values()) |*value| { - allocator.free(@constCast(bun.asByteSlice(value.base_name.?))); - value.arg_types.clearAndFree(allocator); + bun.default_allocator.free(@constCast(bun.asByteSlice(value.base_name.?))); + value.arg_types.clearAndFree(bun.default_allocator); } - symbols.clearAndFree(allocator); + symbols.clearAndFree(bun.default_allocator); dylib.close(); return ret; }; @@ -1111,7 +1108,7 @@ pub const FFI = struct { for (symbols.values()) |*value| { value.deinit(global); } - symbols.clearAndFree(allocator); + symbols.clearAndFree(bun.default_allocator); dylib.close(); return ret; }; @@ -1122,7 +1119,7 @@ pub const FFI = struct { }; const res = ZigString.init(err.msg).toErrorInstance(global); - symbols.clearAndFree(allocator); + symbols.clearAndFree(bun.default_allocator); dylib.close(); return res; }, @@ -1130,7 +1127,7 @@ pub const FFI = struct { for (symbols.values()) |*other_function| { other_function.deinit(global); } - symbols.clearAndFree(allocator); + symbols.clearAndFree(bun.default_allocator); dylib.close(); return ZigString.init("Failed to compile (nothing happend!)").toErrorInstance(global); }, @@ -1151,11 +1148,10 @@ pub const FFI = struct { } } - var lib = allocator.create(FFI) catch unreachable; - lib.* = .{ + const lib = bun.new(FFI, .{ .dylib = dylib, .functions = symbols, - }; + }); const js_object = lib.toJS(global); JSC.Codegen.JSFFI.symbolsValueSetCached(js_object, global, obj); diff --git a/src/bun.js/base.zig b/src/bun.js/base.zig index 4440ef6a73..aa409fcc3b 100644 --- a/src/bun.js/base.zig +++ b/src/bun.js/base.zig @@ -16,6 +16,7 @@ const IdentityContext = @import("../identity_context.zig").IdentityContext; const uws = bun.uws; const TaggedPointerTypes = @import("../tagged_pointer.zig"); const TaggedPointerUnion = TaggedPointerTypes.TaggedPointerUnion; +const JSError = bun.JSError; pub const ExceptionValueRef = [*c]js.JSValueRef; pub const JSValueRef = js.JSValueRef; @@ -261,11 +262,26 @@ pub const ArrayBuffer = extern struct { value: JSC.JSValue = JSC.JSValue.zero, shared: bool = false, + // require('buffer').kMaxLength. + // keep in sync with Bun::Buffer::kMaxLength + pub const max_size = std.math.maxInt(c_uint); + extern fn JSBuffer__fromMmap(*JSC.JSGlobalObject, addr: *anyopaque, len: usize) JSC.JSValue; // 4 MB or so is pretty good for mmap() const mmap_threshold = 1024 * 1024 * 4; + pub fn bytesPerElement(this: *const ArrayBuffer) ?u8 { + return switch (this.typed_array_type) { + .ArrayBuffer, .DataView => null, + .Uint8Array, .Uint8ClampedArray, .Int8Array => 1, + .Uint16Array, .Int16Array, .Float16Array => 2, + .Uint32Array, .Int32Array, .Float32Array => 4, + .BigUint64Array, .BigInt64Array, .Float64Array => 8, + else => null, + }; + } + /// Only use this when reading from the file descriptor is _very_ cheap. Like, for example, an in-memory file descriptor. /// Do not use this for pipes, however tempting it may seem. pub fn toJSBufferFromFd(fd: bun.FileDescriptor, size: usize, globalObject: *JSC.JSGlobalObject) JSC.JSValue { @@ -410,13 +426,19 @@ pub const ArrayBuffer = extern struct { } extern "c" fn Bun__allocUint8ArrayForCopy(*JSC.JSGlobalObject, usize, **anyopaque) JSC.JSValue; - pub fn allocBuffer(globalThis: *JSC.JSGlobalObject, len: usize) struct { JSC.JSValue, []u8 } { + extern "c" fn Bun__allocArrayBufferForCopy(*JSC.JSGlobalObject, usize, **anyopaque) JSC.JSValue; + + pub fn alloc(global: *JSC.JSGlobalObject, comptime kind: JSC.JSValue.JSType, len: u32) JSError!struct { JSC.JSValue, []u8 } { var ptr: [*]u8 = undefined; - const buffer = Bun__allocUint8ArrayForCopy(globalThis, len, @ptrCast(&ptr)); - if (buffer.isEmpty()) { - return .{ buffer, &.{} }; + const buf = switch (comptime kind) { + .Uint8Array => Bun__allocUint8ArrayForCopy(global, len, @ptrCast(&ptr)), + .ArrayBuffer => Bun__allocArrayBufferForCopy(global, len, @ptrCast(&ptr)), + else => @compileError("Not implemented yet"), + }; + if (buf == .zero) { + return error.JSError; } - return .{ buffer, ptr[0..len] }; + return .{ buf, ptr[0..len] }; } extern "c" fn Bun__createUint8ArrayForCopy(*JSC.JSGlobalObject, ptr: ?*const anyopaque, len: usize, buffer: bool) JSC.JSValue; diff --git a/src/bun.js/bindings/ErrorCode.cpp b/src/bun.js/bindings/ErrorCode.cpp index f5ef1f73b5..31229149d6 100644 --- a/src/bun.js/bindings/ErrorCode.cpp +++ b/src/bun.js/bindings/ErrorCode.cpp @@ -399,7 +399,11 @@ void determineSpecificType(JSC::VM& vm, JSC::JSGlobalObject* globalObject, WTF:: StringView view = str; const bool needsEllipsis = jsString->length() > 28; - const bool needsEscape = str->contains('"'); + // node checks for the presence of a single quote. + // - if it does not exist, use single quotes. + // - if it exists, json stringify (use double quotes). + // https://github.com/nodejs/node/blob/c3ed292d17c34578fd7806cb42da82bbe0cca103/lib/internal/errors.js#L1030 + const bool needsEscape = str->contains('\''); if (needsEllipsis) { view = str->substring(0, 25); } @@ -426,13 +430,17 @@ void determineSpecificType(JSC::VM& vm, JSC::JSGlobalObject* globalObject, WTF:: } } } else { - builder.append('"'); + builder.append('\''); builder.append(view); } if (needsEllipsis) { builder.append("..."_s); } - builder.append('"'); + if (UNLIKELY(needsEscape)) { + builder.append('"'); + } else { + builder.append('\''); + } builder.append(')'); return; } diff --git a/src/bun.js/bindings/JSGlobalObject.zig b/src/bun.js/bindings/JSGlobalObject.zig index f85dd37125..b3f8001af8 100644 --- a/src/bun.js/bindings/JSGlobalObject.zig +++ b/src/bun.js/bindings/JSGlobalObject.zig @@ -126,6 +126,17 @@ pub const JSGlobalObject = opaque { return this.ERR_INVALID_ARG_TYPE("The \"{s}\" argument must be of type {s}. Received {}", .{ argname, typename, actual_string_value }).throw(); } + pub fn throwInvalidArgumentTypeValue2( + this: *JSGlobalObject, + argname: []const u8, + typename: []const u8, + value: JSValue, + ) JSError { + const actual_string_value = try determineSpecificType(this, value); + defer actual_string_value.deref(); + return this.ERR_INVALID_ARG_TYPE("The \"{s}\" argument must be {s}. Received {}", .{ argname, typename, actual_string_value }).throw(); + } + pub fn throwInvalidArgumentRangeValue( this: *JSGlobalObject, argname: []const u8, diff --git a/src/bun.js/bindings/JSValue.zig b/src/bun.js/bindings/JSValue.zig index 6822bd3195..06c2b7e79b 100644 --- a/src/bun.js/bindings/JSValue.zig +++ b/src/bun.js/bindings/JSValue.zig @@ -491,6 +491,18 @@ pub const JSValue = enum(i64) { return result; } + // https://tc39.es/ecma262/#sec-number.issafeinteger + pub fn isSafeInteger(this: JSValue) bool { + if (this.isInt32()) { + return true; + } + if (!this.isDouble()) { + return false; + } + const d = this.asDouble(); + return @trunc(d) == d and @abs(d) <= JSC.MAX_SAFE_INTEGER; + } + pub fn coerce(this: JSValue, comptime T: type, globalThis: *JSC.JSGlobalObject) T { return switch (T) { bool => this.toBoolean(), diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index dc429410d2..85f72b8807 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -1892,6 +1892,24 @@ extern "C" JSC__JSValue Bun__allocUint8ArrayForCopy(JSC::JSGlobalObject* globalO return JSValue::encode(array); } +extern "C" JSC__JSValue Bun__allocArrayBufferForCopy(JSC::JSGlobalObject* lexicalGlobalObject, size_t len, void** ptr) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + auto* subclassStructure = globalObject->JSBufferSubclassStructure(); + auto buf = JSC::JSUint8Array::createUninitialized(lexicalGlobalObject, subclassStructure, len); + + if (UNLIKELY(!buf)) { + return {}; + } + + *ptr = buf->vector(); + + return JSValue::encode(buf); +} + extern "C" JSC__JSValue Bun__createUint8ArrayForCopy(JSC::JSGlobalObject* globalObject, const void* ptr, size_t len, bool isBuffer) { VM& vm = globalObject->vm(); diff --git a/src/bun.js/node/node_crypto_binding.zig b/src/bun.js/node/node_crypto_binding.zig index 453ee16855..df9a40a5d4 100644 --- a/src/bun.js/node/node_crypto_binding.zig +++ b/src/bun.js/node/node_crypto_binding.zig @@ -17,56 +17,247 @@ const JSError = bun.JSError; const String = bun.String; const UUID = bun.UUID; -fn randomInt(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue { - const arguments = callframe.arguments_old(2).slice(); +const random = struct { + const max_possible_length = @min(JSC.ArrayBuffer.max_size, std.math.maxInt(i32)); + const max_range = 0xffff_ffff_ffff; - //min, max - if (!arguments[0].isNumber()) return globalThis.throwInvalidArgumentTypeValue("min", "safe integer", arguments[0]); - if (!arguments[1].isNumber()) return globalThis.throwInvalidArgumentTypeValue("max", "safe integer", arguments[1]); - const min = arguments[0].to(i64); - const max = arguments[1].to(i64); + fn randomInt(global: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSError!JSC.JSValue { + const args = callFrame.arguments(); - if (min > JSC.MAX_SAFE_INTEGER or min < JSC.MIN_SAFE_INTEGER) { - return globalThis.throwInvalidArgumentRangeValue("min", "It must be a safe integer type number", min); - } - if (max > JSC.MAX_SAFE_INTEGER) { - return globalThis.throwInvalidArgumentRangeValue("max", "It must be a safe integer type number", max); - } - if (min >= max) { - return globalThis.throwInvalidArgumentRangeValue("max", "should be greater than min", max); - } - const diff = max - min; - if (diff > 281474976710655) { - return globalThis.throwInvalidArgumentRangeValue("max - min", "It must be <= 281474976710655", diff); + const min_value, const max_value, const callback, const min_specified = args: { + if (args.len == 2) { + break :args .{ JSC.jsNumber(0), args[0], args[1], false }; + } + + break :args .{ args[0], args[1], args[2], true }; + }; + + if (!callback.isUndefined()) { + _ = try validators.validateFunction(global, "callback", callback); + } + + if (!min_value.isSafeInteger()) { + return global.throwInvalidArgumentTypeValue2("min", "a safe integer", min_value); + } + if (!max_value.isSafeInteger()) { + return global.throwInvalidArgumentTypeValue2("max", "a safe integer", max_value); + } + + const min: i64 = @intFromFloat(@trunc(min_value.asNumber())); + const max: i64 = @intFromFloat(@trunc(max_value.asNumber())); + + if (max <= min) { + return global.ERR_OUT_OF_RANGE("The value of \"max\" is out of range. It must be greater than the value of \"min\" ({d}). Received {d}", .{ + min, + max, + }).throw(); + } + + if (max - min > max_range) { + if (min_specified) { + return global.ERR_OUT_OF_RANGE("The value of \"max - min\" is out of range. It must be <= {d}. Received {d}", .{ max_range, max - min }).throw(); + } + return global.ERR_OUT_OF_RANGE("The value of \"max\" is out of range. It must be <= {d}. Received {d}", .{ max_range, max - min }).throw(); + } + + return JSC.jsNumber(std.crypto.random.intRangeLessThan(i64, min, max)); } - return JSC.JSValue.jsNumberFromInt64(std.crypto.random.intRangeLessThan(i64, min, max)); -} + fn randomUUID(global: *JSGlobalObject, callFrame: *JSC.CallFrame) JSError!JSValue { + const args = callFrame.arguments(); -fn randomUUID(global: *JSGlobalObject, callFrame: *JSC.CallFrame) JSError!JSValue { - const args = callFrame.arguments(); - - var disable_entropy_cache = false; - if (args.len > 0) { - const options = args[0]; - if (options != .undefined) { - try validators.validateObject(global, options, "options", .{}, .{}); - if (try options.get(global, "disableEntropyCache")) |disable_entropy_cache_value| { - disable_entropy_cache = try validators.validateBoolean(global, disable_entropy_cache_value, "options.disableEntropyCache", .{}); + var disable_entropy_cache = false; + if (args.len > 0) { + const options = args[0]; + if (options != .undefined) { + try validators.validateObject(global, options, "options", .{}, .{}); + if (try options.get(global, "disableEntropyCache")) |disable_entropy_cache_value| { + disable_entropy_cache = try validators.validateBoolean(global, disable_entropy_cache_value, "options.disableEntropyCache", .{}); + } } } + + var str, var bytes = String.createUninitialized(.latin1, 36); + + const uuid = if (disable_entropy_cache) + UUID.init() + else + global.bunVM().rareData().nextUUID(); + + uuid.print(bytes[0..36]); + return str.transferToJS(global); } - var str, var bytes = String.createUninitialized(.latin1, 36); + fn assertOffset(global: *JSGlobalObject, offset_value: JSValue, element_size: u8, length: usize) JSError!u32 { + if (!offset_value.isNumber()) { + return global.throwInvalidArgumentTypeValue("offset", "number", offset_value); + } + const offset = offset_value.asNumber() * @as(f32, @floatFromInt(element_size)); - const uuid = if (disable_entropy_cache) - UUID.init() - else - global.bunVM().rareData().nextUUID(); + const max_length = @min(length, max_possible_length); + if (std.math.isNan(offset) or offset > @as(f64, @floatFromInt(max_length)) or offset < 0) { + return global.throwRangeError(offset, .{ .field_name = "offset", .min = 0, .max = max_length }); + } - uuid.print(bytes[0..36]); - return str.transferToJS(global); -} + return @intFromFloat(offset); + } + fn assertSize(global: *JSGlobalObject, size_value: JSValue, element_size: u8, offset: u32, length: usize) JSError!u32 { + var size = try validators.validateNumber(global, size_value, "size", null, null); + size *= @as(f32, @floatFromInt(element_size)); + + if (std.math.isNan(size) or size > max_possible_length or size < 0) { + return global.throwRangeError(size, .{ .field_name = "size", .min = 0, .max = max_possible_length }); + } + + if (size + @as(f32, @floatFromInt(offset)) > @as(f64, @floatFromInt(length))) { + return global.throwRangeError(size + @as(f32, @floatFromInt(offset)), .{ .field_name = "size + offset", .max = @intCast(length) }); + } + + return @intFromFloat(size); + } + + pub const Job = struct { + vm: *JSC.VirtualMachine, + task: JSC.WorkPoolTask, + any_task: JSC.AnyTask, + + promise: JSC.JSPromise.Strong, + poll: bun.Async.KeepAlive = .{}, + + value: JSC.Strong, + bytes: [*]u8, + offset: u32, + length: usize, + + pub fn runTask(task: *JSC.WorkPoolTask) void { + const job: *Job = @fieldParentPtr("task", task); + defer job.vm.enqueueTaskConcurrent(JSC.ConcurrentTask.create(job.any_task.task())); + + bun.csprng(job.bytes[job.offset..][0..job.length]); + } + + pub fn runFromJS(this: *Job) void { + defer this.deinit(); + const vm = this.vm; + + if (vm.isShuttingDown()) { + return; + } + + const global = this.vm.global; + const promise = this.promise.swap(); + + promise.resolve(global, this.value.swap()); + } + + pub fn create(global: *JSGlobalObject, value: JSValue, bytes: [*]u8, offset: u32, length: usize) *Job { + const vm = global.bunVM(); + const job = bun.new(Job, .{ + .vm = vm, + .task = .{ + .callback = &Job.runTask, + }, + .any_task = undefined, + + .promise = JSC.JSPromise.Strong.init(global), + + .value = JSC.Strong.create(value, global), + .bytes = bytes, + .offset = offset, + .length = length, + }); + + job.any_task = JSC.AnyTask.New(Job, &Job.runFromJS).init(job); + job.poll.ref(vm); + JSC.WorkPool.schedule(&job.task); + return job; + } + + fn deinit(this: *Job) void { + this.poll.unref(this.vm); + bun.destroy(this); + } + }; + + fn randomBytes(global: *JSGlobalObject, callFrame: *JSC.CallFrame) JSError!JSValue { + const size_value, const callback = callFrame.argumentsAsArray(2); + + const size = try assertSize(global, size_value, 1, 0, max_possible_length + 1); + + if (!callback.isUndefined()) { + _ = try validators.validateFunction(global, "callback", callback); + } + + const result, const bytes = try JSC.ArrayBuffer.alloc(global, .ArrayBuffer, size); + + if (callback.isUndefined()) { + bun.csprng(bytes); + return result; + } + + const job = Job.create(global, result, bytes.ptr, 0, size); + + return job.promise.value(); + } + + fn randomFillSync(global: *JSGlobalObject, callFrame: *JSC.CallFrame) JSError!JSValue { + const buf_value, const offset_value, const size_value = callFrame.argumentsAsArray(3); + + const buf = buf_value.asArrayBuffer(global) orelse { + return global.throwInvalidArgumentTypeValue("buf", "ArrayBuffer or ArrayBufferView", buf_value); + }; + + const element_size = buf.bytesPerElement() orelse 1; + + const offset = try assertOffset( + global, + if (offset_value.isUndefined()) JSC.jsNumber(0) else offset_value, + element_size, + buf.byte_len, + ); + + const size = if (size_value.isUndefined()) + buf.byte_len - offset + else + try assertSize(global, size_value, element_size, offset, buf.byte_len); + + if (size == 0) { + return buf_value; + } + + bun.csprng(buf.slice()[offset..][0..size]); + + return buf_value; + } + + fn randomFill(global: *JSGlobalObject, callFrame: *JSC.CallFrame) JSError!JSValue { + const buf_value, const offset_value, const size_value, const callback = + callFrame.argumentsAsArray(4); + + const buf = buf_value.asArrayBuffer(global) orelse { + return global.throwInvalidArgumentTypeValue("buf", "ArrayBuffer or ArrayBufferView", buf_value); + }; + + const element_size = buf.bytesPerElement() orelse 1; + + _ = try validators.validateFunction(global, "callback", callback); + + const offset = try assertOffset(global, offset_value, element_size, buf.byte_len); + + const size = if (size_value.isUndefined()) + buf.byte_len - offset + else + try assertSize(global, size_value, element_size, offset, buf.byte_len); + + if (size == 0) { + return JSC.JSPromise.resolvedPromiseValue(global, callback); + } + + const job = Job.create(global, buf_value, buf.slice().ptr, offset, size); + + return job.promise.value(); + } +}; fn pbkdf2(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue { const arguments = callframe.arguments_old(6); @@ -123,12 +314,15 @@ pub fn timingSafeEqual(global: *JSGlobalObject, callFrame: *JSC.CallFrame) JSErr } pub fn createNodeCryptoBindingZig(global: *JSC.JSGlobalObject) JSC.JSValue { - const crypto = JSC.JSValue.createEmptyObject(global, 5); + const crypto = JSC.JSValue.createEmptyObject(global, 8); crypto.put(global, String.init("pbkdf2"), JSC.JSFunction.create(global, "pbkdf2", pbkdf2, 5, .{})); crypto.put(global, String.init("pbkdf2Sync"), JSC.JSFunction.create(global, "pbkdf2Sync", pbkdf2Sync, 5, .{})); - crypto.put(global, String.init("randomInt"), JSC.JSFunction.create(global, "randomInt", randomInt, 2, .{})); - crypto.put(global, String.init("randomUUID"), JSC.JSFunction.create(global, "randomUUID", randomUUID, 1, .{})); + crypto.put(global, String.init("randomInt"), JSC.JSFunction.create(global, "randomInt", random.randomInt, 2, .{})); + crypto.put(global, String.init("randomFill"), JSC.JSFunction.create(global, "randomFill", random.randomFill, 4, .{})); + crypto.put(global, String.init("randomFillSync"), JSC.JSFunction.create(global, "randomFillSync", random.randomFillSync, 3, .{})); + crypto.put(global, String.init("randomUUID"), JSC.JSFunction.create(global, "randomUUID", random.randomUUID, 1, .{})); + crypto.put(global, String.init("randomBytes"), JSC.JSFunction.create(global, "randomBytes", random.randomBytes, 2, .{})); crypto.put(global, String.init("timingSafeEqual"), JSC.JSFunction.create(global, "timingSafeEqual", timingSafeEqual, 2, .{})); return crypto; diff --git a/src/bun.js/node/node_zlib_binding.zig b/src/bun.js/node/node_zlib_binding.zig index 01b68e7e52..9163f3cc88 100644 --- a/src/bun.js/node/node_zlib_binding.zig +++ b/src/bun.js/node/node_zlib_binding.zig @@ -370,7 +370,7 @@ pub const SNativeZlib = struct { const strategy = try validators.validateInt32(globalThis, arguments[3], "strategy", .{}, null, null); // this does not get gc'd because it is stored in the JS object's `this._writeState`. and the JS object is tied to the native handle as `_handle[owner_symbol]`. const writeResult = arguments[4].asArrayBuffer(globalThis).?.asU32().ptr; - const writeCallback = try validators.validateFunction(globalThis, arguments[5], "writeCallback", .{}); + const writeCallback = try validators.validateFunction(globalThis, "writeCallback", arguments[5]); const dictionary = if (arguments[6].isUndefined()) null else arguments[6].asArrayBuffer(globalThis).?.byteSlice(); this.write_result = writeResult; @@ -738,7 +738,7 @@ pub const SNativeBrotli = struct { // this does not get gc'd because it is stored in the JS object's `this._writeState`. and the JS object is tied to the native handle as `_handle[owner_symbol]`. const writeResult = arguments[1].asArrayBuffer(globalThis).?.asU32().ptr; - const writeCallback = try validators.validateFunction(globalThis, arguments[2], "writeCallback", .{}); + const writeCallback = try validators.validateFunction(globalThis, "writeCallback", arguments[2]); this.write_result = writeResult; diff --git a/src/bun.js/node/util/validators.zig b/src/bun.js/node/util/validators.zig index 85d40370be..4e717ee9ed 100644 --- a/src/bun.js/node/util/validators.zig +++ b/src/bun.js/node/util/validators.zig @@ -150,28 +150,29 @@ pub fn validateString(globalThis: *JSGlobalObject, value: JSValue, comptime name return throwErrInvalidArgType(globalThis, name_fmt, name_args, "string", value); } -pub fn validateNumber(globalThis: *JSGlobalObject, value: JSValue, comptime name_fmt: string, name_args: anytype, min: ?f64, max: ?f64) bun.JSError!f64 { - if (!value.isNumber()) - return throwErrInvalidArgType(globalThis, name_fmt, name_args, "number", value); +pub fn validateNumber(globalThis: *JSGlobalObject, value: JSValue, name: string, maybe_min: ?f64, maybe_max: ?f64) bun.JSError!f64 { + if (!value.isNumber()) { + return globalThis.throwInvalidArgumentTypeValue(name, "number", value); + } const num: f64 = value.asNumber(); var valid = true; - if (min) |min_val| { - if (num < min_val) valid = false; + if (maybe_min) |min| { + if (num < min) valid = false; } - if (max) |max_val| { - if (num > max_val) valid = false; + if (maybe_max) |max| { + if (num > max) valid = false; } - if ((min != null or max != null) and std.math.isNan(num)) { + if ((maybe_min != null or maybe_max != null) and std.math.isNan(num)) { valid = false; } if (!valid) { - if (min != null and max != null) { - return throwRangeError(globalThis, "The value of \"" ++ name_fmt ++ "\" is out of range. It must be >= {d} and <= {d}. Received {s}", name_args ++ .{ min, max, value }); - } else if (min != null) { - return throwRangeError(globalThis, "The value of \"" ++ name_fmt ++ "\" is out of range. It must be >= {d}. Received {s}", name_args ++ .{ max, value }); - } else { - return throwRangeError(globalThis, "The value of \"" ++ name_fmt ++ "\" is out of range. It must be <= {d}. Received {s}", name_args ++ .{ max, value }); + if (maybe_min != null and maybe_max != null) { + return throwRangeError(globalThis, "The value of \"{s}\" is out of range. It must be >= {d} && <= {d}. Received {d}", .{ name, maybe_min.?, maybe_max.?, num }); + } else if (maybe_min != null) { + return throwRangeError(globalThis, "The value of \"{s}\" is out of range. It must be >= {d}. Received {d}", .{ name, maybe_min.?, num }); + } else if (maybe_max != null) { + return throwRangeError(globalThis, "The value of \"{s}\" is out of range. It must be <= {d}. Received {d}", .{ name, maybe_max.?, num }); } } return num; @@ -251,9 +252,10 @@ pub fn validateBooleanArray(globalThis: *JSGlobalObject, value: JSValue, comptim return i; } -pub fn validateFunction(globalThis: *JSGlobalObject, value: JSValue, comptime name_fmt: string, name_args: anytype) bun.JSError!JSValue { - if (!value.jsType().isFunction()) - return throwErrInvalidArgType(globalThis, name_fmt, name_args, "function", value); +pub fn validateFunction(global: *JSGlobalObject, name: string, value: JSValue) bun.JSError!JSValue { + if (!value.isFunction()) { + return global.throwInvalidArgumentTypeValue(name, "function", value); + } return value; } diff --git a/src/bun.js/rare_data.zig b/src/bun.js/rare_data.zig index 939d56d309..098d6fd349 100644 --- a/src/bun.js/rare_data.zig +++ b/src/bun.js/rare_data.zig @@ -245,7 +245,7 @@ pub const EntropyCache = struct { } pub fn fill(this: *EntropyCache) void { - bun.rand(&this.cache); + bun.csprng(&this.cache); this.index = 0; } @@ -479,7 +479,7 @@ pub fn s3DefaultClient(rare: *RareData, globalThis: *JSC.JSGlobalObject) JSC.JSV pub fn defaultCSRFSecret(this: *RareData) []const u8 { if (this.default_csrf_secret.len == 0) { const secret = bun.default_allocator.alloc(u8, 16) catch bun.outOfMemory(); - bun.rand(secret); + bun.csprng(secret); this.default_csrf_secret = secret; } return this.default_csrf_secret; diff --git a/src/bun.js/uuid.zig b/src/bun.js/uuid.zig index 428e12b5cc..8257cc7225 100644 --- a/src/bun.js/uuid.zig +++ b/src/bun.js/uuid.zig @@ -13,7 +13,7 @@ bytes: [16]u8, pub fn init() UUID { var uuid = UUID{ .bytes = undefined }; - bun.rand(&uuid.bytes); + bun.csprng(&uuid.bytes); // Version 4 uuid.bytes[6] = (uuid.bytes[6] & 0x0f) | 0x40; // Variant 1 diff --git a/src/bun.js/webcore.zig b/src/bun.js/webcore.zig index 2f50ebff9d..e8ac98ef6a 100644 --- a/src/bun.js/webcore.zig +++ b/src/bun.js/webcore.zig @@ -604,7 +604,7 @@ pub const Crypto = struct { bun.copy(u8, slice, globalThis.bunVM().rareData().entropySlice(slice.len)); }, else => { - bun.rand(slice); + bun.csprng(slice); }, } } diff --git a/src/bun.zig b/src/bun.zig index 0779b201ba..1b8aaf7459 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -587,7 +587,7 @@ pub fn fastRandom() u64 { return value; } } - rand(std.mem.asBytes(&value)); + csprng(std.mem.asBytes(&value)); seed_value.store(value, .monotonic); } @@ -620,7 +620,7 @@ pub fn hash32(content: []const u8) u32 { pub const HiveArray = @import("./hive_array.zig").HiveArray; -pub fn rand(bytes: []u8) void { +pub fn csprng(bytes: []u8) void { _ = BoringSSL.c.RAND_bytes(bytes.ptr, bytes.len); } diff --git a/src/cli/test_command.zig b/src/cli/test_command.zig index b272932078..47340e584a 100644 --- a/src/cli/test_command.zig +++ b/src/cli/test_command.zig @@ -842,7 +842,7 @@ pub const CommandLineReporter = struct { // Write the lcov.info file to a temporary file we atomically rename to the final name after it succeeds var base64_bytes: [8]u8 = undefined; var shortname_buf: [512]u8 = undefined; - bun.rand(&base64_bytes); + bun.csprng(&base64_bytes); const tmpname = std.fmt.bufPrintZ(&shortname_buf, ".lcov.info.{s}.tmp", .{std.fmt.fmtSliceHexLower(&base64_bytes)}) catch unreachable; const path = bun.path.joinAbsStringBufZ(relative_dir, &lcov_name_buf, &.{ opts.reports_directory, tmpname }, .auto); const file = bun.sys.File.openat( diff --git a/src/csrf.zig b/src/csrf.zig index bf6709cbd1..7c0046a0c9 100644 --- a/src/csrf.zig +++ b/src/csrf.zig @@ -79,7 +79,7 @@ pub fn generate( ) ![]u8 { // Generate nonce from entropy var nonce: [16]u8 = undefined; - bun.rand(&nonce); + bun.csprng(&nonce); // Current timestamp in milliseconds const timestamp = std.time.milliTimestamp(); diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig index de610fb03f..8cfbc1df16 100644 --- a/src/install/lockfile.zig +++ b/src/install/lockfile.zig @@ -2547,7 +2547,7 @@ pub fn saveToDisk(this: *Lockfile, load_result: *const LoadResult, options: *con var tmpname_buf: [512]u8 = undefined; var base64_bytes: [8]u8 = undefined; - bun.rand(&base64_bytes); + bun.csprng(&base64_bytes); const tmpname = if (save_format == .text) std.fmt.bufPrintZ(&tmpname_buf, ".lock-{s}.tmp", .{std.fmt.fmtSliceHexLower(&base64_bytes)}) catch unreachable else diff --git a/src/js/node/crypto.ts b/src/js/node/crypto.ts index 54b2be097e..0b651ef5c8 100644 --- a/src/js/node/crypto.ts +++ b/src/js/node/crypto.ts @@ -46,11 +46,14 @@ const { POINT_CONVERSION_COMPRESSED, POINT_CONVERSION_HYBRID, POINT_CONVERSION_U $processBindingConstants.crypto; const { - randomInt: _randomInt, pbkdf2: _pbkdf2, pbkdf2Sync: _pbkdf2Sync, timingSafeEqual: _timingSafeEqual, + randomInt: _randomInt, randomUUID: _randomUUID, + randomBytes: _randomBytes, + randomFillSync, + randomFill: _randomFill, } = $zig("node_crypto_binding.zig", "createNodeCryptoBindingZig"); const { validateObject, validateString, validateInt32 } = require("internal/validators"); @@ -102,18 +105,6 @@ function getCipherInfo(nameOrNid, options) { return ret; } -function randomInt(min, max, callback) { - if (max == null) { - max = min; - min = 0; - } - if (callback != null) { - process.nextTick(() => callback(null, _randomInt(min, max))); - return; - } - return _randomInt(min, max); -} - const MAX_STRING_LENGTH = 536870888; var Buffer = globalThis.Buffer; const EMPTY_BUFFER = Buffer.alloc(0); @@ -10094,61 +10085,6 @@ var require_browser9 = __commonJS({ }, }); -// node_modules/randomfill/browser.js -var require_browser11 = __commonJS({ - "node_modules/randomfill/browser.js"(exports) { - "use strict"; - var safeBuffer = require_safe_buffer(), - randombytes = require_browser(), - Buffer2 = safeBuffer.Buffer, - kBufferMaxLength = safeBuffer.kMaxLength, - kMaxUint32 = Math.pow(2, 32) - 1; - function assertOffset(offset, length) { - if (typeof offset != "number" || offset !== offset) throw new TypeError("offset must be a number"); - if (offset > kMaxUint32 || offset < 0) throw new TypeError("offset must be a uint32"); - if (offset > kBufferMaxLength || offset > length) throw new RangeError("offset out of range"); - } - function assertSize(size, offset, length) { - if (typeof size != "number" || size !== size) throw new TypeError("size must be a number"); - if (size > kMaxUint32 || size < 0) throw new TypeError("size must be a uint32"); - if (size + offset > length || size > kBufferMaxLength) throw new RangeError("buffer too small"); - } - - exports.randomFill = randomFill; - exports.randomFillSync = randomFillSync; - - function randomFill(buf, offset, size, cb) { - if (!Buffer2.isBuffer(buf) && !(buf instanceof global.Uint8Array)) - throw new TypeError('"buf" argument must be a Buffer or Uint8Array'); - if (typeof offset == "function") (cb = offset), (offset = 0), (size = buf.length); - else if (typeof size == "function") (cb = size), (size = buf.length - offset); - else if (typeof cb != "function") throw new TypeError('"cb" argument must be a function'); - return assertOffset(offset, buf.length), assertSize(size, offset, buf.length), actualFill(buf, offset, size, cb); - } - function actualFill(buf, offset, size, cb) { - if (cb) { - randombytes(size, function (err, bytes2) { - if (err) return cb(err); - bytes2.copy(buf, offset), cb(null, buf); - }); - return; - } - var bytes = randombytes(size); - return bytes.copy(buf, offset), buf; - } - function randomFillSync(buf, offset, size) { - if ((typeof offset > "u" && (offset = 0), !Buffer2.isBuffer(buf) && !(buf instanceof global.Uint8Array))) - throw new TypeError('"buf" argument must be a Buffer or Uint8Array'); - return ( - assertOffset(offset, buf.length), - size === void 0 && (size = buf.length - offset), - assertSize(size, offset, buf.length), - actualFill(buf, offset, size) - ); - } - }, -}); - // node_modules/crypto-browserify/index.js var require_crypto_browserify2 = __commonJS({ "node_modules/crypto-browserify/index.js"(exports) { @@ -10184,9 +10120,6 @@ var require_crypto_browserify2 = __commonJS({ exports.ECDH = ecdh.ECDH; exports.createECDH = ecdh.createECDH; exports.getRandomValues = values => crypto.getRandomValues(values); - var rf = require_browser11(); - exports.randomFill = rf.randomFill; - exports.randomFillSync = rf.randomFillSync; exports.constants = $processBindingConstants.crypto; }, }); @@ -10560,7 +10493,6 @@ crypto_exports.getFips = function getFips() { crypto_exports.getRandomValues = getRandomValues; crypto_exports.randomUUID = _randomUUID; -crypto_exports.randomInt = randomInt; crypto_exports.getCurves = getCurves; crypto_exports.getCipherInfo = getCipherInfo; crypto_exports.scrypt = scrypt; @@ -10712,5 +10644,68 @@ crypto_exports.createVerify = createVerify; }; } +function randomBytes(size, callback) { + if (callback === undefined) { + return _randomBytes(size); + } + + // Crypto random promise job is guaranteed to resolve. + _randomBytes(size, callback).then(buf => { + callback(null, buf); + }); +} + +crypto_exports.randomBytes = randomBytes; + +for (const rng of ["pseudoRandomBytes", "prng", "rng"]) { + Object.defineProperty(crypto_exports, rng, { + value: randomBytes, + enumerable: false, + configurable: true, + }); +} + +function randomInt(min, max, callback) { + let res; + if (typeof max === "undefined" || typeof max === "function") { + callback = max; + res = _randomInt(min, callback); + } else { + res = _randomInt(min, max, callback); + } + + if (callback !== undefined) { + // Crypto random promise job is guaranteed to resolve. + process.nextTick(callback, undefined, res); + } + + return res; +} + +crypto_exports.randomInt = randomInt; + +function randomFill(buf, offset, size, callback) { + if (!isAnyArrayBuffer(buf) && !isArrayBufferView(buf)) { + throw $ERR_INVALID_ARG_TYPE("buf", ["ArrayBuffer", "ArrayBufferView"], buf); + } + + if (typeof offset === "function") { + callback = offset; + offset = 0; + size = buf.length; + } else if (typeof size === "function") { + callback = size; + size = buf.length - offset; + } + + // Crypto random promise job is guaranteed to resolve. + _randomFill(buf, offset, size, callback).then(() => { + callback(null, buf); + }); +} + +crypto_exports.randomFill = randomFill; +crypto_exports.randomFillSync = randomFillSync; + export default crypto_exports; /*! safe-buffer. MIT License. Feross Aboukhadijeh */ diff --git a/src/sql/postgres.zig b/src/sql/postgres.zig index a8f52d9c4e..0704c6cae9 100644 --- a/src/sql/postgres.zig +++ b/src/sql/postgres.zig @@ -1387,7 +1387,7 @@ pub const PostgresSQLConnection = struct { pub fn nonce(this: *SASL) []const u8 { if (this.nonce_len == 0) { var bytes: [nonce_byte_len]u8 = .{0} ** nonce_byte_len; - bun.rand(&bytes); + bun.csprng(&bytes); this.nonce_len = @intCast(bun.base64.encode(&this.nonce_base64_bytes, &bytes)); } return this.nonce_base64_bytes[0..this.nonce_len]; diff --git a/test/js/bun/globals.test.js b/test/js/bun/globals.test.js index 2f24fa3cb3..95b6653965 100644 --- a/test/js/bun/globals.test.js +++ b/test/js/bun/globals.test.js @@ -36,7 +36,7 @@ it("ERR_INVALID_THIS", () => { } catch (e) { expect(e.code).toBe("ERR_INVALID_THIS"); expect(e.name).toBe("TypeError"); - expect(e.message).toBe(`Expected this to be instanceof Request, but received type string ("hellooo")`); + expect(e.message).toBe(`Expected this to be instanceof Request, but received type string ('hellooo')`); } }); diff --git a/test/js/node/buffer.test.js b/test/js/node/buffer.test.js index 798bb0b6ae..54ce5a80b0 100644 --- a/test/js/node/buffer.test.js +++ b/test/js/node/buffer.test.js @@ -22,7 +22,17 @@ afterEach(() => gc()); const NumberIsInteger = Number.isInteger; class ERR_INVALID_ARG_TYPE extends TypeError { constructor(name, type, value) { - super(`The "${name}" argument must be of type ${type}. Received type ${typeof value} (${Bun.inspect(value)})`); + let inspected; + if (typeof value === "string") { + if (value.indexOf("'") === -1) { + inspected = `'${value}'`; + } else { + inspected = `${JSON.stringify(value)}`; + } + } else { + inspected = Bun.inspect(value); + } + super(`The "${name}" argument must be of type ${type}. Received type ${typeof value} (${inspected})`); this.code = "ERR_INVALID_ARG_TYPE"; } } @@ -272,7 +282,7 @@ for (let withOverridenBufferWrite of [false, true]) { expect(() => b.write("test string", 0, 5, "invalid")).toThrow(/encoding/); // Unsupported arguments for Buffer.write expect(() => b.write("test", "utf8", 0)).toThrow( - `The "offset" argument must be of type number. Received type string ("utf8")`, + `The "offset" argument must be of type number. Received type string ('utf8')`, ); }); diff --git a/test/js/node/crypto/crypto-random.test.ts b/test/js/node/crypto/crypto-random.test.ts index ba5050f60d..664f7e53a2 100644 --- a/test/js/node/crypto/crypto-random.test.ts +++ b/test/js/node/crypto/crypto-random.test.ts @@ -16,8 +16,8 @@ describe("randomInt args validation", async () => { }); it("max/min should not be greater than Number.MAX_SAFE_INTEGER or less than Number.MIN_SAFE_INTEGER", () => { - expect(() => randomInt(Number.MAX_SAFE_INTEGER + 1)).toThrow(RangeError); - expect(() => randomInt(-Number.MAX_SAFE_INTEGER - 1, -Number.MAX_SAFE_INTEGER + 1)).toThrow(RangeError); + expect(() => randomInt(Number.MAX_SAFE_INTEGER + 1)).toThrow(TypeError); + expect(() => randomInt(-Number.MAX_SAFE_INTEGER - 1, -Number.MAX_SAFE_INTEGER + 1)).toThrow(TypeError); }); it("max - min should be <= 281474976710655", () => { diff --git a/test/js/node/dns/node-dns.test.js b/test/js/node/dns/node-dns.test.js index 06eefdef12..6161ff9631 100644 --- a/test/js/node/dns/node-dns.test.js +++ b/test/js/node/dns/node-dns.test.js @@ -423,7 +423,7 @@ describe("test invalid arguments", () => { }).toThrow("Expected address to be a non-empty string for 'lookupService'."); expect(() => { dns.lookupService("google.com", 443, (err, hostname, service) => {}); - }).toThrow(`The "address" argument is invalid. Received type string ("google.com")`); + }).toThrow(`The "address" argument is invalid. Received type string ('google.com')`); }); }); diff --git a/test/js/node/process/process.test.js b/test/js/node/process/process.test.js index f390bafdd3..340dcdd82b 100644 --- a/test/js/node/process/process.test.js +++ b/test/js/node/process/process.test.js @@ -374,7 +374,7 @@ it("process.argv in testing", () => { describe("process.exitCode", () => { it("validates int", () => { expect(() => (process.exitCode = "potato")).toThrow( - `The "code" argument must be of type number. Received type string ("potato")`, + `The "code" argument must be of type number. Received type string ('potato')`, ); expect(() => (process.exitCode = 1.2)).toThrow( `The value of \"code\" is out of range. It must be an integer. Received 1.2`, diff --git a/test/js/node/test/common/index.js b/test/js/node/test/common/index.js index 56c1f22d8a..8a2868f0fa 100644 --- a/test/js/node/test/common/index.js +++ b/test/js/node/test/common/index.js @@ -857,6 +857,13 @@ function invalidArgTypeHelper(input) { } return ` Received ${inspect(input, { depth: -1 })}`; } + if (typeof input === 'string') { + input.length > 28 && (input = `${input.slice(0, 25)}...`); + if (input.indexOf("'") === -1) { + return ` Received type string ('${input}')`; + } + return ` Received type string (${JSON.stringify(input)})`; + } let inspected = inspect(input, { colors: false }); if (inspected.length > 28) { inspected = `${inspected.slice(inspected, 0, 25)}...`; } diff --git a/test/js/node/test/parallel/test-crypto-random.js b/test/js/node/test/parallel/test-crypto-random.js new file mode 100644 index 0000000000..1ca4039986 --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-random.js @@ -0,0 +1,528 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Flags: --pending-deprecation +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); +const { kMaxLength } = require('buffer'); + +const kMaxInt32 = 2 ** 31 - 1; +const kMaxPossibleLength = Math.min(kMaxLength, kMaxInt32); + +// common.expectWarning('DeprecationWarning', +// 'crypto.pseudoRandomBytes is deprecated.', 'DEP0115'); + +{ + [crypto.randomBytes, crypto.pseudoRandomBytes].forEach((f) => { + [undefined, null, false, true, {}, []].forEach((value) => { + const errObj = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "size" argument must be of type number.' + + common.invalidArgTypeHelper(value) + }; + assert.throws(() => f(value), errObj); + assert.throws(() => f(value, common.mustNotCall()), errObj); + }); + + [-1, NaN, 2 ** 32, 2 ** 31].forEach((value) => { + const errObj = { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "size" is out of range. It must be >= 0 and <= ' + + `${kMaxPossibleLength}. Received ${value}` + }; + assert.throws(() => f(value), errObj); + assert.throws(() => f(value, common.mustNotCall()), errObj); + }); + + [0, 1, 2, 4, 16, 256, 1024, 101.2].forEach((len) => { + f(len, common.mustCall((ex, buf) => { + assert.strictEqual(ex, null); + assert.strictEqual(buf.length, Math.floor(len)); + assert.ok(Buffer.isBuffer(buf)); + })); + }); + }); +} + +{ + const buf = Buffer.alloc(10); + const before = buf.toString('hex'); + const after = crypto.randomFillSync(buf).toString('hex'); + assert.notStrictEqual(before, after); +} + +{ + const buf = new Uint8Array(new Array(10).fill(0)); + const before = Buffer.from(buf).toString('hex'); + crypto.randomFillSync(buf); + const after = Buffer.from(buf).toString('hex'); + assert.notStrictEqual(before, after); +} + +{ + [ + new Uint16Array(10), + new Uint32Array(10), + new Float32Array(10), + new Float64Array(10), + new DataView(new ArrayBuffer(10)), + ].forEach((buf) => { + const before = Buffer.from(buf.buffer).toString('hex'); + crypto.randomFillSync(buf); + const after = Buffer.from(buf.buffer).toString('hex'); + assert.notStrictEqual(before, after); + }); +} + +{ + [ + new Uint16Array(10), + new Uint32Array(10), + ].forEach((buf) => { + const before = Buffer.from(buf.buffer).toString('hex'); + globalThis.crypto.getRandomValues(buf); + const after = Buffer.from(buf.buffer).toString('hex'); + assert.notStrictEqual(before, after); + }); +} + +{ + [ + new ArrayBuffer(10), + new SharedArrayBuffer(10), + ].forEach((buf) => { + const before = Buffer.from(buf).toString('hex'); + crypto.randomFillSync(buf); + const after = Buffer.from(buf).toString('hex'); + assert.notStrictEqual(before, after); + }); +} + +{ + const buf = Buffer.alloc(10); + const before = buf.toString('hex'); + crypto.randomFill(buf, common.mustSucceed((buf) => { + const after = buf.toString('hex'); + assert.notStrictEqual(before, after); + })); +} + +{ + const buf = new Uint8Array(new Array(10).fill(0)); + const before = Buffer.from(buf).toString('hex'); + crypto.randomFill(buf, common.mustSucceed((buf) => { + const after = Buffer.from(buf).toString('hex'); + assert.notStrictEqual(before, after); + })); +} + +{ + [ + new Uint16Array(10), + new Uint32Array(10), + new Float32Array(10), + new Float64Array(10), + new DataView(new ArrayBuffer(10)), + ].forEach((buf) => { + const before = Buffer.from(buf.buffer).toString('hex'); + crypto.randomFill(buf, common.mustSucceed((buf) => { + const after = Buffer.from(buf.buffer).toString('hex'); + assert.notStrictEqual(before, after); + })); + }); +} + +{ + [ + new ArrayBuffer(10), + new SharedArrayBuffer(10), + ].forEach((buf) => { + const before = Buffer.from(buf).toString('hex'); + crypto.randomFill(buf, common.mustSucceed((buf) => { + const after = Buffer.from(buf).toString('hex'); + assert.notStrictEqual(before, after); + })); + }); +} + +{ + const buf = Buffer.alloc(10); + const before = buf.toString('hex'); + crypto.randomFillSync(buf, 5, 5); + const after = buf.toString('hex'); + assert.notStrictEqual(before, after); + assert.deepStrictEqual(before.slice(0, 5), after.slice(0, 5)); +} + +{ + const buf = new Uint8Array(new Array(10).fill(0)); + const before = Buffer.from(buf).toString('hex'); + crypto.randomFillSync(buf, 5, 5); + const after = Buffer.from(buf).toString('hex'); + assert.notStrictEqual(before, after); + assert.deepStrictEqual(before.slice(0, 5), after.slice(0, 5)); +} + +{ + const buf = Buffer.alloc(10); + const before = buf.toString('hex'); + crypto.randomFillSync(buf, 5); + const after = buf.toString('hex'); + assert.notStrictEqual(before, after); + assert.deepStrictEqual(before.slice(0, 5), after.slice(0, 5)); +} + +{ + const buf = Buffer.alloc(10); + const before = buf.toString('hex'); + crypto.randomFill(buf, 5, 5, common.mustSucceed((buf) => { + const after = buf.toString('hex'); + assert.notStrictEqual(before, after); + assert.deepStrictEqual(before.slice(0, 5), after.slice(0, 5)); + })); +} + +{ + const buf = new Uint8Array(new Array(10).fill(0)); + const before = Buffer.from(buf).toString('hex'); + crypto.randomFill(buf, 5, 5, common.mustSucceed((buf) => { + const after = Buffer.from(buf).toString('hex'); + assert.notStrictEqual(before, after); + assert.deepStrictEqual(before.slice(0, 5), after.slice(0, 5)); + })); +} + +{ + [ + Buffer.alloc(10), + new Uint8Array(new Array(10).fill(0)), + ].forEach((buf) => { + const len = Buffer.byteLength(buf); + assert.strictEqual(len, 10, `Expected byteLength of 10, got ${len}`); + + const typeErrObj = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "offset" argument must be of type number. ' + + "Received type string ('test')" + }; + + assert.throws(() => crypto.randomFillSync(buf, 'test'), typeErrObj); + + assert.throws( + () => crypto.randomFill(buf, 'test', common.mustNotCall()), + typeErrObj); + + typeErrObj.message = typeErrObj.message.replace('offset', 'size'); + assert.throws(() => crypto.randomFillSync(buf, 0, 'test'), typeErrObj); + + assert.throws( + () => crypto.randomFill(buf, 0, 'test', common.mustNotCall()), + typeErrObj + ); + + [NaN, kMaxPossibleLength + 1, -10, (-1 >>> 0) + 1].forEach((offsetSize) => { + const errObj = { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 and <= 10. Received ${offsetSize}` + }; + + assert.throws(() => crypto.randomFillSync(buf, offsetSize), errObj); + + assert.throws( + () => crypto.randomFill(buf, offsetSize, common.mustNotCall()), + errObj); + + errObj.message = 'The value of "size" is out of range. It must be >= ' + + `0 and <= ${kMaxPossibleLength}. Received ${offsetSize}`; + assert.throws(() => crypto.randomFillSync(buf, 1, offsetSize), errObj); + + assert.throws( + () => crypto.randomFill(buf, 1, offsetSize, common.mustNotCall()), + errObj + ); + }); + + const rangeErrObj = { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "size + offset" is out of range. ' + + 'It must be <= 10. Received 11' + }; + assert.throws(() => crypto.randomFillSync(buf, 1, 10), rangeErrObj); + + assert.throws( + () => crypto.randomFill(buf, 1, 10, common.mustNotCall()), + rangeErrObj + ); + }); +} + +// https://github.com/nodejs/node-v0.x-archive/issues/5126, +// "FATAL ERROR: v8::Object::SetIndexedPropertiesToExternalArrayData() length +// exceeds max acceptable value" +assert.throws( + () => crypto.randomBytes((-1 >>> 0) + 1), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "size" is out of range. ' + + `It must be >= 0 and <= ${kMaxPossibleLength}. Received 4294967296` + } +); + +[1, true, NaN, null, undefined, {}, []].forEach((i) => { + const buf = Buffer.alloc(10); + assert.throws( + () => crypto.randomFillSync(i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + assert.throws( + () => crypto.randomFill(i, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + assert.throws( + () => crypto.randomFill(buf, 0, 10, i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); +}); + +[1, true, NaN, null, {}, []].forEach((i) => { + assert.throws( + () => crypto.randomBytes(1, i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + } + ); +}); + +['pseudoRandomBytes', 'prng', 'rng'].forEach((f) => { + const desc = Object.getOwnPropertyDescriptor(crypto, f); + assert.ok(desc); + assert.strictEqual(desc.configurable, true); + assert.strictEqual(desc.enumerable, false); +}); + + +{ + // Asynchronous API + const randomInts = []; + for (let i = 0; i < 100; i++) { + crypto.randomInt(3, common.mustSucceed((n) => { + assert.ok(n >= 0); + assert.ok(n < 3); + randomInts.push(n); + if (randomInts.length === 100) { + assert.ok(!randomInts.includes(-1)); + assert.ok(randomInts.includes(0)); + assert.ok(randomInts.includes(1)); + assert.ok(randomInts.includes(2)); + assert.ok(!randomInts.includes(3)); + } + })); + } +} +{ + // Synchronous API + const randomInts = []; + for (let i = 0; i < 100; i++) { + const n = crypto.randomInt(3); + assert.ok(n >= 0); + assert.ok(n < 3); + randomInts.push(n); + } + + assert.ok(!randomInts.includes(-1)); + assert.ok(randomInts.includes(0)); + assert.ok(randomInts.includes(1)); + assert.ok(randomInts.includes(2)); + assert.ok(!randomInts.includes(3)); +} +{ + // Positive range + const randomInts = []; + for (let i = 0; i < 100; i++) { + crypto.randomInt(1, 3, common.mustSucceed((n) => { + assert.ok(n >= 1); + assert.ok(n < 3); + randomInts.push(n); + if (randomInts.length === 100) { + assert.ok(!randomInts.includes(0)); + assert.ok(randomInts.includes(1)); + assert.ok(randomInts.includes(2)); + assert.ok(!randomInts.includes(3)); + } + })); + } +} +{ + // Negative range + const randomInts = []; + for (let i = 0; i < 100; i++) { + crypto.randomInt(-10, -8, common.mustSucceed((n) => { + assert.ok(n >= -10); + assert.ok(n < -8); + randomInts.push(n); + if (randomInts.length === 100) { + assert.ok(!randomInts.includes(-11)); + assert.ok(randomInts.includes(-10)); + assert.ok(randomInts.includes(-9)); + assert.ok(!randomInts.includes(-8)); + } + })); + } +} +{ + + ['10', true, NaN, null, {}, []].forEach((i) => { + const invalidMinError = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "min" argument must be a safe integer.' + + `${common.invalidArgTypeHelper(i)}`, + }; + const invalidMaxError = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "max" argument must be a safe integer.' + + `${common.invalidArgTypeHelper(i)}`, + }; + + assert.throws( + () => crypto.randomInt(i, 100), + invalidMinError + ); + assert.throws( + () => crypto.randomInt(i, 100, common.mustNotCall()), + invalidMinError + ); + assert.throws( + () => crypto.randomInt(i), + invalidMaxError + ); + assert.throws( + () => crypto.randomInt(i, common.mustNotCall()), + invalidMaxError + ); + assert.throws( + () => crypto.randomInt(0, i, common.mustNotCall()), + invalidMaxError + ); + assert.throws( + () => crypto.randomInt(0, i), + invalidMaxError + ); + }); + + const maxInt = Number.MAX_SAFE_INTEGER; + const minInt = Number.MIN_SAFE_INTEGER; + + crypto.randomInt(minInt, minInt + 5, common.mustSucceed()); + crypto.randomInt(maxInt - 5, maxInt, common.mustSucceed()); + + assert.throws( + () => crypto.randomInt(minInt - 1, minInt + 5, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "min" argument must be a safe integer.' + + `${common.invalidArgTypeHelper(minInt - 1)}`, + } + ); + + assert.throws( + () => crypto.randomInt(maxInt + 1, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "max" argument must be a safe integer.' + + `${common.invalidArgTypeHelper(maxInt + 1)}`, + } + ); + + crypto.randomInt(1, common.mustSucceed()); + crypto.randomInt(0, 1, common.mustSucceed()); + for (const arg of [[0], [1, 1], [3, 2], [-5, -5], [11, -10]]) { + assert.throws(() => crypto.randomInt(...arg, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "max" is out of range. It must be greater than ' + + `the value of "min" (${arg[arg.length - 2] || 0}). ` + + `Received ${arg[arg.length - 1]}` + }); + } + + const MAX_RANGE = 0xFFFF_FFFF_FFFF; + crypto.randomInt(MAX_RANGE, common.mustSucceed()); + crypto.randomInt(1, MAX_RANGE + 1, common.mustSucceed()); + assert.throws( + () => crypto.randomInt(1, MAX_RANGE + 2, common.mustNotCall()), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "max - min" is out of range. ' + + `It must be <= ${MAX_RANGE}. ` + + 'Received 281474976710656' + } + ); + + assert.throws(() => crypto.randomInt(MAX_RANGE + 1, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "max" is out of range. ' + + `It must be <= ${MAX_RANGE}. ` + + 'Received 281474976710656' + }); + + [true, NaN, null, {}, [], 10].forEach((i) => { + const cbError = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }; + assert.throws(() => crypto.randomInt(0, 1, i), cbError); + }); +} + +{ + // Verify that it doesn't throw or abort + crypto.randomFill(new Uint16Array(10), 0, common.mustSucceed()); + crypto.randomFill(new Uint32Array(10), 0, common.mustSucceed()); + crypto.randomFill(new Uint32Array(10), 0, 1, common.mustSucceed()); +} diff --git a/test/js/node/test/parallel/test-net-server-listen-options-signal.js b/test/js/node/test/parallel/test-net-server-listen-options-signal.js index cc9101c9b5..60e78fa00c 100644 --- a/test/js/node/test/parallel/test-net-server-listen-options-signal.js +++ b/test/js/node/test/parallel/test-net-server-listen-options-signal.js @@ -9,7 +9,6 @@ const net = require('net'); assert.throws( () => server.listen({ port: 0, signal: 'INVALID_SIGNAL' }), { - message: `The "options.signal" property must be of type AbortSignal. Received type string ("INVALID_SIGNAL")`, code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' }); diff --git a/test/js/node/test/parallel/test-process-exit-code-validation.js b/test/js/node/test/parallel/test-process-exit-code-validation.js index dc9bb4c979..7548dcdc38 100644 --- a/test/js/node/test/parallel/test-process-exit-code-validation.js +++ b/test/js/node/test/parallel/test-process-exit-code-validation.js @@ -6,17 +6,17 @@ const invalids = [ { code: '', expected: 1, - pattern: `Received type string \\(""\\)$`, + pattern: "Received type string \\(''\\)$", }, { code: '1 one', expected: 1, - pattern: `Received type string \\("1 one"\\)$`, + pattern: "Received type string \\('1 one'\\)$", }, { code: 'two', expected: 1, - pattern: `Received type string \\("two"\\)$`, + pattern: "Received type string \\('two'\\)$", }, { code: {},