aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bun.js/rare_data.zig60
-rw-r--r--src/bun.js/uuid.zig24
-rw-r--r--src/bun.js/webcore.zig52
-rw-r--r--src/deps/boringssl.translated.zig13
-rw-r--r--src/global.zig8
-rw-r--r--src/http/websocket_http_client.zig3
-rw-r--r--test/bun.js/web-globals.test.js28
7 files changed, 158 insertions, 30 deletions
diff --git a/src/bun.js/rare_data.zig b/src/bun.js/rare_data.zig
index cfca61ce0..8237d99a9 100644
--- a/src/bun.js/rare_data.zig
+++ b/src/bun.js/rare_data.zig
@@ -7,6 +7,7 @@ const Syscall = @import("./node/syscall.zig");
const JSC = @import("javascript_core");
const std = @import("std");
const BoringSSL = @import("boringssl");
+const bun = @import("../global.zig");
const WebSocketClientMask = @import("../http/websocket_http_client.zig").Mask;
boring_ssl_engine: ?*BoringSSL.ENGINE = null,
@@ -16,11 +17,70 @@ stdin_store: ?*Blob.Store = null,
stdout_store: ?*Blob.Store = null,
websocket_mask: WebSocketClientMask = WebSocketClientMask{},
+uuid_entropy_cache: ?*EntropyCache = null,
+
// TODO: make this per JSGlobalObject instead of global
// This does not handle ShadowRealm correctly!
tail_cleanup_hook: ?*CleanupHook = null,
cleanup_hook: ?*CleanupHook = null,
+pub fn nextUUID(this: *RareData) [16]u8 {
+ if (this.uuid_entropy_cache == null) {
+ this.uuid_entropy_cache = default_allocator.create(EntropyCache) catch unreachable;
+ this.uuid_entropy_cache.?.init();
+ }
+
+ return this.uuid_entropy_cache.?.get();
+}
+
+pub fn entropySlice(this: *RareData, len: usize) []u8 {
+ if (this.uuid_entropy_cache == null) {
+ this.uuid_entropy_cache = default_allocator.create(EntropyCache) catch unreachable;
+ this.uuid_entropy_cache.?.init();
+ }
+
+ return this.uuid_entropy_cache.?.slice(len);
+}
+
+pub const EntropyCache = struct {
+ pub const buffered_uuids_count = 16;
+ pub const size = buffered_uuids_count * 128;
+
+ cache: [size]u8 = undefined,
+ index: usize = 0,
+
+ pub fn init(instance: *EntropyCache) void {
+ instance.fill();
+ }
+
+ pub fn fill(this: *EntropyCache) void {
+ bun.rand(&this.cache);
+ this.index = 0;
+ }
+
+ pub fn slice(this: *EntropyCache, len: usize) []u8 {
+ if (len > this.cache.len) {
+ return &[_]u8{};
+ }
+
+ if (this.index + len > this.cache.len) {
+ this.fill();
+ }
+ const result = this.cache[this.index..][0..len];
+ this.index += len;
+ return result;
+ }
+
+ pub fn get(this: *EntropyCache) [16]u8 {
+ if (this.index + 16 > this.cache.len) {
+ this.fill();
+ }
+ const result = this.cache[this.index..][0..16].*;
+ this.index += 16;
+ return result;
+ }
+};
+
pub const CleanupHook = struct {
next: ?*CleanupHook = null,
ctx: ?*anyopaque,
diff --git a/src/bun.js/uuid.zig b/src/bun.js/uuid.zig
index 740309396..fa59520c2 100644
--- a/src/bun.js/uuid.zig
+++ b/src/bun.js/uuid.zig
@@ -3,16 +3,17 @@ const std = @import("std");
const crypto = std.crypto;
const fmt = std.fmt;
const testing = std.testing;
+const bun = @import("../global.zig");
pub const Error = error{InvalidUUID};
const UUID = @This();
-bytes: [16]u8,
+bytes: [16]u8 = undefined,
pub fn init() UUID {
var uuid = UUID{ .bytes = undefined };
- crypto.random.bytes(&uuid.bytes);
+ bun.rand(&uuid.bytes);
// Version 4
uuid.bytes[6] = (uuid.bytes[6] & 0x0f) | 0x40;
// Variant 1
@@ -67,22 +68,29 @@ pub fn format(
) !void {
_ = options; // currently unused
- if (layout.len != 0 and layout[0] != 's')
+ if (comptime layout.len != 0 and layout[0] != 's')
@compileError("Unsupported format specifier for UUID type: '" ++ layout ++ "'.");
-
var buf: [36]u8 = undefined;
+ self.print(&buf);
+
+ try fmt.format(writer, "{s}", .{buf});
+}
+
+pub fn print(
+ self: UUID,
+ buf: *[36]u8,
+) void {
const hex = "0123456789abcdef";
+ const bytes = self.bytes;
buf[8] = '-';
buf[13] = '-';
buf[18] = '-';
buf[23] = '-';
inline for (encoded_pos) |i, j| {
- buf[i + 0] = hex[self.bytes[j] >> 4];
- buf[i + 1] = hex[self.bytes[j] & 0x0f];
+ buf[comptime i + 0] = hex[bytes[j] >> 4];
+ buf[comptime i + 1] = hex[bytes[j] & 0x0f];
}
-
- try fmt.format(writer, "{s}", .{buf});
}
pub fn parse(buf: []const u8) Error!UUID {
diff --git a/src/bun.js/webcore.zig b/src/bun.js/webcore.zig
index ac5238dac..4d06003b5 100644
--- a/src/bun.js/webcore.zig
+++ b/src/bun.js/webcore.zig
@@ -358,7 +358,7 @@ pub const Prompt = struct {
pub const Crypto = struct {
const UUID = @import("./uuid.zig");
-
+ const BoringSSL = @import("boringssl");
pub const Class = JSC.NewClass(void, .{ .name = "crypto" }, .{
.getRandomValues = JSC.DOMCall("Crypto", @This(), "getRandomValues", JSC.JSValue, JSC.DOMEffect.top),
.randomUUID = JSC.DOMCall("Crypto", @This(), "randomUUID", *JSC.JSString, JSC.DOMEffect.top),
@@ -389,20 +389,40 @@ pub const Crypto = struct {
return JSC.JSValue.jsUndefined();
};
var slice = array_buffer.byteSlice();
- if (slice.len > 0)
- std.crypto.random.bytes(slice);
+
+ switch (slice.len) {
+ 0 => {},
+ 1...JSC.RareData.EntropyCache.size / 8 => {
+ if (arguments.len > 1) {
+ bun.rand(slice);
+ } else {
+ std.mem.copy(u8, slice, globalThis.bunVM().rareData().entropySlice(slice.len));
+ }
+ },
+ else => {
+ bun.rand(slice);
+ },
+ }
return arguments[0];
}
pub fn getRandomValuesWithoutTypeChecks(
- _: *JSC.JSGlobalObject,
+ globalThis: *JSC.JSGlobalObject,
_: *anyopaque,
array: *JSC.JSUint8Array,
) callconv(.C) JSC.JSValue {
var slice = array.slice();
- if (slice.len > 0)
- std.crypto.random.bytes(slice);
+ switch (slice.len) {
+ 0 => {},
+ // 512 bytes or less we reuse from the same cache as UUID generation.
+ 1...JSC.RareData.EntropyCache.size / 8 => {
+ std.mem.copy(u8, slice, globalThis.bunVM().rareData().entropySlice(slice.len));
+ },
+ else => {
+ bun.rand(slice);
+ },
+ }
return @intToEnum(JSC.JSValue, @bitCast(i64, @ptrToInt(array)));
}
@@ -412,20 +432,24 @@ pub const Crypto = struct {
_: JSC.JSValue,
_: []const JSC.JSValue,
) JSC.JSValue {
- var uuid = UUID.init();
- var out: [128]u8 = undefined;
- var str = std.fmt.bufPrint(&out, "{s}", .{uuid}) catch unreachable;
- return JSC.ZigString.init(str).toValueGC(globalThis);
+ var out: [36]u8 = undefined;
+ const uuid: UUID = .{
+ .bytes = globalThis.bunVM().rareData().nextUUID(),
+ };
+ uuid.print(&out);
+ return JSC.ZigString.init(&out).toValueGC(globalThis);
}
pub fn randomUUIDWithoutTypeChecks(
globalThis: *JSC.JSGlobalObject,
_: *anyopaque,
) callconv(.C) JSC.JSValue {
- var uuid = UUID.init();
- var out: [128]u8 = undefined;
- var str = std.fmt.bufPrint(&out, "{s}", .{uuid}) catch unreachable;
- return JSC.ZigString.init(str).toValueGC(globalThis);
+ var out: [36]u8 = undefined;
+ const uuid: UUID = .{
+ .bytes = globalThis.bunVM().rareData().nextUUID(),
+ };
+ uuid.print(&out);
+ return JSC.ZigString.init(&out).toValueGC(globalThis);
}
pub fn call(
diff --git a/src/deps/boringssl.translated.zig b/src/deps/boringssl.translated.zig
index 4561df8ca..7b5f55a27 100644
--- a/src/deps/boringssl.translated.zig
+++ b/src/deps/boringssl.translated.zig
@@ -18618,6 +18618,19 @@ pub const ssl_comp_st = struct_ssl_comp_st;
pub const stack_st_SSL_COMP = struct_stack_st_SSL_COMP;
pub const ssl_conf_ctx_st = struct_ssl_conf_ctx_st;
+pub extern fn RAND_bytes(buf: [*]u8, len: usize) c_int;
+
+/// RAND_enable_fork_unsafe_buffering enables efficient buffered reading of
+/// /dev/urandom. It adds an overhead of a few KB of memory per thread. It must
+/// be called before the first call to |RAND_bytes|.
+///
+/// |fd| must be -1. We no longer support setting the file descriptor with this
+/// function.
+///
+/// It has an unusual name because the buffer is unsafe across calls to |fork|.
+/// Hence, this function should never be called by libraries.
+pub extern fn RAND_enable_fork_unsafe_buffering(c_int) void;
+
// Manual modification
pub const struct_bio_st = extern struct {
diff --git a/src/global.zig b/src/global.zig
index 9d68753ea..5db2de0b6 100644
--- a/src/global.zig
+++ b/src/global.zig
@@ -319,3 +319,11 @@ pub const LinearFifo = @import("./linear_fifo.zig").LinearFifo;
pub fn hash(content: []const u8) u64 {
return std.hash.Wyhash.hash(0, content);
}
+
+pub const HiveArray = @import("./hive_array.zig").HiveArray;
+
+pub fn rand(bytes: []u8) void {
+ const BoringSSL = @import("boringssl");
+ if (bytes.len > 512)
+ _ = BoringSSL.RAND_bytes(bytes.ptr, bytes.len);
+}
diff --git a/src/http/websocket_http_client.zig b/src/http/websocket_http_client.zig
index f380b38e1..cba0b2cae 100644
--- a/src/http/websocket_http_client.zig
+++ b/src/http/websocket_http_client.zig
@@ -24,8 +24,7 @@ const Opcode = @import("./websocket.zig").Opcode;
fn buildRequestBody(vm: *JSC.VirtualMachine, pathname: *const JSC.ZigString, host: *const JSC.ZigString, client_protocol: *const JSC.ZigString, client_protocol_hash: *u64) std.mem.Allocator.Error![]u8 {
const allocator = vm.allocator;
- var input_rand_buf: [16]u8 = undefined;
- std.crypto.random.bytes(&input_rand_buf);
+ const input_rand_buf = vm.rareData().nextUUID();
const temp_buf_size = comptime std.base64.standard.Encoder.calcSize(16);
var encoded_buf: [temp_buf_size]u8 = undefined;
const accept_key = std.base64.standard.Encoder.encode(&encoded_buf, &input_rand_buf);
diff --git a/test/bun.js/web-globals.test.js b/test/bun.js/web-globals.test.js
index 178f5dd00..c5740b0fc 100644
--- a/test/bun.js/web-globals.test.js
+++ b/test/bun.js/web-globals.test.js
@@ -48,18 +48,34 @@ test("MessageEvent", () => {
it("crypto.getRandomValues", () => {
var foo = new Uint8Array(32);
- // run it once
- var array = crypto.getRandomValues(foo);
- expect(array).toBe(foo);
- expect(array.reduce((sum, a) => (sum += a === 0), 0) != foo.length).toBe(
- true
- );
+ // run it once buffered and unbuffered
+ {
+ var array = crypto.getRandomValues(foo);
+ expect(array).toBe(foo);
+ expect(array.reduce((sum, a) => (sum += a === 0), 0) != foo.length).toBe(
+ true
+ );
+ }
// run it again to check that the fast path works
for (var i = 0; i < 9000; i++) {
var array = crypto.getRandomValues(foo);
expect(array).toBe(foo);
}
+
+ // run it on a large input
+ expect(
+ !!crypto.getRandomValues(new Uint8Array(8096)).find((a) => a > 0)
+ ).toBe(true);
+
+ {
+ // any additional input into getRandomValues() makes it unbuffered
+ var array = crypto.getRandomValues(foo, "unbuffered");
+ expect(array).toBe(foo);
+ expect(array.reduce((sum, a) => (sum += a === 0), 0) != foo.length).toBe(
+ true
+ );
+ }
});
it("crypto.randomUUID", () => {