diff options
-rw-r--r-- | bench.node.cjs | 0 | ||||
-rw-r--r-- | src/deps/uws.zig | 7 | ||||
-rw-r--r-- | src/env.zig | 1 | ||||
-rw-r--r-- | src/global.zig | 37 | ||||
-rw-r--r-- | src/http/websocket.zig | 28 | ||||
-rw-r--r-- | src/http/websocket_http_client.zig | 576 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/ScriptExecutionContext.cpp | 74 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/ScriptExecutionContext.h | 10 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/headers-cpp.h | 2 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/headers-handwritten.h | 2 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/headers-replacements.zig | 2 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/headers.h | 529 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/headers.zig | 2 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/webcore/WebSocket.cpp | 219 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/webcore/WebSocket.h | 6 | ||||
-rw-r--r-- | src/javascript/jsc/event_loop.zig | 2 | ||||
-rw-r--r-- | src/linear_fifo.zig | 536 |
17 files changed, 1363 insertions, 670 deletions
diff --git a/bench.node.cjs b/bench.node.cjs new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/bench.node.cjs diff --git a/src/deps/uws.zig b/src/deps/uws.zig index 48c93ab53..695cef9cb 100644 --- a/src/deps/uws.zig +++ b/src/deps/uws.zig @@ -48,7 +48,8 @@ pub fn NewSocketHandler(comptime ssl: bool) type { comptime ssl_int, this.socket, data.ptr, - @intCast(c_int, data.len), + // truncate to 31 bits since sign bit exists + @intCast(c_int, @truncate(u31, data.len)), @as(c_int, @boolToInt(msg_more)), ); } @@ -214,10 +215,10 @@ pub fn NewSocketHandler(comptime ssl: bool) type { comptime socket_field_name: []const u8, ctx: Context, ) ?*Context { - var adopted = Socket{ .socket = us_socket_context_adopt_socket(comptime ssl_int, socket_ctx, socket, @sizeOf(Context)) orelse return null }; + var adopted = ThisSocket{ .socket = us_socket_context_adopt_socket(comptime ssl_int, socket_ctx, socket, @sizeOf(Context)) orelse return null }; var holder = adopted.ext(Context) orelse { if (comptime bun.Environment.allow_assert) unreachable; - _ = us_socket_close(comptime ssl_int, socket); + _ = us_socket_close(comptime ssl_int, socket, 0, null); return null; }; holder.* = ctx; diff --git a/src/env.zig b/src/env.zig index 9801966c0..0e4cbc414 100644 --- a/src/env.zig +++ b/src/env.zig @@ -25,3 +25,4 @@ pub const isX86 = @import("builtin").target.cpu.arch.isX86(); pub const isX64 = @import("builtin").target.cpu.arch == .x86_64; pub const allow_assert = isDebug or isTest; pub const analytics_url = if (isDebug) "http://localhost:4000/events" else "http://i.bun.sh/events"; +pub const simd = isX86 or isAarch64; diff --git a/src/global.zig b/src/global.zig index 6160ce781..adf7bed06 100644 --- a/src/global.zig +++ b/src/global.zig @@ -201,3 +201,40 @@ pub inline fn range(comptime min: anytype, comptime max: anytype) [max - min]usi break :brk slice; }; } + +pub fn copy(comptime Type: type, dest: []Type, src: []const Type) void { + std.debug.assert(dest.len >= src.len); + var input = std.mem.sliceAsBytes(src); + var output = std.mem.sliceAsBytes(dest); + var input_end = input.ptr + input.len; + const output_end = output.ptr + output.len; + + if (@ptrToInt(input.ptr) <= @ptrToInt(output.ptr) and @ptrToInt(output_end) <= @ptrToInt(input_end)) { + // input is overlapping with output + if (input.len > strings.ascii_vector_size) { + const input_end_vectorized = input.ptr + input.len - (input.len % strings.ascii_vector_size); + while (input.ptr != input_end_vectorized) { + const input_vec = @as(@Vector(strings.ascii_vector_size, u8), input[0..strings.ascii_vector_size].*); + output[0..strings.ascii_vector_size].* = input_vec; + input = input[strings.ascii_vector_size..]; + output = output[strings.ascii_vector_size..]; + } + } + + while (input.len >= @sizeOf(usize)) { + output[0..@sizeOf(usize)].* = input[0..@sizeOf(usize)].*; + input = input[@sizeOf(usize)..]; + output = output[@sizeOf(usize)..]; + } + + while (input.ptr != input_end) { + output[0] = input[0]; + input = input[1..]; + output = output[1..]; + } + } else { + @memcpy(output.ptr, input.ptr, input.len); + } +} + +pub const LinearFifo = @import("./linear_fifo.zig").LinearFifo; diff --git a/src/http/websocket.zig b/src/http/websocket.zig index 6bb433ea3..12348f83c 100644 --- a/src/http/websocket.zig +++ b/src/http/websocket.zig @@ -54,20 +54,26 @@ pub const WebsocketHeader = packed struct { final: bool = true, pub fn writeHeader(header: WebsocketHeader, writer: anytype, n: usize) anyerror!void { - try writer.writeIntBig(u16, @bitCast(u16, header)); - - // Write extended length if needed - switch (n) { - 0...126 => {}, // Included in header - 127...0xFFFF => try writer.writeIntBig(u16, @truncate(u16, n)), - else => try writer.writeIntBig(u64, n), + // packed structs are sometimes buggy + // lets check it worked right + if (comptime Environment.allow_assert) { + var buf_ = [2]u8{ 0, 0 }; + var stream = std.io.fixedBufferStream(&buf_); + stream.writer().writeIntBig(u16, @bitCast(u16, header)) catch unreachable; + stream.pos = 0; + const casted = stream.reader().readIntBig(u16) catch unreachable; + std.debug.assert(casted == @bitCast(u16, header)); + std.debug.assert(std.meta.eql(@bitCast(WebsocketHeader, casted), header)); } + + try writer.writeIntBig(u16, @bitCast(u16, header)); + std.debug.assert(header.len == packLength(n)); } pub fn packLength(length: usize) u7 { return switch (length) { - 0...126 => @truncate(u7, length), - 127...0xFFFF => 126, + 0...125 => @truncate(u7, length), + 126...0xFFFF => 126, else => 127, }; } @@ -77,8 +83,8 @@ pub const WebsocketHeader = packed struct { pub fn lengthByteCount(byte_length: usize) usize { return switch (byte_length) { - 0...126 => 0, - 127...0xFFFF => @sizeOf(u16), + 0...125 => 0, + 126...0xFFFF => @sizeOf(u16), else => @sizeOf(u64), }; } diff --git a/src/http/websocket_http_client.zig b/src/http/websocket_http_client.zig index 2be382dbe..8522c6038 100644 --- a/src/http/websocket_http_client.zig +++ b/src/http/websocket_http_client.zig @@ -92,6 +92,7 @@ const ErrorCode = enum(i32) { compression_unsupported, unexpected_mask_from_server, expected_control_frame, + unsupported_control_frame, unexpected_opcode, invalid_utf8, }; @@ -101,9 +102,9 @@ extern fn WebSocket__didConnect( buffered_data: ?[*]u8, buffered_len: usize, ) void; -extern fn WebSocket__didFailWithErrorCode(websocket_context: *anyopaque, reason: ErrorCode) void; +extern fn WebSocket__didCloseWithErrorCode(websocket_context: *anyopaque, reason: ErrorCode) void; extern fn WebSocket__didReceiveText(websocket_context: *anyopaque, clone: bool, text: *const JSC.ZigString) void; -extern fn WebSocket__didReceiveBytes(websocket_context: *anyopaque, bytes: []const u8) void; +extern fn WebSocket__didReceiveBytes(websocket_context: *anyopaque, bytes: [*]const u8, byte_len: usize) void; const body_buf_len = 16384 - 16; const BodyBufBytes = [body_buf_len]u8; @@ -159,7 +160,7 @@ pub fn NewHTTPUpgradeClient(comptime ssl: bool) type { var client_protocol_hash: u64 = 0; var body = buildRequestBody(global.bunVM(), pathname, host, client_protocol, &client_protocol_hash) catch return null; var client: HTTPClient = HTTPClient{ - .socket = undefined, + .tcp = undefined, .outgoing_websocket = websocket, .input_body_buf = body, .websocket_protocol = client_protocol_hash, @@ -200,14 +201,14 @@ pub fn NewHTTPUpgradeClient(comptime ssl: bool) type { pub fn fail(this: *HTTPClient, code: ErrorCode) void { JSC.markBinding(); - WebSocket__didFailWithErrorCode(this.outgoing_websocket, code); + WebSocket__didCloseWithErrorCode(this.outgoing_websocket, code); this.cancel(); } pub fn handleClose(this: *HTTPClient, _: Socket, _: c_int, _: ?*anyopaque) void { JSC.markBinding(); this.clearData(); - WebSocket__didFailWithErrorCode(this.outgoing_websocket, ErrorCode.ended); + WebSocket__didCloseWithErrorCode(this.outgoing_websocket, ErrorCode.ended); } pub fn terminate(this: *HTTPClient, code: ErrorCode) void { @@ -407,6 +408,7 @@ pub fn NewHTTPUpgradeClient(comptime ssl: bool) type { this.clearData(); JSC.markBinding(); + this.tcp.timeout(0); WebSocket__didConnect(this.outgoing_websocket, this.tcp.socket, overflow.ptr, overflow.len); } @@ -500,17 +502,45 @@ pub const Mask = struct { pub fn fill(this: *Mask, mask_buf: *[4]u8, output_: []u8, input_: []const u8) void { const mask = this.get(); mask_buf.* = mask; + + const skip_mask = @bitCast(u32, mask) == 0; + if (!skip_mask) { + fillWithSkipMask(mask, output_, input_, false); + } else { + fillWithSkipMask(mask, output_, input_, true); + } + } + + fn fillWithSkipMask(mask: [4]u8, output_: []u8, input_: []const u8, comptime skip_mask: bool) void { var input = input_; var output = output_; + if (comptime Environment.isAarch64 or Environment.isX64) { if (input.len >= strings.ascii_vector_size) { - const vec: strings.AsciiVector = @as(strings.AsciiVector, mask ** (strings.ascii_vector_size / 4)); + const vec: strings.AsciiVector = brk: { + var in: [strings.ascii_vector_size]u8 = undefined; + comptime var i: usize = 0; + inline while (i < strings.ascii_vector_size) : (i += 4) { + in[i..][0..4].* = mask; + } + break :brk @as(strings.AsciiVector, in); + }; const end_ptr_wrapped_to_last_16 = input.ptr + input.len - (input.len % strings.ascii_vector_size); - while (input.ptr != end_ptr_wrapped_to_last_16) { - const input_vec: strings.AsciiVector = @as(strings.AsciiVector, input[0..strings.ascii_vector_size].*); - output.ptr[0..strings.ascii_vector_size].* = input_vec ^ vec; - output = output[strings.ascii_vector_size..]; - input = input[strings.ascii_vector_size..]; + + if (comptime skip_mask) { + while (input.ptr != end_ptr_wrapped_to_last_16) { + const input_vec: strings.AsciiVector = @as(strings.AsciiVector, input[0..strings.ascii_vector_size].*); + output.ptr[0..strings.ascii_vector_size].* = input_vec; + output = output[strings.ascii_vector_size..]; + input = input[strings.ascii_vector_size..]; + } + } else { + while (input.ptr != end_ptr_wrapped_to_last_16) { + const input_vec: strings.AsciiVector = @as(strings.AsciiVector, input[0..strings.ascii_vector_size].*); + output.ptr[0..strings.ascii_vector_size].* = input_vec ^ vec; + output = output[strings.ascii_vector_size..]; + input = input[strings.ascii_vector_size..]; + } } } @@ -518,20 +548,35 @@ pub const Mask = struct { std.debug.assert(input.len < strings.ascii_vector_size); } - while (input.len >= 4) { - const input_vec: [4]u8 = input[0..4].*; - output.ptr[0..4].* = [4]u8{ - input_vec[0] ^ mask[0], - input_vec[1] ^ mask[1], - input_vec[2] ^ mask[2], - input_vec[3] ^ mask[3], - }; - output = output[4..]; - input = input[4..]; + if (comptime !skip_mask) { + while (input.len >= 4) { + const input_vec: [4]u8 = input[0..4].*; + output.ptr[0..4].* = [4]u8{ + input_vec[0] ^ mask[0], + input_vec[1] ^ mask[1], + input_vec[2] ^ mask[2], + input_vec[3] ^ mask[3], + }; + output = output[4..]; + input = input[4..]; + } + } else { + while (input.len >= 4) { + const input_vec: [4]u8 = input[0..4].*; + output.ptr[0..4].* = input_vec; + output = output[4..]; + input = input[4..]; + } } - for (input) |c, i| { - output[i] = c ^ mask[i % 4]; + if (comptime !skip_mask) { + for (input) |c, i| { + output[i] = c ^ mask[i % 4]; + } + } else { + for (input) |c, i| { + output[i] = c; + } } } @@ -548,7 +593,8 @@ const ReceiveState = enum { extended_payload_length_16, extended_payload_length_64, ping, - closing, + pong, + close, fail, pub fn needControlFrame(this: ReceiveState) bool { @@ -629,71 +675,82 @@ const Copy = union(enum) { return WebsocketHeader.frameSizeIncludingMask(byte_len.*); }, .latin1 => { - byte_len.* = strings.elementLengthLatin1IntoUTF8([]const u8, this.latin1); + byte_len.* = this.latin1.len; return WebsocketHeader.frameSizeIncludingMask(byte_len.*); }, .bytes => { byte_len.* = this.bytes.len; return WebsocketHeader.frameSizeIncludingMask(byte_len.*); }, - .raw => return this.raw.len, + .raw => { + byte_len.* = this.raw.len; + return this.raw.len; + }, } } pub fn copy(this: @This(), globalThis: *JSC.JSGlobalObject, buf: []u8, content_byte_len: usize) void { + if (this == .raw) { + std.debug.assert(buf.len >= this.raw.len); + std.debug.assert(buf.ptr != this.raw.ptr); + @memcpy(buf.ptr, this.raw.ptr, this.raw.len); + return; + } + + const how_big_is_the_length_integer = WebsocketHeader.lengthByteCount(content_byte_len); + const how_big_is_the_mask = 4; + const mask_offset = 2 + how_big_is_the_length_integer; + const content_offset = mask_offset + how_big_is_the_mask; + + // 2 byte header + // 4 byte mask + // 0, 2, 8 byte length + var to_mask = buf[content_offset..]; + + var header = @bitCast(WebsocketHeader, @as(u16, 0)); + + // Write extended length if needed + switch (how_big_is_the_length_integer) { + 0 => {}, + 2 => std.mem.writeIntBig(u16, buf[2..][0..2], @truncate(u16, content_byte_len)), + 8 => std.mem.writeIntBig(u64, buf[2..][0..8], @truncate(u64, content_byte_len)), + else => unreachable, + } + + header.mask = true; + header.compressed = false; + header.final = true; + + std.debug.assert(WebsocketHeader.frameSizeIncludingMask(content_byte_len) == buf.len); + switch (this) { .utf16 => |utf16| { - const length_offset = 2; - const length_length = WebsocketHeader.lengthByteCount(content_byte_len); - const mask_offset = length_offset + length_length; - const content_offset = mask_offset + 4; - var to_mask = buf[content_offset..]; - const encode_into_result = strings.copyUTF16IntoUTF8(utf16, to_mask); + header.len = WebsocketHeader.packLength(content_byte_len); + const encode_into_result = strings.copyUTF16IntoUTF8(to_mask, []const u16, utf16); std.debug.assert(@as(usize, encode_into_result.written) == content_byte_len); std.debug.assert(@as(usize, encode_into_result.read) == utf16.len); - var header = @bitCast(WebsocketHeader, @as(u16, 0)); - header.len = WebsocketHeader.packLength(content_byte_len); - header.mask = true; + header.len = WebsocketHeader.packLength(encode_into_result.written); header.opcode = Opcode.Text; - header.compressed = false; - header.final = true; - header.writeHeader(std.io.fixedBufferStream(buf), content_byte_len) catch unreachable; - Mask.from(globalThis).fill(buf[mask_offset..][0..4], to_mask, to_mask); + header.writeHeader(std.io.fixedBufferStream(buf).writer(), encode_into_result.written) catch unreachable; + + Mask.from(globalThis).fill(buf[mask_offset..][0..4], to_mask[0..content_byte_len], to_mask[0..content_byte_len]); }, .latin1 => |latin1| { - const length_offset = 2; - const length_length = WebsocketHeader.lengthByteCount(content_byte_len); - const mask_offset = length_offset + length_length; - const content_offset = mask_offset + 4; - var to_mask = buf[content_offset..]; - const encode_into_result = strings.copyLatin1IntoUTF8(latin1, to_mask); + const encode_into_result = strings.copyLatin1IntoUTF8(to_mask, []const u8, latin1); std.debug.assert(@as(usize, encode_into_result.written) == content_byte_len); std.debug.assert(@as(usize, encode_into_result.read) == latin1.len); - var header = @bitCast(WebsocketHeader, @as(u16, 0)); - header.len = WebsocketHeader.packLength(content_byte_len); - header.mask = true; + header.len = WebsocketHeader.packLength(encode_into_result.written); header.opcode = Opcode.Text; - header.compressed = false; - header.final = true; - header.writeHeader(std.io.fixedBufferStream(buf), content_byte_len) catch unreachable; - Mask.from(globalThis).fill(buf[mask_offset..][0..4], to_mask, to_mask); + header.writeHeader(std.io.fixedBufferStream(buf).writer(), encode_into_result.written) catch unreachable; + Mask.from(globalThis).fill(buf[mask_offset..][0..4], to_mask[0..content_byte_len], to_mask[0..content_byte_len]); }, .bytes => |bytes| { - const length_offset = 2; - const length_length = WebsocketHeader.lengthByteCount(bytes.len); - const mask_offset = length_offset + length_length; - const content_offset = mask_offset + 4; - var to_mask = buf[content_offset..]; - var header = @bitCast(WebsocketHeader, @as(u16, 0)); header.len = WebsocketHeader.packLength(bytes.len); - header.mask = true; - header.opcode = Opcode.Text; - header.compressed = false; - header.final = true; - header.writeHeader(std.io.fixedBufferStream(buf), bytes.len) catch unreachable; - Mask.from(globalThis).fill(buf[mask_offset..][0..4], to_mask, to_mask); + header.opcode = Opcode.Binary; + header.writeHeader(std.io.fixedBufferStream(buf).writer(), bytes.len) catch unreachable; + Mask.from(globalThis).fill(buf[mask_offset..][0..4], to_mask[0..content_byte_len], bytes); }, - .raw => @memcpy(buf.ptr, this.raw.ptr, this.raw.len), + .raw => unreachable, } } }; @@ -709,7 +766,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { receive_remaining: usize = 0, receiving_type: Opcode = Opcode.ResB, - ping_frame_bytes: [128 + 6]u8 = [_]u8{0} ** 128 + 6, + ping_frame_bytes: [128 + 6]u8 = [_]u8{0} ** (128 + 6), ping_len: u8 = 0, receive_frame: usize = 0, @@ -717,9 +774,8 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { receive_pending_chunk_len: usize = 0, receive_body_buf: ?*BodyBuf = null, receive_overflow_buffer: std.ArrayListUnmanaged(u8) = .{}, - send_overflow_buffer: std.ArrayListUnmanaged(u8) = .{}, - send_body_buf: ?*BodyBuf = null, + send_buffer: bun.LinearFifo(u8, .Dynamic), send_len: usize = 0, send_off: usize = 0, @@ -728,10 +784,11 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { pub const name = if (ssl) "WebSocketClientTLS" else "WebSocketClient"; pub const shim = JSC.Shimmer("Bun", name, @This()); + const stack_frame_size = 1024; - const HTTPClient = @This(); + const WebSocket = @This(); - pub fn register(global: *JSC.JSGlobalObject, loop_: *anyopaque, parent: *anyopaque, ctx_: *anyopaque) callconv(.C) void { + pub fn register(global: *JSC.JSGlobalObject, loop_: *anyopaque, ctx_: *anyopaque) callconv(.C) void { var vm = global.bunVM(); var loop = @ptrCast(*uws.Loop, loop_); var ctx: *uws.us_socket_context_t = @ptrCast(*uws.us_socket_context_t, ctx_); @@ -742,10 +799,9 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { vm.uws_event_loop = loop; - Socket.configureChild( + Socket.configure( ctx, - parent, - HTTPClient, + WebSocket, null, handleClose, handleData, @@ -756,7 +812,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { ); } - pub fn clearData(this: *HTTPClient) void { + pub fn clearData(this: *WebSocket) void { this.clearReceiveBuffers(true); this.clearSendBuffers(true); this.ping_len = 0; @@ -764,7 +820,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { this.receive_remaining = 0; } - pub fn cancel(this: *HTTPClient) callconv(.C) void { + pub fn cancel(this: *WebSocket) callconv(.C) void { this.clearData(); if (this.tcp.isClosed() or this.tcp.isShutdown()) @@ -777,34 +833,34 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { } } - pub fn fail(this: *HTTPClient, code: ErrorCode) void { + pub fn fail(this: *WebSocket, code: ErrorCode) void { JSC.markBinding(); if (this.outgoing_websocket) |ws| - WebSocket__didFailWithErrorCode(ws, code); + WebSocket__didCloseWithErrorCode(ws, code); this.cancel(); } - pub fn handleClose(this: *HTTPClient, _: Socket, _: c_int, _: ?*anyopaque) void { + pub fn handleClose(this: *WebSocket, _: Socket, _: c_int, _: ?*anyopaque) void { JSC.markBinding(); this.clearData(); if (this.outgoing_websocket) |ws| - WebSocket__didFailWithErrorCode(ws, ErrorCode.ended); + WebSocket__didCloseWithErrorCode(ws, ErrorCode.ended); } - pub fn terminate(this: *HTTPClient, code: ErrorCode) void { + pub fn terminate(this: *WebSocket, code: ErrorCode) void { this.fail(code); } - fn getBody(this: *HTTPClient) *BodyBufBytes { - if (this.send_body_buf == null) { - this.send_body_buf = BodyBufPool.get(bun.default_allocator); - } + // fn getBody(this: *WebSocket) *BodyBufBytes { + // if (this.send_body_buf == null) { + // this.send_body_buf = BodyBufPool.get(bun.default_allocator); + // } - return &this.send_body_buf.?.data; - } + // return &this.send_body_buf.?.data; + // } - fn getReceiveBody(this: *HTTPClient) *BodyBufBytes { + fn getReceiveBody(this: *WebSocket) *BodyBufBytes { if (this.receive_body_buf == null) { this.receive_body_buf = BodyBufPool.get(bun.default_allocator); } @@ -812,7 +868,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { return &this.receive_body_buf.?.data; } - fn clearReceiveBuffers(this: *HTTPClient, free: bool) void { + fn clearReceiveBuffers(this: *WebSocket, free: bool) void { if (this.receive_body_buf) |receive_buf| { receive_buf.release(); this.receive_body_buf = null; @@ -825,50 +881,58 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { } } - fn clearSendBuffers(this: *HTTPClient, free: bool) void { - if (this.send_body_buf) |buf| { - buf.release(); - this.send_body_buf = null; - } + fn clearSendBuffers(this: *WebSocket, free: bool) void { + // if (this.send_body_buf) |buf| { + // buf.release(); + // this.send_body_buf = null; + // } + this.send_buffer.discard(this.send_buffer.count); if (free) { - this.send_overflow_buffer.clearAndFree(bun.default_allocator); - } else { - this.send_overflow_buffer.clearRetainingCapacity(); + this.send_buffer.deinit(); + this.send_buffer.buf.len = 0; } + this.send_off = 0; this.send_len = 0; } - fn dispatchData(this: *HTTPClient, data_: []const u8, kind: Opcode) void { + fn dispatchData(this: *WebSocket, data_: []const u8, kind: Opcode) void { + var out = this.outgoing_websocket orelse { + this.clearData(); + return; + }; switch (kind) { .Text => { // this function encodes to UTF-16 if > 127 // so we don't need to worry about latin1 non-ascii code points const utf16_bytes_ = strings.toUTF16Alloc(bun.default_allocator, data_, true) catch { this.terminate(ErrorCode.invalid_utf8); - return 0; + return; }; defer this.clearReceiveBuffers(false); var outstring = JSC.ZigString.Empty; if (utf16_bytes_) |utf16| { outstring = JSC.ZigString.from16Slice(utf16); outstring.markUTF16(); - WebSocket__didReceiveText(this.outgoing_websocket, false, &outstring); + JSC.markBinding(); + WebSocket__didReceiveText(out, false, &outstring); } else { outstring = JSC.ZigString.init(data_); - WebSocket__didReceiveText(this.outgoing_websocket, true, &outstring); + JSC.markBinding(); + WebSocket__didReceiveText(out, true, &outstring); } }, .Binary => { - WebSocket__didReceiveBytes(this.outgoing_websocket, data_); + JSC.markBinding(); + WebSocket__didReceiveBytes(out, data_.ptr, data_.len); this.clearReceiveBuffers(false); }, else => unreachable, } } - pub fn consume(this: *HTTPClient, data_: []const u8, max: usize, kind: Opcode, is_final: bool) usize { + pub fn consume(this: *WebSocket, data_: []const u8, max: usize, kind: Opcode, is_final: bool) usize { std.debug.assert(kind == .Text or kind == .Binary); std.debug.assert(data_.len <= max); @@ -898,7 +962,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { if (new_pending_chunk_len <= body_buf_len) { // if our previously-allocated buffer is too small or we don't have one, use from the pool var body = this.getReceiveBody(); - @memcpy(body + this.receive_pending_chunk_len, data_.ptr, data_.len); + @memcpy(body[this.receive_pending_chunk_len..].ptr, data_.ptr, data_.len); if (can_dispatch_data) { this.dispatchData(body[0..new_pending_chunk_len], kind); this.receive_pending_chunk_len = 0; @@ -924,7 +988,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { } } - pub fn handleData(this: *HTTPClient, socket: Socket, data_: []const u8) void { + pub fn handleData(this: *WebSocket, socket: Socket, data_: []const u8) void { var data = data_; var receive_state = this.receive_state; var terminated = false; @@ -983,7 +1047,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { is_final = false; receive_state = parseWebSocketHeader( - header_bytes[0..2], + header_bytes[0..2].*, &receiving_type, &receive_body_remain, &is_fragmented, @@ -1005,7 +1069,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { switch (receiving_type) { .Continue, .Text, .Binary, .Ping, .Pong, .Close => {}, else => { - this.terminate(ErrorCode.unexpected_opcode); + this.terminate(ErrorCode.unsupported_control_frame); terminated = true; break; }, @@ -1037,8 +1101,8 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { // Multibyte length quantities are expressed in network byte order receive_body_remain = switch (byte_size) { - 8 => @as(usize, std.mem.readIntBig(u64, data[0..8].*)), - 2 => @as(usize, std.mem.readIntBig(u16, data[0..2].*)), + 8 => @as(usize, std.mem.readIntBig(u64, data[0..8])), + 2 => @as(usize, std.mem.readIntBig(u16, data[0..2])), else => unreachable, }; data = data[byte_size..]; @@ -1058,7 +1122,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { this.ping_len = @truncate(u8, ping_len); if (ping_len > 0) { - @memcpy(&this.ping_frame_bytes + 6, data.ptr, ping_len); + @memcpy(this.ping_frame_bytes[6..], data.ptr, ping_len); data = data[ping_len..]; } @@ -1110,7 +1174,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { break; }, .fail => { - this.terminate(ErrorCode.unexpected_control_frame); + this.terminate(ErrorCode.unsupported_control_frame); terminated = true; break; }, @@ -1118,18 +1182,18 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { } } - pub fn sendClose(this: *HTTPClient) void { - this.sendCloseWithBody(this.tcp, 1001, null, null, 0); + pub fn sendClose(this: *WebSocket) void { + this.sendCloseWithBody(this.tcp, 1001, null, 0); } fn enqueueEncodedBytesMaybeFinal( - this: *HTTPClient, + this: *WebSocket, socket: Socket, bytes: []const u8, is_closing: bool, ) bool { // fast path: no backpressure, no queue, just send the bytes. - if (this.send_len == 0) { + if (!this.hasBackpressure()) { const wrote = socket.write(bytes, !is_closing); const expected = @intCast(c_int, bytes.len); if (wrote == expected) { @@ -1141,82 +1205,87 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { return false; } - _ = this.copyToSendBuffer(bytes[bytes.len - @intCast(usize, wrote) ..], false, is_closing, false); + _ = this.copyToSendBuffer(bytes[@intCast(usize, wrote)..], false, is_closing); return true; } - return this.copyToSendBuffer(bytes, true, is_closing, false); + return this.copyToSendBuffer(bytes, true, is_closing); } - fn copyToSendBuffer(this: *HTTPClient, bytes: []const u8, do_write: bool, is_closing: bool) bool { + fn copyToSendBuffer(this: *WebSocket, bytes: []const u8, do_write: bool, is_closing: bool) bool { return this.sendData(.{ .raw = bytes }, do_write, is_closing); } - fn sendData(this: *HTTPClient, bytes: Copy, do_write: bool, is_closing: bool) bool { + fn sendData(this: *WebSocket, bytes: Copy, do_write: bool, is_closing: bool) bool { var content_byte_len: usize = 0; const write_len = bytes.len(&content_byte_len); std.debug.assert(write_len > 0); - this.send_len += write_len; - var out_buf: []const u8 = ""; - const send_end = this.send_off + this.send_len; - var ring_buffer = &this.send_overflow_buffer; - - if (send_end <= ring_buffer.capacity) { - bytes.copy(this.globalThis, ring_buffer.items.ptr[send_end - write_len .. send_end], content_byte_len); - ring_buffer.items.len += write_len; - - out_buf = ring_buffer.items[this.send_off..send_end]; - } else if (send_end <= body_buf_len) { - var buf = this.getBody(); - bytes.copy(this.globalThis, buf[send_end - write_len ..][0..write_len], content_byte_len); - out_buf = buf[this.send_off..send_end]; - } else { - if (this.send_body_buf) |send_body| { - // transfer existing send buffer to overflow buffer - ring_buffer.ensureTotalCapacityPrecise(bun.default_allocator, this.send_len) catch { - this.terminate(ErrorCode.failed_to_allocate_memory); - return false; - }; - const off = this.send_len - write_len; - @memcpy(ring_buffer.items.ptr, send_body + this.send_off, off); - send_body.release(); - bytes.copy(this.globalThis, (ring_buffer.items.ptr + off)[0..write_len], content_byte_len); - ring_buffer.items.len = this.send_len; - this.send_body_buf = null; - this.send_off = 0; - } else if (send_end <= ring_buffer.capacity) { - bytes.copy(this.globalThis, (ring_buffer.items.ptr + (send_end - write_len))[0..write_len], content_byte_len); - ring_buffer.items.len += write_len; - // can we treat it as a ring buffer without re-allocating the array? - } else if (send_end - this.send_off <= ring_buffer.capacity) { - std.mem.copyBackwards(u8, ring_buffer.items[0..this.send_len], ring_buffer.items[this.send_off..send_end]); - bytes.copy(this.globalThis, ring_buffer.items.ptr[this.send_len - write_len .. this.send_len], content_byte_len); - ring_buffer.items.len = this.send_len; - this.send_off = 0; - } else { - // we need to re-allocate the array - ring_buffer.ensureTotalCapacity(bun.default_allocator, this.send_len) catch { - this.terminate(ErrorCode.failed_to_allocate_memory); - return false; - }; - - const data_to_copy = ring_buffer.items[this.send_off..][0..(this.send_len - write_len)]; - const position_in_slice = ring_buffer.items[0 .. this.send_len - write_len]; - if (data_to_copy.len > 0 and data_to_copy.ptr != position_in_slice.ptr) - std.mem.copyBackwards( - u8, - position_in_slice, - data_to_copy, - ); - - ring_buffer.items.len = this.send_len; - bytes.copy(this.globalThis, ring_buffer.items[this.send_len - write_len ..], content_byte_len); - this.send_off = 0; - } - - out_buf = ring_buffer.items[this.send_off..send_end]; - } + // this.send_len += write_len; + var writable = this.send_buffer.writableWithSize(write_len) catch unreachable; + bytes.copy(this.globalThis, writable[0..write_len], content_byte_len); + this.send_buffer.update(write_len); + // const send_end = this.send_off + this.send_len; + // var ring_buffer = &this.send_overflow_buffer; + // const prev_len = this.send_len - write_len; + + // if (send_end <= ring_buffer.capacity) { + // const mid = ring_buffer.capacity / 2; + // if (send_end > mid and this.send_len < mid) { + // std.mem.copyBackwards(u8, ring_buffer.items.ptr[0..prev_len], ring_buffer.items.ptr[this.send_off..ring_buffer.capacity][0..prev_len]); + // this.send_off = 0; + // ring_buffer.items.len = prev_len; + // bytes.copy(this.globalThis, ring_buffer.items.ptr[prev_len..this.send_len], content_byte_len); + // } else { + // bytes.copy(this.globalThis, ring_buffer.items.ptr[send_end - write_len .. send_end], content_byte_len); + // } + // ring_buffer.items.len += write_len; + + // out_buf = ring_buffer.items[this.send_off..][0..this.send_len]; + // } else if (send_end <= body_buf_len) { + // var buf = this.getBody(); + // bytes.copy(this.globalThis, buf[send_end - write_len ..][0..write_len], content_byte_len); + // out_buf = buf[this.send_off..][0..this.send_len]; + // } else { + // if (this.send_body_buf) |send_body| { + // var buf = &send_body.data; + // // transfer existing send buffer to overflow buffer + // ring_buffer.ensureTotalCapacityPrecise(bun.default_allocator, this.send_len) catch { + // this.terminate(ErrorCode.failed_to_allocate_memory); + // return false; + // }; + // @memcpy(ring_buffer.items.ptr, buf[this.send_off..].ptr, prev_len); + // send_body.release(); + // bytes.copy(this.globalThis, (ring_buffer.items.ptr + prev_len)[0..write_len], content_byte_len); + // ring_buffer.items.len = this.send_len; + // this.send_body_buf = null; + // this.send_off = 0; + // } else if (send_end <= ring_buffer.capacity) { + // bytes.copy(this.globalThis, (ring_buffer.items.ptr + (send_end - write_len))[0..write_len], content_byte_len); + // ring_buffer.items.len += write_len; + // } else { + // // we need to re-allocate the array + // ring_buffer.ensureTotalCapacity(bun.default_allocator, this.send_len) catch { + // this.terminate(ErrorCode.failed_to_allocate_memory); + // return false; + // }; + + // const data_to_copy = ring_buffer.items[this.send_off..][0..prev_len]; + // const position_in_slice = ring_buffer.items[0..prev_len]; + // if (data_to_copy.len > 0 and data_to_copy.ptr != position_in_slice.ptr) + // std.mem.copyBackwards( + // u8, + // position_in_slice, + // data_to_copy, + // ); + + // ring_buffer.items.len = this.send_len; + // bytes.copy(this.globalThis, ring_buffer.items[prev_len..], content_byte_len); + // this.send_off = 0; + // } + + // out_buf = ring_buffer.items[this.send_off..][0..this.send_len]; + // } if (do_write) { if (comptime Environment.allow_assert) { @@ -1224,61 +1293,66 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { std.debug.assert(!this.tcp.isClosed()); std.debug.assert(this.tcp.isEstablished()); } - return this.sendBuffer(out_buf, is_closing); + return this.sendBuffer(this.send_buffer.readableSlice(0), is_closing, !is_closing); } return true; } - fn sendBuffer(this: *HTTPClient, out_buf: []const u8, is_closing: bool) bool { + fn sendBuffer( + this: *WebSocket, + out_buf: []const u8, + is_closing: bool, + _: bool, + ) bool { std.debug.assert(out_buf.len > 0); - const wrote = this.tcp.write(out_buf, !is_closing); - const expected = @intCast(c_int, out_buf.len); - if (wrote == expected) { - this.clearSendBuffers(false); - return true; - } - + _ = is_closing; + // set msg_more to false + // it seems to improve perf by ~20% + const wrote = this.tcp.write(out_buf, false); if (wrote < 0) { this.terminate(ErrorCode.failed_to_write); return false; } + const expected = @intCast(usize, wrote); + var readable = this.send_buffer.readableSlice(0); + if (readable.ptr == out_buf.ptr) { + this.send_buffer.discard(expected); + } - this.send_len -= @intCast(usize, wrote); - this.send_off += @intCast(usize, wrote); return true; } - fn enqueueEncodedBytes(this: *HTTPClient, socket: Socket, bytes: []const u8) bool { + fn enqueueEncodedBytes(this: *WebSocket, socket: Socket, bytes: []const u8) bool { return this.enqueueEncodedBytesMaybeFinal(socket, bytes, false); } - fn sendPong(this: *HTTPClient, socket: Socket) bool { + fn sendPong(this: *WebSocket, socket: Socket) bool { if (socket.isClosed() or socket.isShutdown()) { this.dispatchClose(); return false; } var header = @bitCast(WebsocketHeader, @as(u16, 0)); - header.fin = true; + header.final = true; header.opcode = .Pong; - var to_mask = &this.ping_frame_bytes[6..][0..this.ping_len]; + var to_mask = this.ping_frame_bytes[6..][0..this.ping_len]; header.mask = to_mask.len > 0; header.len = @truncate(u7, this.ping_len); this.ping_frame_bytes[0..2].* = @bitCast(u16, header); if (to_mask.len > 0) { - Mask.from(this.globalThis).fill(&this.ping_frame_bytes[2..6], &to_mask, &to_mask); + Mask.from(this.globalThis).fill(this.ping_frame_bytes[2..6], to_mask, to_mask); return this.enqueueEncodedBytes(socket, this.ping_frame_bytes[0 .. 6 + @as(usize, this.ping_len)]); } else { - return this.enqueueEncodedBytes(socket, &this.ping_frame_bytes[0..2]); + return this.enqueueEncodedBytes(socket, this.ping_frame_bytes[0..2]); } } fn sendCloseWithBody( - this: *HTTPClient, + this: *WebSocket, socket: Socket, code: u16, body: ?*[125]u8, @@ -1293,16 +1367,16 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { socket.shutdownRead(); var final_body_bytes: [128 + 8]u8 = undefined; var header = @bitCast(WebsocketHeader, @as(u16, 0)); - header.fin = true; + header.final = true; header.opcode = .Close; header.mask = true; - header.len = body_len + 2; + header.len = @truncate(u7, body_len + 2); final_body_bytes[0..2].* = @bitCast([2]u8, @bitCast(u16, header)); - var mask_buf = &final_body_bytes[2..6]; - std.mem.writeIntSliceBig(u16, &final_body_bytes[6..8], code); + var mask_buf: *[4]u8 = final_body_bytes[2..6]; + std.mem.writeIntSliceBig(u16, final_body_bytes[6..8], code); if (body) |data| { - if (body_len > 0) @memcpy(&final_body_bytes[8..], data, body_len); + if (body_len > 0) @memcpy(final_body_bytes[8..], data, body_len); } // we must mask the code @@ -1315,41 +1389,37 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { } } - pub fn handleEnd(this: *HTTPClient, socket: Socket) void { + pub fn handleEnd(this: *WebSocket, socket: Socket) void { std.debug.assert(socket.socket == this.tcp.socket); this.terminate(ErrorCode.ended); } pub fn handleWritable( - this: *HTTPClient, + this: *WebSocket, socket: Socket, ) void { std.debug.assert(socket.socket == this.tcp.socket); - if (this.send_len == 0) + const send_buf = this.send_buffer.readableSlice(0); + if (send_buf.len == 0) return; - - var send_buf: []const u8 = undefined; - if (this.send_body_buf) |send_body_buf| { - send_buf = send_body_buf[this.send_off..][0..this.send_len]; - std.debug.assert(this.send_overflow_buffer.items.len == 0); - } else { - send_buf = this.send_overflow_buffer.items[this.send_off..][0..this.send_len]; - } - std.debug.assert(send_buf.len == this.send_len); - _ = this.sendBuffer(send_buf, false); + _ = this.sendBuffer(send_buf, false, true); } pub fn handleTimeout( - this: *HTTPClient, + this: *WebSocket, _: Socket, ) void { this.terminate(ErrorCode.timeout); } - pub fn handleConnectError(this: *HTTPClient, _: Socket, _: c_int) void { + pub fn handleConnectError(this: *WebSocket, _: Socket, _: c_int) void { this.terminate(ErrorCode.failed_to_connect); } + pub fn hasBackpressure(this: *const WebSocket) bool { + return this.send_buffer.count > 0; + } + pub fn writeBinaryData( - this: *HTTPClient, + this: *WebSocket, ptr: [*]const u8, len: usize, ) callconv(.C) void { @@ -1365,17 +1435,17 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { const bytes = Copy{ .bytes = slice }; // fast path: small frame, no backpressure, attempt to send without allocating const frame_size = WebsocketHeader.frameSizeIncludingMask(len); - if (this.send_len == 0 and frame_size < 128 + 4) { - var inline_buf: [128 + 4]u8 = undefined; - bytes.copy(this.globalThis, &inline_buf[0..frame_size], slice.len); - _ = this.enqueueEncodedBytes(this.socket, inline_buf[0..frame_size]); + if (!this.hasBackpressure() and frame_size < stack_frame_size) { + var inline_buf: [stack_frame_size]u8 = undefined; + bytes.copy(this.globalThis, inline_buf[0..frame_size], slice.len); + _ = this.enqueueEncodedBytes(this.tcp, inline_buf[0..frame_size]); return; } - _ = this.sendData(bytes, true, false); + _ = this.sendData(bytes, !this.hasBackpressure(), false); } pub fn writeString( - this: *HTTPClient, + this: *WebSocket, str_: *const JSC.ZigString, ) callconv(.C) void { const str = str_.*; @@ -1389,25 +1459,25 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { } { - var inline_buf: [128 + 4]u8 = undefined; + var inline_buf: [stack_frame_size]u8 = undefined; // fast path: small frame, no backpressure, attempt to send without allocating - if (!str.is16Bit() and str.len < 128 + 4) { + if (!str.is16Bit() and str.len < stack_frame_size) { const bytes = Copy{ .latin1 = str.slice() }; const frame_size = WebsocketHeader.frameSizeIncludingMask(str.len); - if (this.send_len == 0 and frame_size < 128 + 4) { - bytes.copy(this.globalThis, &inline_buf[0..frame_size], str.len); - _ = this.enqueueEncodedBytes(this.socket, inline_buf[0..frame_size]); + if (!this.hasBackpressure() and frame_size < stack_frame_size) { + bytes.copy(this.globalThis, inline_buf[0..frame_size], str.len); + _ = this.enqueueEncodedBytes(this.tcp, inline_buf[0..frame_size]); return; } // max length of a utf16 -> utf8 conversion is 4 times the length of the utf16 string - } else if ((str.len * 4) < (128 + 4) and this.send_len == 0) { - const bytes = Copy{ .utf16 = str.slice() }; + } else if ((str.len * 4) < (stack_frame_size) and !this.hasBackpressure()) { + const bytes = Copy{ .utf16 = str.utf16SliceAligned() }; var byte_len: usize = 0; const frame_size = bytes.len(&byte_len); - std.debug.assert(frame_size <= 128 + 4); - bytes.copy(this.globalThis, &inline_buf[0..frame_size], byte_len); - _ = this.enqueueEncodedBytes(this.socket, inline_buf[0..frame_size]); + std.debug.assert(frame_size <= stack_frame_size); + bytes.copy(this.globalThis, inline_buf[0..frame_size], byte_len); + _ = this.enqueueEncodedBytes(this.tcp, inline_buf[0..frame_size]); return; } } @@ -1417,12 +1487,18 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { Copy{ .utf16 = str.utf16SliceAligned() } else Copy{ .latin1 = str.slice() }, - true, + !this.hasBackpressure(), false, ); } - pub fn close(this: *HTTPClient, code: u16, reason: ?*const JSC.ZigString) callconv(.C) void { + fn dispatchClose(this: *WebSocket) void { + var out = this.outgoing_websocket orelse return; + JSC.markBinding(); + WebSocket__didCloseWithErrorCode(out, ErrorCode.closed); + } + + pub fn close(this: *WebSocket, code: u16, reason: ?*const JSC.ZigString) callconv(.C) void { if (this.tcp.isClosed() or this.tcp.isShutdown()) return; @@ -1430,8 +1506,8 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { if (reason) |str| { inner: { var fixed_buffer = std.heap.FixedBufferAllocator.init(&close_reason_buf); - const allocator = fixed_buffer.get(); - const wrote = std.fmt.allocPrint(allocator, "{}", str.*) catch break :inner; + const allocator = fixed_buffer.allocator(); + const wrote = std.fmt.allocPrint(allocator, "{}", .{str.*}) catch break :inner; this.sendCloseWithBody(this.tcp, code, wrote.ptr[0..125], wrote.len); return; } @@ -1445,20 +1521,31 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { input_socket: *anyopaque, socket_ctx: *anyopaque, globalThis: *JSC.JSGlobalObject, - ) callconv(.C) *anyopaque { + ) callconv(.C) ?*anyopaque { var tcp = @ptrCast(*uws.Socket, input_socket); var ctx = @ptrCast(*uws.us_socket_context_t, socket_ctx); - - return @ptrCast( - *anyopaque, - Socket.adopt(tcp, ctx, HTTPClient{ + var adopted = Socket.adopt( + tcp, + ctx, + WebSocket, + "tcp", + WebSocket{ + .tcp = undefined, .outgoing_websocket = outgoing, .globalThis = globalThis, - }), + .receive_overflow_buffer = .{}, + .send_buffer = bun.LinearFifo(u8, .Dynamic).init(bun.default_allocator), + }, + ) orelse return null; + adopted.send_buffer.ensureTotalCapacity(2048) catch return null; + _ = globalThis.bunVM().eventLoop().ready_tasks_count.fetchAdd(1, .Monotonic); + return @ptrCast( + *anyopaque, + adopted, ); } - pub fn finalize(this: *HTTPClient) callconv(.C) void { + pub fn finalize(this: *WebSocket) callconv(.C) void { this.clearData(); if (this.tcp.isClosed()) @@ -1466,6 +1553,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { this.tcp.close(0, null); this.outgoing_websocket = null; + _ = this.globalThis.bunVM().eventLoop().ready_tasks_count.fetchSub(1, .Monotonic); } pub const Export = shim.exportFunctions(.{ diff --git a/src/javascript/jsc/bindings/ScriptExecutionContext.cpp b/src/javascript/jsc/bindings/ScriptExecutionContext.cpp index 9c6735993..73b8b4c01 100644 --- a/src/javascript/jsc/bindings/ScriptExecutionContext.cpp +++ b/src/javascript/jsc/bindings/ScriptExecutionContext.cpp @@ -55,73 +55,25 @@ us_socket_context_t* ScriptExecutionContext::webSocketContextNoSSL() } template<bool SSL> -static uWS::WebSocketContext<SSL, false, WebCore::WebSocket*>* registerWebSocketClientContext(ScriptExecutionContext* script, us_socket_context_t* parent) +static us_socket_context_t* registerWebSocketClientContext(ScriptExecutionContext* script, us_socket_context_t* parent) { - uWS::Loop* loop = uWS::Loop::get(); - uWS::WebSocketContext<SSL, false, WebCore::WebSocket*>* ctx = uWS::WebSocketContext<SSL, false, WebCore::WebSocket*>::createClient(loop, parent); - - auto* opts = ctx->getExt(); - - /* Maximum message size we can receive */ - unsigned int maxPayloadLength = 16 * 1024; - /* 2 minutes timeout is good */ - unsigned short idleTimeout = 120; - /* 64kb backpressure is probably good */ - unsigned int maxBackpressure = 64 * 1024; - bool closeOnBackpressureLimit = false; - /* This one depends on kernel timeouts and is a bad default */ - bool resetIdleTimeoutOnSend = false; - /* A good default, esp. for newcomers */ - bool sendPingsAutomatically = false; - /* Maximum socket lifetime in seconds before forced closure (defaults to disabled) */ - unsigned short maxLifetime = 0; - - opts->maxPayloadLength = maxPayloadLength; - opts->maxBackpressure = maxBackpressure; - opts->closeOnBackpressureLimit = closeOnBackpressureLimit; - opts->resetIdleTimeoutOnSend = resetIdleTimeoutOnSend; - opts->sendPingsAutomatically = sendPingsAutomatically; - // opts->compression = compression; - // TODO: - opts->compression = uWS::CompressOptions::DISABLED; - - opts->openHandler = [](uWS::WebSocket<SSL, false, WebCore::WebSocket*>* ws) { - WebCore::WebSocket* webSocket = *ws->getUserData(); - webSocket->didConnect(); - }; - - opts->messageHandler = [](uWS::WebSocket<SSL, false, WebCore::WebSocket*>* ws, std::string_view input, uWS::OpCode opCode) { - WebCore::WebSocket* webSocket = *ws->getUserData(); - if (opCode == uWS::OpCode::BINARY) { - webSocket->didReceiveBinaryData({ const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(input.data())), input.length() }); - } else { - webSocket->didReceiveMessage(WTF::String::fromUTF8(input.data(), input.length())); - } - }; - - // pts->drainHandler = [](uWS::WebSocket<SSL, false, WebCore::WebSocket>* ws, std::string_view input, uWS::OpCode opCode) { - // WebCore::WebSocket* webSocket = *ws->getUserData(); - // webSocket->didReceiveData(input.data(), input.length()); - // }; - - opts->closeHandler = [](uWS::WebSocket<SSL, false, WebCore::WebSocket*>* ws, int code, std::string_view message) { - WebCore::WebSocket* webSocket = *ws->getUserData(); - webSocket->didClose( - ws->getBufferedAmount(), - code, - WTF::String::fromUTF8( - message.data(), - message.length())); - }; - - return ctx; + us_loop_t* loop = (us_loop_t*)uWS::Loop::get(); + if constexpr (SSL) { + us_socket_context_t* child = us_create_child_socket_context(1, parent, sizeof(size_t)); + Bun__WebSocketClientTLS__register(script->jsGlobalObject(), loop, child); + return child; + } else { + us_socket_context_t* child = us_create_child_socket_context(0, parent, sizeof(size_t)); + Bun__WebSocketClient__register(script->jsGlobalObject(), loop, child); + return child; + } } -uWS::WebSocketContext<false, false, WebSocket*>* ScriptExecutionContext::connectedWebSocketKindClient() +us_socket_context_t* ScriptExecutionContext::connectedWebSocketKindClient() { return registerWebSocketClientContext<false>(this, webSocketContextNoSSL()); } -uWS::WebSocketContext<true, false, WebSocket*>* ScriptExecutionContext::connectedWebSocketKindClientSSL() +us_socket_context_t* ScriptExecutionContext::connectedWebSocketKindClientSSL() { return registerWebSocketClientContext<true>(this, webSocketContextSSL()); } diff --git a/src/javascript/jsc/bindings/ScriptExecutionContext.h b/src/javascript/jsc/bindings/ScriptExecutionContext.h index ea8302356..e5644c2fc 100644 --- a/src/javascript/jsc/bindings/ScriptExecutionContext.h +++ b/src/javascript/jsc/bindings/ScriptExecutionContext.h @@ -118,18 +118,18 @@ private: us_socket_context_t* webSocketContextSSL(); us_socket_context_t* webSocketContextNoSSL(); - uWS::WebSocketContext<true, false, WebSocket*>* connectedWebSocketKindClientSSL(); - uWS::WebSocketContext<false, false, WebSocket*>* connectedWebSocketKindClient(); + us_socket_context_t* connectedWebSocketKindClientSSL(); + us_socket_context_t* connectedWebSocketKindClient(); us_socket_context_t* m_ssl_client_websockets_ctx = nullptr; us_socket_context_t* m_client_websockets_ctx = nullptr; - uWS::WebSocketContext<true, false, WebSocket*>* m_connected_ssl_client_websockets_ctx = nullptr; - uWS::WebSocketContext<false, false, WebSocket*>* m_connected_client_websockets_ctx = nullptr; + us_socket_context_t* m_connected_ssl_client_websockets_ctx = nullptr; + us_socket_context_t* m_connected_client_websockets_ctx = nullptr; public: template<bool isSSL, bool isServer> - uWS::WebSocketContext<isSSL, isServer, WebSocket*>* connnectedWebSocketContext() + us_socket_context_t* connnectedWebSocketContext() { if constexpr (isSSL) { if (!m_connected_ssl_client_websockets_ctx) { diff --git a/src/javascript/jsc/bindings/headers-cpp.h b/src/javascript/jsc/bindings/headers-cpp.h index 8d44b538a..ebf00db91 100644 --- a/src/javascript/jsc/bindings/headers-cpp.h +++ b/src/javascript/jsc/bindings/headers-cpp.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1655526456 +//-- AUTOGENERATED FILE -- 1655637924 // clang-format off #pragma once diff --git a/src/javascript/jsc/bindings/headers-handwritten.h b/src/javascript/jsc/bindings/headers-handwritten.h index 995393b93..3f0bf5327 100644 --- a/src/javascript/jsc/bindings/headers-handwritten.h +++ b/src/javascript/jsc/bindings/headers-handwritten.h @@ -189,6 +189,8 @@ typedef struct StringPointer { typedef void WebSocketHTTPClient; typedef void WebSocketHTTPSClient; +typedef void WebSocketClient; +typedef void WebSocketClientTLS; #ifdef __cplusplus diff --git a/src/javascript/jsc/bindings/headers-replacements.zig b/src/javascript/jsc/bindings/headers-replacements.zig index 80f3dd6dc..712a63454 100644 --- a/src/javascript/jsc/bindings/headers-replacements.zig +++ b/src/javascript/jsc/bindings/headers-replacements.zig @@ -68,3 +68,5 @@ pub const ArrayBufferSink = @import("../webcore/streams.zig").ArrayBufferSink; pub const WebSocketHTTPClient = bindings.WebSocketHTTPClient; pub const WebSocketHTTPSClient = bindings.WebSocketHTTPSClient; +pub const WebSocketClient = bindings.WebSocketClient; +pub const WebSocketClientTLS = bindings.WebSocketClientTLS; diff --git a/src/javascript/jsc/bindings/headers.h b/src/javascript/jsc/bindings/headers.h index 0a23cdd2c..3db8ed275 100644 --- a/src/javascript/jsc/bindings/headers.h +++ b/src/javascript/jsc/bindings/headers.h @@ -1,5 +1,5 @@ // clang-format: off -//-- AUTOGENERATED FILE -- 1655526456 +//-- AUTOGENERATED FILE -- 1655637924 #pragma once #include <stddef.h> @@ -7,11 +7,11 @@ #include <stdbool.h> #ifdef __cplusplus -#define AUTO_EXTERN_C extern "C" -#define AUTO_EXTERN_C_ZIG extern "C" __attribute__((weak)) + #define AUTO_EXTERN_C extern "C" + #define AUTO_EXTERN_C_ZIG extern "C" __attribute__((weak)) #else -#define AUTO_EXTERN_C -#define AUTO_EXTERN_C_ZIG __attribute__((weak)) + #define AUTO_EXTERN_C + #define AUTO_EXTERN_C_ZIG __attribute__((weak)) #endif #define ZIG_DECL AUTO_EXTERN_C_ZIG #define CPP_DECL AUTO_EXTERN_C @@ -26,287 +26,246 @@ typedef void* JSClassRef; #include "JavaScriptCore/JSClassRef.h" #endif #include "headers-handwritten.h" -typedef struct bJSC__SourceCode { - unsigned char bytes[24]; -} bJSC__SourceCode; -typedef char* bJSC__SourceCode_buf; -typedef struct bWTF__URL { - unsigned char bytes[40]; -} bWTF__URL; -typedef char* bWTF__URL_buf; -typedef struct bJSC__JSModuleRecord { - unsigned char bytes[216]; -} bJSC__JSModuleRecord; -typedef char* bJSC__JSModuleRecord_buf; -typedef struct bJSC__ThrowScope { - unsigned char bytes[8]; -} bJSC__ThrowScope; -typedef char* bJSC__ThrowScope_buf; -typedef struct bJSC__PropertyName { - unsigned char bytes[8]; -} bJSC__PropertyName; -typedef char* bJSC__PropertyName_buf; -typedef struct bJSC__JSFunction { - unsigned char bytes[32]; -} bJSC__JSFunction; -typedef char* bJSC__JSFunction_buf; -typedef struct bJSC__JSGlobalObject { - unsigned char bytes[2312]; -} bJSC__JSGlobalObject; -typedef char* bJSC__JSGlobalObject_buf; -typedef struct bJSC__JSCell { - unsigned char bytes[8]; -} bJSC__JSCell; -typedef char* bJSC__JSCell_buf; -typedef struct bJSC__CatchScope { - unsigned char bytes[8]; -} bJSC__CatchScope; -typedef char* bJSC__CatchScope_buf; -typedef struct bWTF__String { - unsigned char bytes[8]; -} bWTF__String; -typedef char* bWTF__String_buf; -typedef struct bWTF__StringView { - unsigned char bytes[16]; -} bWTF__StringView; -typedef char* bWTF__StringView_buf; -typedef struct bJSC__JSModuleLoader { - unsigned char bytes[16]; -} bJSC__JSModuleLoader; -typedef char* bJSC__JSModuleLoader_buf; -typedef struct bInspector__ScriptArguments { - unsigned char bytes[32]; -} bInspector__ScriptArguments; -typedef char* bInspector__ScriptArguments_buf; -typedef struct bJSC__Exception { - unsigned char bytes[40]; -} bJSC__Exception; -typedef char* bJSC__Exception_buf; -typedef struct bJSC__VM { - unsigned char bytes[52168]; -} bJSC__VM; -typedef char* bJSC__VM_buf; -typedef struct bJSC__JSString { - unsigned char bytes[16]; -} bJSC__JSString; -typedef char* bJSC__JSString_buf; -typedef struct bJSC__SourceOrigin { - unsigned char bytes[48]; -} bJSC__SourceOrigin; -typedef char* bJSC__SourceOrigin_buf; -typedef struct bWTF__ExternalStringImpl { - unsigned char bytes[40]; -} bWTF__ExternalStringImpl; -typedef char* bWTF__ExternalStringImpl_buf; -typedef struct bJSC__JSInternalPromise { - unsigned char bytes[32]; -} bJSC__JSInternalPromise; -typedef char* bJSC__JSInternalPromise_buf; -typedef struct bWTF__StringImpl { - unsigned char bytes[24]; -} bWTF__StringImpl; -typedef char* bWTF__StringImpl_buf; -typedef struct bJSC__JSPromise { - unsigned char bytes[32]; -} bJSC__JSPromise; -typedef char* bJSC__JSPromise_buf; -typedef struct bJSC__JSObject { - unsigned char bytes[16]; -} bJSC__JSObject; -typedef char* bJSC__JSObject_buf; -typedef struct bJSC__Identifier { - unsigned char bytes[8]; -} bJSC__Identifier; -typedef char* bJSC__Identifier_buf; + typedef struct bJSC__SourceCode { unsigned char bytes[24]; } bJSC__SourceCode; + typedef char* bJSC__SourceCode_buf; + typedef struct bWTF__URL { unsigned char bytes[40]; } bWTF__URL; + typedef char* bWTF__URL_buf; + typedef struct bJSC__JSModuleRecord { unsigned char bytes[216]; } bJSC__JSModuleRecord; + typedef char* bJSC__JSModuleRecord_buf; + typedef struct bJSC__ThrowScope { unsigned char bytes[8]; } bJSC__ThrowScope; + typedef char* bJSC__ThrowScope_buf; + typedef struct bJSC__PropertyName { unsigned char bytes[8]; } bJSC__PropertyName; + typedef char* bJSC__PropertyName_buf; + typedef struct bJSC__JSFunction { unsigned char bytes[32]; } bJSC__JSFunction; + typedef char* bJSC__JSFunction_buf; + typedef struct bJSC__JSGlobalObject { unsigned char bytes[2312]; } bJSC__JSGlobalObject; + typedef char* bJSC__JSGlobalObject_buf; + typedef struct bJSC__JSCell { unsigned char bytes[8]; } bJSC__JSCell; + typedef char* bJSC__JSCell_buf; + typedef struct bJSC__CatchScope { unsigned char bytes[8]; } bJSC__CatchScope; + typedef char* bJSC__CatchScope_buf; + typedef struct bWTF__String { unsigned char bytes[8]; } bWTF__String; + typedef char* bWTF__String_buf; + typedef struct bWTF__StringView { unsigned char bytes[16]; } bWTF__StringView; + typedef char* bWTF__StringView_buf; + typedef struct bJSC__JSModuleLoader { unsigned char bytes[16]; } bJSC__JSModuleLoader; + typedef char* bJSC__JSModuleLoader_buf; + typedef struct bInspector__ScriptArguments { unsigned char bytes[32]; } bInspector__ScriptArguments; + typedef char* bInspector__ScriptArguments_buf; + typedef struct bJSC__Exception { unsigned char bytes[40]; } bJSC__Exception; + typedef char* bJSC__Exception_buf; + typedef struct bJSC__VM { unsigned char bytes[52168]; } bJSC__VM; + typedef char* bJSC__VM_buf; + typedef struct bJSC__JSString { unsigned char bytes[16]; } bJSC__JSString; + typedef char* bJSC__JSString_buf; + typedef struct bJSC__SourceOrigin { unsigned char bytes[48]; } bJSC__SourceOrigin; + typedef char* bJSC__SourceOrigin_buf; + typedef struct bWTF__ExternalStringImpl { unsigned char bytes[40]; } bWTF__ExternalStringImpl; + typedef char* bWTF__ExternalStringImpl_buf; + typedef struct bJSC__JSInternalPromise { unsigned char bytes[32]; } bJSC__JSInternalPromise; + typedef char* bJSC__JSInternalPromise_buf; + typedef struct bWTF__StringImpl { unsigned char bytes[24]; } bWTF__StringImpl; + typedef char* bWTF__StringImpl_buf; + typedef struct bJSC__JSPromise { unsigned char bytes[32]; } bJSC__JSPromise; + typedef char* bJSC__JSPromise_buf; + typedef struct bJSC__JSObject { unsigned char bytes[16]; } bJSC__JSObject; + typedef char* bJSC__JSObject_buf; + typedef struct bJSC__Identifier { unsigned char bytes[8]; } bJSC__Identifier; + typedef char* bJSC__Identifier_buf; #ifndef __cplusplus -typedef bJSC__CatchScope JSC__CatchScope; // JSC::CatchScope -typedef struct JSC__GeneratorPrototype JSC__GeneratorPrototype; // JSC::GeneratorPrototype -typedef struct JSC__ArrayIteratorPrototype JSC__ArrayIteratorPrototype; // JSC::ArrayIteratorPrototype -typedef ErrorableResolvedSource ErrorableResolvedSource; -typedef struct JSC__JSPromisePrototype JSC__JSPromisePrototype; // JSC::JSPromisePrototype -typedef ErrorableZigString ErrorableZigString; -typedef bJSC__PropertyName JSC__PropertyName; // JSC::PropertyName -typedef bJSC__JSObject JSC__JSObject; // JSC::JSObject -typedef bWTF__ExternalStringImpl WTF__ExternalStringImpl; // WTF::ExternalStringImpl -typedef struct JSC__AsyncIteratorPrototype JSC__AsyncIteratorPrototype; // JSC::AsyncIteratorPrototype -typedef bJSC__JSModuleLoader JSC__JSModuleLoader; // JSC::JSModuleLoader -typedef struct JSC__AsyncGeneratorPrototype JSC__AsyncGeneratorPrototype; // JSC::AsyncGeneratorPrototype -typedef struct JSC__AsyncGeneratorFunctionPrototype JSC__AsyncGeneratorFunctionPrototype; // JSC::AsyncGeneratorFunctionPrototype -typedef bJSC__Identifier JSC__Identifier; // JSC::Identifier -typedef struct JSC__ArrayPrototype JSC__ArrayPrototype; // JSC::ArrayPrototype -typedef struct Zig__JSMicrotaskCallback Zig__JSMicrotaskCallback; // Zig::JSMicrotaskCallback -typedef bJSC__JSPromise JSC__JSPromise; // JSC::JSPromise -typedef WebSocketHTTPClient WebSocketHTTPClient; -typedef struct JSC__SetIteratorPrototype JSC__SetIteratorPrototype; // JSC::SetIteratorPrototype -typedef SystemError SystemError; -typedef bJSC__JSCell JSC__JSCell; // JSC::JSCell -typedef bJSC__SourceOrigin JSC__SourceOrigin; // JSC::SourceOrigin -typedef Bun__Writable Bun__Writable; -typedef bJSC__JSModuleRecord JSC__JSModuleRecord; // JSC::JSModuleRecord -typedef bWTF__String WTF__String; // WTF::String -typedef bWTF__URL WTF__URL; // WTF::URL -typedef struct JSC__IteratorPrototype JSC__IteratorPrototype; // JSC::IteratorPrototype -typedef bJSC__JSInternalPromise JSC__JSInternalPromise; // JSC::JSInternalPromise -typedef Bun__Readable Bun__Readable; -typedef struct JSC__RegExpPrototype JSC__RegExpPrototype; // JSC::RegExpPrototype -typedef struct JSC__MapIteratorPrototype JSC__MapIteratorPrototype; // JSC::MapIteratorPrototype -typedef struct WebCore__FetchHeaders WebCore__FetchHeaders; // WebCore::FetchHeaders -typedef struct JSC__CallFrame JSC__CallFrame; // JSC::CallFrame -typedef bWTF__StringView WTF__StringView; // WTF::StringView -typedef bJSC__ThrowScope JSC__ThrowScope; // JSC::ThrowScope -typedef bWTF__StringImpl WTF__StringImpl; // WTF::StringImpl -typedef WebSocketHTTPSClient WebSocketHTTPSClient; -typedef bJSC__VM JSC__VM; // JSC::VM -typedef JSClassRef JSClassRef; -typedef Bun__ArrayBuffer Bun__ArrayBuffer; -typedef bJSC__JSGlobalObject JSC__JSGlobalObject; // JSC::JSGlobalObject -typedef bJSC__JSFunction JSC__JSFunction; // JSC::JSFunction -typedef struct JSC__AsyncFunctionPrototype JSC__AsyncFunctionPrototype; // JSC::AsyncFunctionPrototype -typedef ZigException ZigException; -typedef bJSC__SourceCode JSC__SourceCode; // JSC::SourceCode -typedef struct JSC__BigIntPrototype JSC__BigIntPrototype; // JSC::BigIntPrototype -typedef struct JSC__GeneratorFunctionPrototype JSC__GeneratorFunctionPrototype; // JSC::GeneratorFunctionPrototype -typedef ZigString ZigString; -typedef struct WebCore__DOMURL WebCore__DOMURL; // WebCore::DOMURL -typedef int64_t JSC__JSValue; -typedef struct JSC__FunctionPrototype JSC__FunctionPrototype; // JSC::FunctionPrototype -typedef bInspector__ScriptArguments Inspector__ScriptArguments; // Inspector::ScriptArguments -typedef bJSC__Exception JSC__Exception; // JSC::Exception -typedef bJSC__JSString JSC__JSString; // JSC::JSString -typedef struct JSC__ObjectPrototype JSC__ObjectPrototype; // JSC::ObjectPrototype -typedef struct JSC__StringPrototype JSC__StringPrototype; // JSC::StringPrototype + typedef bJSC__CatchScope JSC__CatchScope; // JSC::CatchScope + typedef struct JSC__GeneratorPrototype JSC__GeneratorPrototype; // JSC::GeneratorPrototype + typedef struct JSC__ArrayIteratorPrototype JSC__ArrayIteratorPrototype; // JSC::ArrayIteratorPrototype + typedef ErrorableResolvedSource ErrorableResolvedSource; + typedef struct JSC__JSPromisePrototype JSC__JSPromisePrototype; // JSC::JSPromisePrototype + typedef ErrorableZigString ErrorableZigString; + typedef bJSC__PropertyName JSC__PropertyName; // JSC::PropertyName + typedef bJSC__JSObject JSC__JSObject; // JSC::JSObject + typedef WebSocketClient WebSocketClient; + typedef bWTF__ExternalStringImpl WTF__ExternalStringImpl; // WTF::ExternalStringImpl + typedef struct JSC__AsyncIteratorPrototype JSC__AsyncIteratorPrototype; // JSC::AsyncIteratorPrototype + typedef bJSC__JSModuleLoader JSC__JSModuleLoader; // JSC::JSModuleLoader + typedef struct JSC__AsyncGeneratorPrototype JSC__AsyncGeneratorPrototype; // JSC::AsyncGeneratorPrototype + typedef struct JSC__AsyncGeneratorFunctionPrototype JSC__AsyncGeneratorFunctionPrototype; // JSC::AsyncGeneratorFunctionPrototype + typedef WebSocketClientTLS WebSocketClientTLS; + typedef bJSC__Identifier JSC__Identifier; // JSC::Identifier + typedef struct JSC__ArrayPrototype JSC__ArrayPrototype; // JSC::ArrayPrototype + typedef struct Zig__JSMicrotaskCallback Zig__JSMicrotaskCallback; // Zig::JSMicrotaskCallback + typedef bJSC__JSPromise JSC__JSPromise; // JSC::JSPromise + typedef WebSocketHTTPClient WebSocketHTTPClient; + typedef struct JSC__SetIteratorPrototype JSC__SetIteratorPrototype; // JSC::SetIteratorPrototype + typedef SystemError SystemError; + typedef bJSC__JSCell JSC__JSCell; // JSC::JSCell + typedef bJSC__SourceOrigin JSC__SourceOrigin; // JSC::SourceOrigin + typedef Bun__Writable Bun__Writable; + typedef bJSC__JSModuleRecord JSC__JSModuleRecord; // JSC::JSModuleRecord + typedef bWTF__String WTF__String; // WTF::String + typedef bWTF__URL WTF__URL; // WTF::URL + typedef struct JSC__IteratorPrototype JSC__IteratorPrototype; // JSC::IteratorPrototype + typedef bJSC__JSInternalPromise JSC__JSInternalPromise; // JSC::JSInternalPromise + typedef Bun__Readable Bun__Readable; + typedef struct JSC__RegExpPrototype JSC__RegExpPrototype; // JSC::RegExpPrototype + typedef struct JSC__MapIteratorPrototype JSC__MapIteratorPrototype; // JSC::MapIteratorPrototype + typedef struct WebCore__FetchHeaders WebCore__FetchHeaders; // WebCore::FetchHeaders + typedef struct JSC__CallFrame JSC__CallFrame; // JSC::CallFrame + typedef bWTF__StringView WTF__StringView; // WTF::StringView + typedef bJSC__ThrowScope JSC__ThrowScope; // JSC::ThrowScope + typedef bWTF__StringImpl WTF__StringImpl; // WTF::StringImpl + typedef WebSocketHTTPSClient WebSocketHTTPSClient; + typedef bJSC__VM JSC__VM; // JSC::VM + typedef JSClassRef JSClassRef; + typedef Bun__ArrayBuffer Bun__ArrayBuffer; + typedef bJSC__JSGlobalObject JSC__JSGlobalObject; // JSC::JSGlobalObject + typedef bJSC__JSFunction JSC__JSFunction; // JSC::JSFunction + typedef struct JSC__AsyncFunctionPrototype JSC__AsyncFunctionPrototype; // JSC::AsyncFunctionPrototype + typedef ZigException ZigException; + typedef bJSC__SourceCode JSC__SourceCode; // JSC::SourceCode + typedef struct JSC__BigIntPrototype JSC__BigIntPrototype; // JSC::BigIntPrototype + typedef struct JSC__GeneratorFunctionPrototype JSC__GeneratorFunctionPrototype; // JSC::GeneratorFunctionPrototype + typedef ZigString ZigString; + typedef struct WebCore__DOMURL WebCore__DOMURL; // WebCore::DOMURL + typedef int64_t JSC__JSValue; + typedef struct JSC__FunctionPrototype JSC__FunctionPrototype; // JSC::FunctionPrototype + typedef bInspector__ScriptArguments Inspector__ScriptArguments; // Inspector::ScriptArguments + typedef bJSC__Exception JSC__Exception; // JSC::Exception + typedef bJSC__JSString JSC__JSString; // JSC::JSString + typedef struct JSC__ObjectPrototype JSC__ObjectPrototype; // JSC::ObjectPrototype + typedef struct JSC__StringPrototype JSC__StringPrototype; // JSC::StringPrototype #endif #ifdef __cplusplus -namespace JSC { -class JSCell; -class Exception; -class JSPromisePrototype; -class StringPrototype; -class GeneratorFunctionPrototype; -class ArrayPrototype; -class JSString; -class JSObject; -class AsyncIteratorPrototype; -class AsyncGeneratorFunctionPrototype; -class Identifier; -class JSPromise; -class RegExpPrototype; -class AsyncFunctionPrototype; -class CatchScope; -class VM; -class BigIntPrototype; -class SourceOrigin; -class ThrowScope; -class SetIteratorPrototype; -class AsyncGeneratorPrototype; -class PropertyName; -class MapIteratorPrototype; -class JSModuleRecord; -class JSInternalPromise; -class ArrayIteratorPrototype; -class JSFunction; -class JSModuleLoader; -class GeneratorPrototype; -class JSGlobalObject; -class SourceCode; -class FunctionPrototype; -class IteratorPrototype; -class CallFrame; -class ObjectPrototype; -} -namespace WTF { -class URL; -class StringImpl; -class String; -class StringView; -class ExternalStringImpl; -} -namespace Zig { -class JSMicrotaskCallback; -} -namespace WebCore { -class DOMURL; -class FetchHeaders; -} -namespace Inspector { -class ScriptArguments; -} - -typedef ErrorableResolvedSource ErrorableResolvedSource; -typedef ErrorableZigString ErrorableZigString; -typedef WebSocketHTTPClient WebSocketHTTPClient; -typedef SystemError SystemError; -typedef Bun__Writable Bun__Writable; -typedef Bun__Readable Bun__Readable; -typedef WebSocketHTTPSClient WebSocketHTTPSClient; -typedef JSClassRef JSClassRef; -typedef Bun__ArrayBuffer Bun__ArrayBuffer; -typedef ZigException ZigException; -typedef ZigString ZigString; -typedef int64_t JSC__JSValue; -using JSC__JSCell = JSC::JSCell; -using JSC__Exception = JSC::Exception; -using JSC__JSPromisePrototype = JSC::JSPromisePrototype; -using JSC__StringPrototype = JSC::StringPrototype; -using JSC__GeneratorFunctionPrototype = JSC::GeneratorFunctionPrototype; -using JSC__ArrayPrototype = JSC::ArrayPrototype; -using JSC__JSString = JSC::JSString; -using JSC__JSObject = JSC::JSObject; -using JSC__AsyncIteratorPrototype = JSC::AsyncIteratorPrototype; -using JSC__AsyncGeneratorFunctionPrototype = JSC::AsyncGeneratorFunctionPrototype; -using JSC__Identifier = JSC::Identifier; -using JSC__JSPromise = JSC::JSPromise; -using JSC__RegExpPrototype = JSC::RegExpPrototype; -using JSC__AsyncFunctionPrototype = JSC::AsyncFunctionPrototype; -using JSC__CatchScope = JSC::CatchScope; -using JSC__VM = JSC::VM; -using JSC__BigIntPrototype = JSC::BigIntPrototype; -using JSC__SourceOrigin = JSC::SourceOrigin; -using JSC__ThrowScope = JSC::ThrowScope; -using JSC__SetIteratorPrototype = JSC::SetIteratorPrototype; -using JSC__AsyncGeneratorPrototype = JSC::AsyncGeneratorPrototype; -using JSC__PropertyName = JSC::PropertyName; -using JSC__MapIteratorPrototype = JSC::MapIteratorPrototype; -using JSC__JSModuleRecord = JSC::JSModuleRecord; -using JSC__JSInternalPromise = JSC::JSInternalPromise; -using JSC__ArrayIteratorPrototype = JSC::ArrayIteratorPrototype; -using JSC__JSFunction = JSC::JSFunction; -using JSC__JSModuleLoader = JSC::JSModuleLoader; -using JSC__GeneratorPrototype = JSC::GeneratorPrototype; -using JSC__JSGlobalObject = JSC::JSGlobalObject; -using JSC__SourceCode = JSC::SourceCode; -using JSC__FunctionPrototype = JSC::FunctionPrototype; -using JSC__IteratorPrototype = JSC::IteratorPrototype; -using JSC__CallFrame = JSC::CallFrame; -using JSC__ObjectPrototype = JSC::ObjectPrototype; -using WTF__URL = WTF::URL; -using WTF__StringImpl = WTF::StringImpl; -using WTF__String = WTF::String; -using WTF__StringView = WTF::StringView; -using WTF__ExternalStringImpl = WTF::ExternalStringImpl; -using Zig__JSMicrotaskCallback = Zig::JSMicrotaskCallback; -using WebCore__DOMURL = WebCore::DOMURL; -using WebCore__FetchHeaders = WebCore::FetchHeaders; -using Inspector__ScriptArguments = Inspector::ScriptArguments; + namespace JSC { + class JSCell; + class Exception; + class JSPromisePrototype; + class StringPrototype; + class GeneratorFunctionPrototype; + class ArrayPrototype; + class JSString; + class JSObject; + class AsyncIteratorPrototype; + class AsyncGeneratorFunctionPrototype; + class Identifier; + class JSPromise; + class RegExpPrototype; + class AsyncFunctionPrototype; + class CatchScope; + class VM; + class BigIntPrototype; + class SourceOrigin; + class ThrowScope; + class SetIteratorPrototype; + class AsyncGeneratorPrototype; + class PropertyName; + class MapIteratorPrototype; + class JSModuleRecord; + class JSInternalPromise; + class ArrayIteratorPrototype; + class JSFunction; + class JSModuleLoader; + class GeneratorPrototype; + class JSGlobalObject; + class SourceCode; + class FunctionPrototype; + class IteratorPrototype; + class CallFrame; + class ObjectPrototype; + } + namespace WTF { + class URL; + class StringImpl; + class String; + class StringView; + class ExternalStringImpl; + } + namespace Zig { + class JSMicrotaskCallback; + } + namespace WebCore { + class DOMURL; + class FetchHeaders; + } + namespace Inspector { + class ScriptArguments; + } + + typedef ErrorableResolvedSource ErrorableResolvedSource; + typedef ErrorableZigString ErrorableZigString; + typedef WebSocketClient WebSocketClient; + typedef WebSocketClientTLS WebSocketClientTLS; + typedef WebSocketHTTPClient WebSocketHTTPClient; + typedef SystemError SystemError; + typedef Bun__Writable Bun__Writable; + typedef Bun__Readable Bun__Readable; + typedef WebSocketHTTPSClient WebSocketHTTPSClient; + typedef JSClassRef JSClassRef; + typedef Bun__ArrayBuffer Bun__ArrayBuffer; + typedef ZigException ZigException; + typedef ZigString ZigString; + typedef int64_t JSC__JSValue; + using JSC__JSCell = JSC::JSCell; + using JSC__Exception = JSC::Exception; + using JSC__JSPromisePrototype = JSC::JSPromisePrototype; + using JSC__StringPrototype = JSC::StringPrototype; + using JSC__GeneratorFunctionPrototype = JSC::GeneratorFunctionPrototype; + using JSC__ArrayPrototype = JSC::ArrayPrototype; + using JSC__JSString = JSC::JSString; + using JSC__JSObject = JSC::JSObject; + using JSC__AsyncIteratorPrototype = JSC::AsyncIteratorPrototype; + using JSC__AsyncGeneratorFunctionPrototype = JSC::AsyncGeneratorFunctionPrototype; + using JSC__Identifier = JSC::Identifier; + using JSC__JSPromise = JSC::JSPromise; + using JSC__RegExpPrototype = JSC::RegExpPrototype; + using JSC__AsyncFunctionPrototype = JSC::AsyncFunctionPrototype; + using JSC__CatchScope = JSC::CatchScope; + using JSC__VM = JSC::VM; + using JSC__BigIntPrototype = JSC::BigIntPrototype; + using JSC__SourceOrigin = JSC::SourceOrigin; + using JSC__ThrowScope = JSC::ThrowScope; + using JSC__SetIteratorPrototype = JSC::SetIteratorPrototype; + using JSC__AsyncGeneratorPrototype = JSC::AsyncGeneratorPrototype; + using JSC__PropertyName = JSC::PropertyName; + using JSC__MapIteratorPrototype = JSC::MapIteratorPrototype; + using JSC__JSModuleRecord = JSC::JSModuleRecord; + using JSC__JSInternalPromise = JSC::JSInternalPromise; + using JSC__ArrayIteratorPrototype = JSC::ArrayIteratorPrototype; + using JSC__JSFunction = JSC::JSFunction; + using JSC__JSModuleLoader = JSC::JSModuleLoader; + using JSC__GeneratorPrototype = JSC::GeneratorPrototype; + using JSC__JSGlobalObject = JSC::JSGlobalObject; + using JSC__SourceCode = JSC::SourceCode; + using JSC__FunctionPrototype = JSC::FunctionPrototype; + using JSC__IteratorPrototype = JSC::IteratorPrototype; + using JSC__CallFrame = JSC::CallFrame; + using JSC__ObjectPrototype = JSC::ObjectPrototype; + using WTF__URL = WTF::URL; + using WTF__StringImpl = WTF::StringImpl; + using WTF__String = WTF::String; + using WTF__StringView = WTF::StringView; + using WTF__ExternalStringImpl = WTF::ExternalStringImpl; + using Zig__JSMicrotaskCallback = Zig::JSMicrotaskCallback; + using WebCore__DOMURL = WebCore::DOMURL; + using WebCore__FetchHeaders = WebCore::FetchHeaders; + using Inspector__ScriptArguments = Inspector::ScriptArguments; #endif + #pragma mark - JSC::JSObject -CPP_DECL JSC__JSValue JSC__JSObject__create(JSC__JSGlobalObject* arg0, size_t arg1, void* arg2, void (*ArgFn3)(void* arg0, JSC__JSObject* arg1, JSC__JSGlobalObject* arg2)); +CPP_DECL JSC__JSValue JSC__JSObject__create(JSC__JSGlobalObject* arg0, size_t arg1, void* arg2, void (* ArgFn3)(void* arg0, JSC__JSObject* arg1, JSC__JSGlobalObject* arg2)); CPP_DECL size_t JSC__JSObject__getArrayLength(JSC__JSObject* arg0); CPP_DECL JSC__JSValue JSC__JSObject__getDirect(JSC__JSObject* arg0, JSC__JSGlobalObject* arg1, const ZigString* arg2); CPP_DECL JSC__JSValue JSC__JSObject__getIndex(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, uint32_t arg2); CPP_DECL void JSC__JSObject__putRecord(JSC__JSObject* arg0, JSC__JSGlobalObject* arg1, ZigString* arg2, ZigString* arg3, size_t arg4); -CPP_DECL JSC__JSValue ZigString__external(const ZigString* arg0, JSC__JSGlobalObject* arg1, void* arg2, void (*ArgFn3)(void* arg0, void* arg1, size_t arg2)); +CPP_DECL JSC__JSValue ZigString__external(const ZigString* arg0, JSC__JSGlobalObject* arg1, void* arg2, void (* ArgFn3)(void* arg0, void* arg1, size_t arg2)); CPP_DECL JSC__JSValue ZigString__to16BitValue(const ZigString* arg0, JSC__JSGlobalObject* arg1); CPP_DECL JSC__JSValue ZigString__toErrorInstance(const ZigString* arg0, JSC__JSGlobalObject* arg1); CPP_DECL JSC__JSValue ZigString__toExternalU16(const uint16_t* arg0, size_t arg1, JSC__JSGlobalObject* arg2); CPP_DECL JSC__JSValue ZigString__toExternalValue(const ZigString* arg0, JSC__JSGlobalObject* arg1); -CPP_DECL JSC__JSValue ZigString__toExternalValueWithCallback(const ZigString* arg0, JSC__JSGlobalObject* arg1, void (*ArgFn2)(void* arg0, void* arg1, size_t arg2)); +CPP_DECL JSC__JSValue ZigString__toExternalValueWithCallback(const ZigString* arg0, JSC__JSGlobalObject* arg1, void (* ArgFn2)(void* arg0, void* arg1, size_t arg2)); CPP_DECL JSC__JSValue ZigString__toValue(const ZigString* arg0, JSC__JSGlobalObject* arg1); CPP_DECL JSC__JSValue ZigString__toValueGC(const ZigString* arg0, JSC__JSGlobalObject* arg1); CPP_DECL WebCore__DOMURL* WebCore__DOMURL__cast_(JSC__JSValue JSValue0, JSC__VM* arg1); @@ -503,7 +462,7 @@ CPP_DECL size_t WTF__String__length(WTF__String* arg0); #pragma mark - JSC::JSValue -CPP_DECL void JSC__JSValue___then(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, void* arg2, void (*ArgFn3)(JSC__JSGlobalObject* arg0, void* arg1, void** arg2, size_t arg3), void (*ArgFn4)(JSC__JSGlobalObject* arg0, void* arg1, void** arg2, size_t arg3)); +CPP_DECL void JSC__JSValue___then(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, void* arg2, void (* ArgFn3)(JSC__JSGlobalObject* arg0, void* arg1, void** arg2, size_t arg3), void (* ArgFn4)(JSC__JSGlobalObject* arg0, void* arg1, void** arg2, size_t arg3)); CPP_DECL bool JSC__JSValue__asArrayBuffer_(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, Bun__ArrayBuffer* arg2); CPP_DECL JSC__JSCell* JSC__JSValue__asCell(JSC__JSValue JSValue0); CPP_DECL JSC__JSInternalPromise* JSC__JSValue__asInternalPromise(JSC__JSValue JSValue0); @@ -521,7 +480,7 @@ CPP_DECL JSC__JSValue JSC__JSValue__createTypeError(const ZigString* arg0, const CPP_DECL JSC__JSValue JSC__JSValue__createUninitializedUint8Array(JSC__JSGlobalObject* arg0, size_t arg1); CPP_DECL bool JSC__JSValue__eqlCell(JSC__JSValue JSValue0, JSC__JSCell* arg1); CPP_DECL bool JSC__JSValue__eqlValue(JSC__JSValue JSValue0, JSC__JSValue JSValue1); -CPP_DECL void JSC__JSValue__forEach(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, void* arg2, void (*ArgFn3)(JSC__VM* arg0, JSC__JSGlobalObject* arg1, void* arg2, JSC__JSValue JSValue3)); +CPP_DECL void JSC__JSValue__forEach(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, void* arg2, void (* ArgFn3)(JSC__VM* arg0, JSC__JSGlobalObject* arg1, void* arg2, JSC__JSValue JSValue3)); CPP_DECL JSC__JSValue JSC__JSValue__fromEntries(JSC__JSGlobalObject* arg0, ZigString* arg1, ZigString* arg2, size_t arg3, bool arg4); CPP_DECL JSC__JSValue JSC__JSValue__fromInt64NoTruncate(JSC__JSGlobalObject* arg0, int64_t arg1); CPP_DECL JSC__JSValue JSC__JSValue__fromUInt64NoTruncate(JSC__JSGlobalObject* arg0, uint64_t arg1); @@ -607,13 +566,13 @@ CPP_DECL JSC__JSValue JSC__Exception__value(JSC__Exception* arg0); CPP_DECL void JSC__VM__clearExecutionTimeLimit(JSC__VM* arg0); CPP_DECL JSC__VM* JSC__VM__create(unsigned char HeapType0); -CPP_DECL void JSC__VM__deferGC(JSC__VM* arg0, void* arg1, void (*ArgFn2)(void* arg0)); +CPP_DECL void JSC__VM__deferGC(JSC__VM* arg0, void* arg1, void (* ArgFn2)(void* arg0)); CPP_DECL void JSC__VM__deinit(JSC__VM* arg0, JSC__JSGlobalObject* arg1); CPP_DECL void JSC__VM__deleteAllCode(JSC__VM* arg0, JSC__JSGlobalObject* arg1); CPP_DECL void JSC__VM__doWork(JSC__VM* arg0); CPP_DECL void JSC__VM__drainMicrotasks(JSC__VM* arg0); CPP_DECL bool JSC__VM__executionForbidden(JSC__VM* arg0); -CPP_DECL void JSC__VM__holdAPILock(JSC__VM* arg0, void* arg1, void (*ArgFn2)(void* arg0)); +CPP_DECL void JSC__VM__holdAPILock(JSC__VM* arg0, void* arg1, void (* ArgFn2)(void* arg0)); CPP_DECL bool JSC__VM__isEntered(JSC__VM* arg0); CPP_DECL bool JSC__VM__isJITEnabled(); CPP_DECL void JSC__VM__releaseWeakRefs(JSC__VM* arg0); @@ -623,7 +582,7 @@ CPP_DECL void JSC__VM__setExecutionTimeLimit(JSC__VM* arg0, double arg1); CPP_DECL void JSC__VM__shrinkFootprint(JSC__VM* arg0); CPP_DECL void JSC__VM__throwError(JSC__VM* arg0, JSC__JSGlobalObject* arg1, JSC__JSValue JSValue2); CPP_DECL void JSC__VM__throwError(JSC__VM* arg0, JSC__JSGlobalObject* arg1, JSC__JSValue JSValue2); -CPP_DECL void JSC__VM__whenIdle(JSC__VM* arg0, void (*ArgFn1)()); +CPP_DECL void JSC__VM__whenIdle(JSC__VM* arg0, void (* ArgFn1)()); #pragma mark - JSC::ThrowScope @@ -670,7 +629,7 @@ CPP_DECL size_t WTF__StringImpl__length(const WTF__StringImpl* arg0); CPP_DECL const uint16_t* WTF__ExternalStringImpl__characters16(const WTF__ExternalStringImpl* arg0); CPP_DECL const unsigned char* WTF__ExternalStringImpl__characters8(const WTF__ExternalStringImpl* arg0); -CPP_DECL bWTF__ExternalStringImpl WTF__ExternalStringImpl__create(const unsigned char* arg0, size_t arg1, void (*ArgFn2)(void* arg0, unsigned char* arg1, size_t arg2)); +CPP_DECL bWTF__ExternalStringImpl WTF__ExternalStringImpl__create(const unsigned char* arg0, size_t arg1, void (* ArgFn2)(void* arg0, unsigned char* arg1, size_t arg2)); CPP_DECL bool WTF__ExternalStringImpl__is16Bit(const WTF__ExternalStringImpl* arg0); CPP_DECL bool WTF__ExternalStringImpl__is8Bit(const WTF__ExternalStringImpl* arg0); CPP_DECL bool WTF__ExternalStringImpl__isEmpty(const WTF__ExternalStringImpl* arg0); @@ -812,6 +771,28 @@ ZIG_DECL void Bun__WebSocketHTTPSClient__register(JSC__JSGlobalObject* arg0, voi #ifdef __cplusplus +ZIG_DECL void Bun__WebSocketClient__close(WebSocketClient* arg0, uint16_t arg1, const ZigString* arg2); +ZIG_DECL void Bun__WebSocketClient__finalize(WebSocketClient* arg0); +ZIG_DECL void* Bun__WebSocketClient__init(void* arg0, void* arg1, void* arg2, JSC__JSGlobalObject* arg3); +ZIG_DECL void Bun__WebSocketClient__register(JSC__JSGlobalObject* arg0, void* arg1, void* arg2); +ZIG_DECL void Bun__WebSocketClient__writeBinaryData(WebSocketClient* arg0, const unsigned char* arg1, size_t arg2); +ZIG_DECL void Bun__WebSocketClient__writeString(WebSocketClient* arg0, const ZigString* arg1); + +#endif + +#ifdef __cplusplus + +ZIG_DECL void Bun__WebSocketClientTLS__close(WebSocketClientTLS* arg0, uint16_t arg1, const ZigString* arg2); +ZIG_DECL void Bun__WebSocketClientTLS__finalize(WebSocketClientTLS* arg0); +ZIG_DECL void* Bun__WebSocketClientTLS__init(void* arg0, void* arg1, void* arg2, JSC__JSGlobalObject* arg3); +ZIG_DECL void Bun__WebSocketClientTLS__register(JSC__JSGlobalObject* arg0, void* arg1, void* arg2); +ZIG_DECL void Bun__WebSocketClientTLS__writeBinaryData(WebSocketClientTLS* arg0, const unsigned char* arg1, size_t arg2); +ZIG_DECL void Bun__WebSocketClientTLS__writeString(WebSocketClientTLS* arg0, const ZigString* arg1); + +#endif + +#ifdef __cplusplus + ZIG_DECL void Bun__Process__exit(JSC__JSGlobalObject* arg0, int32_t arg1); ZIG_DECL JSC__JSValue Bun__Process__getArgv(JSC__JSGlobalObject* arg0); ZIG_DECL JSC__JSValue Bun__Process__getCwd(JSC__JSGlobalObject* arg0); @@ -824,6 +805,7 @@ CPP_DECL ZigException ZigException__fromException(JSC__Exception* arg0); #pragma mark - Zig::ConsoleClient + #ifdef __cplusplus ZIG_DECL void Zig__ConsoleClient__count(void* arg0, JSC__JSGlobalObject* arg1, const unsigned char* arg2, size_t arg3); @@ -844,6 +826,7 @@ ZIG_DECL void Zig__ConsoleClient__timeStamp(void* arg0, JSC__JSGlobalObject* arg #pragma mark - Bun__Timer + #ifdef __cplusplus ZIG_DECL JSC__JSValue Bun__Timer__clearInterval(JSC__JSGlobalObject* arg0, JSC__JSValue JSValue1); diff --git a/src/javascript/jsc/bindings/headers.zig b/src/javascript/jsc/bindings/headers.zig index 4b05ed250..306ee44f2 100644 --- a/src/javascript/jsc/bindings/headers.zig +++ b/src/javascript/jsc/bindings/headers.zig @@ -68,6 +68,8 @@ pub const ArrayBufferSink = @import("../webcore/streams.zig").ArrayBufferSink; pub const WebSocketHTTPClient = bindings.WebSocketHTTPClient; pub const WebSocketHTTPSClient = bindings.WebSocketHTTPSClient; +pub const WebSocketClient = bindings.WebSocketClient; +pub const WebSocketClientTLS = bindings.WebSocketClientTLS; // GENERATED CODE - DO NOT MODIFY BY HAND pub const ptrdiff_t = c_long; diff --git a/src/javascript/jsc/bindings/webcore/WebSocket.cpp b/src/javascript/jsc/bindings/webcore/WebSocket.cpp index 3c9f3a373..7748f2ae8 100644 --- a/src/javascript/jsc/bindings/webcore/WebSocket.cpp +++ b/src/javascript/jsc/bindings/webcore/WebSocket.cpp @@ -176,11 +176,11 @@ WebSocket::~WebSocket() switch (m_connectedWebSocketKind) { case ConnectedWebSocketKind::Client: { - this->m_connectedWebSocket.client->end(None); + Bun__WebSocketClient__finalize(this->m_connectedWebSocket.client); break; } case ConnectedWebSocketKind::ClientSSL: { - this->m_connectedWebSocket.clientSSL->end(None); + Bun__WebSocketClientTLS__finalize(this->m_connectedWebSocket.clientSSL); break; } case ConnectedWebSocketKind::Server: { @@ -408,17 +408,17 @@ ExceptionOr<void> WebSocket::send(const String& message) LOG(Network, "WebSocket %p send() Sending String '%s'", this, message.utf8().data()); if (m_state == CONNECTING) return Exception { InvalidStateError }; - auto utf8 = message.utf8(StrictConversionReplacingUnpairedSurrogatesWithFFFD); // No exception is raised if the connection was once established but has subsequently been closed. if (m_state == CLOSING || m_state == CLOSED) { + auto utf8 = message.utf8(StrictConversionReplacingUnpairedSurrogatesWithFFFD); size_t payloadSize = utf8.length(); m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, payloadSize); m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, getFramingOverhead(payloadSize)); return {}; } - if (utf8.length() > 0) - this->sendWebSocketData<false>(utf8.data(), utf8.length()); + if (message.length() > 0) + this->sendWebSocketString(message); return {}; } @@ -437,7 +437,7 @@ ExceptionOr<void> WebSocket::send(ArrayBuffer& binaryData) char* data = static_cast<char*>(binaryData.data()); size_t length = binaryData.byteLength(); if (length > 0) - this->sendWebSocketData<true>(data, length); + this->sendWebSocketData(data, length); return {}; } @@ -458,7 +458,7 @@ ExceptionOr<void> WebSocket::send(ArrayBufferView& arrayBufferView) char* baseAddress = reinterpret_cast<char*>(buffer->data()) + arrayBufferView.byteOffset(); size_t length = arrayBufferView.byteLength(); if (length > 0) - this->sendWebSocketData<true>(baseAddress, length); + this->sendWebSocketData(baseAddress, length); return {}; } @@ -480,48 +480,74 @@ ExceptionOr<void> WebSocket::send(ArrayBufferView& arrayBufferView) // return {}; // } -template<bool isBinary> void WebSocket::sendWebSocketData(const char* baseAddress, size_t length) { - uWS::OpCode opCode = uWS::OpCode::TEXT; + uWS::OpCode opCode = uWS::OpCode::BINARY; - if constexpr (isBinary) - opCode = uWS::OpCode::BINARY; + switch (m_connectedWebSocketKind) { + case ConnectedWebSocketKind::Client: { + Bun__WebSocketClient__writeBinaryData(this->m_connectedWebSocket.client, reinterpret_cast<const unsigned char*>(baseAddress), length); + // this->m_connectedWebSocket.client->send({ baseAddress, length }, opCode); + // this->m_bufferedAmount = this->m_connectedWebSocket.client->getBufferedAmount(); + break; + } + case ConnectedWebSocketKind::ClientSSL: { + Bun__WebSocketClientTLS__writeBinaryData(this->m_connectedWebSocket.clientSSL, reinterpret_cast<const unsigned char*>(baseAddress), length); + break; + } + case ConnectedWebSocketKind::Server: { + this->m_connectedWebSocket.server->send({ baseAddress, length }, opCode); + this->m_bufferedAmount = this->m_connectedWebSocket.server->getBufferedAmount(); + break; + } + case ConnectedWebSocketKind::ServerSSL: { + this->m_connectedWebSocket.serverSSL->send({ baseAddress, length }, opCode); + this->m_bufferedAmount = this->m_connectedWebSocket.serverSSL->getBufferedAmount(); + break; + } + default: { + RELEASE_ASSERT_NOT_REACHED(); + } + } +} - this->m_connectedWebSocket.client->cork( - [&]() { - switch (m_connectedWebSocketKind) { - case ConnectedWebSocketKind::Client: { - this->m_connectedWebSocket.client->send({ baseAddress, length }, opCode); - this->m_bufferedAmount = this->m_connectedWebSocket.client->getBufferedAmount(); - break; - } - case ConnectedWebSocketKind::ClientSSL: { - this->m_connectedWebSocket.clientSSL->send({ baseAddress, length }, opCode); - this->m_bufferedAmount = this->m_connectedWebSocket.clientSSL->getBufferedAmount(); - break; - } - case ConnectedWebSocketKind::Server: { - this->m_connectedWebSocket.server->send({ baseAddress, length }, opCode); - this->m_bufferedAmount = this->m_connectedWebSocket.server->getBufferedAmount(); - break; - } - case ConnectedWebSocketKind::ServerSSL: { - this->m_connectedWebSocket.serverSSL->send({ baseAddress, length }, opCode); - this->m_bufferedAmount = this->m_connectedWebSocket.serverSSL->getBufferedAmount(); - break; - } - default: { - RELEASE_ASSERT_NOT_REACHED(); - } - } - }); +void WebSocket::sendWebSocketString(const String& message) +{ + + switch (m_connectedWebSocketKind) { + case ConnectedWebSocketKind::Client: { + auto zigStr = Zig::toZigString(message); + Bun__WebSocketClient__writeString(this->m_connectedWebSocket.client, &zigStr); + // this->m_connectedWebSocket.client->send({ baseAddress, length }, opCode); + // this->m_bufferedAmount = this->m_connectedWebSocket.client->getBufferedAmount(); + break; + } + case ConnectedWebSocketKind::ClientSSL: { + auto zigStr = Zig::toZigString(message); + Bun__WebSocketClientTLS__writeString(this->m_connectedWebSocket.clientSSL, &zigStr); + break; + } + case ConnectedWebSocketKind::Server: { + auto utf8 = message.utf8(StrictConversionReplacingUnpairedSurrogatesWithFFFD); + this->m_connectedWebSocket.server->send({ utf8.data(), utf8.length() }, uWS::OpCode::TEXT); + this->m_bufferedAmount = this->m_connectedWebSocket.server->getBufferedAmount(); + break; + } + case ConnectedWebSocketKind::ServerSSL: { + auto utf8 = message.utf8(StrictConversionReplacingUnpairedSurrogatesWithFFFD); + this->m_connectedWebSocket.serverSSL->send({ utf8.data(), utf8.length() }, uWS::OpCode::TEXT); + this->m_bufferedAmount = this->m_connectedWebSocket.serverSSL->getBufferedAmount(); + break; + } + default: { + RELEASE_ASSERT_NOT_REACHED(); + } + } } ExceptionOr<void> WebSocket::close(std::optional<unsigned short> optionalCode, const String& reason) { - CString utf8 = reason.utf8(StrictConversionReplacingUnpairedSurrogatesWithFFFD); int code = optionalCode ? optionalCode.value() : static_cast<int>(0); if (code == 0) LOG(Network, "WebSocket %p close() without code and reason", this); @@ -529,7 +555,7 @@ ExceptionOr<void> WebSocket::close(std::optional<unsigned short> optionalCode, c LOG(Network, "WebSocket %p close() code=%d reason='%s'", this, code, reason.utf8().data()); // if (!(code == WebSocketChannel::CloseEventCodeNormalClosure || (WebSocketChannel::CloseEventCodeMinimumUserDefined <= code && code <= WebSocketChannel::CloseEventCodeMaximumUserDefined))) // return Exception { InvalidAccessError }; - if (utf8.length() > maxReasonSizeInBytes) { + if (reason.length() > maxReasonSizeInBytes) { // scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "WebSocket close message is too long."_s); return Exception { SyntaxError, "WebSocket close message is too long."_s }; } @@ -553,23 +579,25 @@ ExceptionOr<void> WebSocket::close(std::optional<unsigned short> optionalCode, c m_state = CLOSING; switch (m_connectedWebSocketKind) { case ConnectedWebSocketKind::Client: { - this->m_connectedWebSocket.client->end(code, { utf8.data(), utf8.length() }); - this->m_bufferedAmount = this->m_connectedWebSocket.client->getBufferedAmount(); + ZigString reasonZigStr = Zig::toZigString(reason); + Bun__WebSocketClient__close(this->m_connectedWebSocket.client, code, &reasonZigStr); + // this->m_bufferedAmount = this->m_connectedWebSocket.client->getBufferedAmount(); break; } case ConnectedWebSocketKind::ClientSSL: { - this->m_connectedWebSocket.clientSSL->end(code, { utf8.data(), utf8.length() }); - this->m_bufferedAmount = this->m_connectedWebSocket.clientSSL->getBufferedAmount(); + ZigString reasonZigStr = Zig::toZigString(reason); + Bun__WebSocketClientTLS__close(this->m_connectedWebSocket.clientSSL, code, &reasonZigStr); + // this->m_bufferedAmount = this->m_connectedWebSocket.clientSSL->getBufferedAmount(); break; } case ConnectedWebSocketKind::Server: { - this->m_connectedWebSocket.server->end(code, { utf8.data(), utf8.length() }); - this->m_bufferedAmount = this->m_connectedWebSocket.server->getBufferedAmount(); + // this->m_connectedWebSocket.server->end(code, { utf8.data(), utf8.length() }); + // this->m_bufferedAmount = this->m_connectedWebSocket.server->getBufferedAmount(); break; } case ConnectedWebSocketKind::ServerSSL: { - this->m_connectedWebSocket.serverSSL->end(code, { utf8.data(), utf8.length() }); - this->m_bufferedAmount = this->m_connectedWebSocket.serverSSL->getBufferedAmount(); + // this->m_connectedWebSocket.serverSSL->end(code, { utf8.data(), utf8.length() }); + // this->m_bufferedAmount = this->m_connectedWebSocket.serverSSL->getBufferedAmount(); break; } default: { @@ -832,26 +860,12 @@ void WebSocket::didConnect(us_socket_t* socket, char* bufferedData, size_t buffe { this->m_upgradeClient = nullptr; if (m_isSecure) { - /* Adopting a socket invalidates it, do not rely on it directly to carry any data */ - uWS::WebSocket<true, false, WebSocket*>* webSocket = (uWS::WebSocket<true, false, WebSocket*>*)us_socket_context_adopt_socket(1, - (us_socket_context_t*)this->scriptExecutionContext()->connnectedWebSocketContext<true, false>(), socket, sizeof(uWS::WebSocketData) + sizeof(WebSocket*)); - - webSocket->AsyncSocket<true>::uncork(); - - webSocket->init(0, uWS::CompressOptions::DISABLED, uWS::BackPressure()); - *webSocket->getUserData() = this; - this->m_connectedWebSocket.clientSSL = webSocket; + us_socket_context_t* ctx = (us_socket_context_t*)this->scriptExecutionContext()->connnectedWebSocketContext<true, false>(); + this->m_connectedWebSocket.clientSSL = Bun__WebSocketClientTLS__init(this, socket, ctx, this->scriptExecutionContext()->jsGlobalObject()); this->m_connectedWebSocketKind = ConnectedWebSocketKind::ClientSSL; } else { - /* Adopting a socket invalidates it, do not rely on it directly to carry any data */ - uWS::WebSocket<false, false, WebSocket*>* webSocket = (uWS::WebSocket<false, false, WebSocket*>*)us_socket_context_adopt_socket(1, - (us_socket_context_t*)this->scriptExecutionContext()->connnectedWebSocketContext<false, false>(), socket, sizeof(uWS::WebSocketData) + sizeof(WebSocket*)); - - webSocket->AsyncSocket<false>::uncork(); - - webSocket->init(0, uWS::CompressOptions::DISABLED, uWS::BackPressure()); - *webSocket->getUserData() = this; - this->m_connectedWebSocket.client = webSocket; + us_socket_context_t* ctx = (us_socket_context_t*)this->scriptExecutionContext()->connnectedWebSocketContext<false, false>(); + this->m_connectedWebSocket.client = Bun__WebSocketClient__init(this, socket, ctx, this->scriptExecutionContext()->jsGlobalObject()); this->m_connectedWebSocketKind = ConnectedWebSocketKind::Client; } @@ -977,6 +991,61 @@ void WebSocket::didFailWithErrorCode(int32_t code) didReceiveMessageError(message); break; } + + // failed_to_allocate_memory + case 18: { + auto message = MAKE_STATIC_STRING_IMPL("Failed to allocate memory"); + didReceiveMessageError(message); + break; + } + // control_frame_is_fragmented + case 19: { + auto message = MAKE_STATIC_STRING_IMPL("Protocol error - control frame is fragmented"); + didReceiveMessageError(message); + break; + } + // invalid_control_frame + case 20: { + auto message = MAKE_STATIC_STRING_IMPL("Protocol error - invalid control frame"); + didReceiveMessageError(message); + break; + } + // compression_unsupported + case 21: { + auto message = MAKE_STATIC_STRING_IMPL("Compression not implemented yet"); + didReceiveMessageError(message); + break; + } + // unexpected_mask_from_server + case 22: { + auto message = MAKE_STATIC_STRING_IMPL("Protocol error - unexpected mask from server"); + didReceiveMessageError(message); + break; + } + // expected_control_frame + case 23: { + auto message = MAKE_STATIC_STRING_IMPL("Protocol error - expected control frame"); + didReceiveMessageError(message); + break; + } + // unsupported_control_frame + case 24: { + auto message = MAKE_STATIC_STRING_IMPL("Protocol error - unsupported control frame"); + didReceiveMessageError(message); + break; + } + // unexpected_opcode + case 25: { + auto message = MAKE_STATIC_STRING_IMPL("Protocol error - unexpected opcode"); + didReceiveMessageError(message); + break; + } + // invalid_utf8 + case 26: { + auto message = MAKE_STATIC_STRING_IMPL("Server sent invalid UTF8"); + didReceiveMessageError(message); + break; + } } } } // namespace WebCore @@ -985,7 +1054,21 @@ extern "C" void WebSocket__didConnect(WebCore::WebSocket* webSocket, us_socket_t { webSocket->didConnect(socket, bufferedData, len); } -extern "C" void WebSocket__didFailWithErrorCode(WebCore::WebSocket* webSocket, int32_t errorCode) +extern "C" void WebSocket__didCloseWithErrorCode(WebCore::WebSocket* webSocket, int32_t errorCode) { webSocket->didFailWithErrorCode(errorCode); +} + +extern "C" void WebSocket__didReceiveText(WebCore::WebSocket* webSocket, bool clone, const ZigString* str) +{ + WTF::String wtf_str = Zig::toString(*str); + if (clone) { + wtf_str = wtf_str.isolatedCopy(); + } + + webSocket->didReceiveMessage(WTFMove(wtf_str)); +} +extern "C" void WebSocket__didReceiveBytes(WebCore::WebSocket* webSocket, uint8_t* bytes, size_t len) +{ + webSocket->didReceiveBinaryData({ bytes, len }); }
\ No newline at end of file diff --git a/src/javascript/jsc/bindings/webcore/WebSocket.h b/src/javascript/jsc/bindings/webcore/WebSocket.h index 51b885c3b..03c0d7709 100644 --- a/src/javascript/jsc/bindings/webcore/WebSocket.h +++ b/src/javascript/jsc/bindings/webcore/WebSocket.h @@ -105,8 +105,8 @@ public: private: typedef union AnyWebSocket { - uWS::WebSocket<false, false, WebCore::WebSocket*>* client; - uWS::WebSocket<true, false, WebCore::WebSocket*>* clientSSL; + WebSocketClient* client; + WebSocketClientTLS* clientSSL; uWS::WebSocket<false, true, WebCore::WebSocket*>* server; uWS::WebSocket<true, true, WebCore::WebSocket*>* serverSSL; } AnyWebSocket; @@ -138,7 +138,7 @@ private: void didUpdateBufferedAmount(unsigned bufferedAmount); void didStartClosingHandshake(); - template<bool isBinary> + void sendWebSocketString(const String& message); void sendWebSocketData(const char* data, size_t length); void failAsynchronously(); diff --git a/src/javascript/jsc/event_loop.zig b/src/javascript/jsc/event_loop.zig index 9cc6c835a..fbd14c270 100644 --- a/src/javascript/jsc/event_loop.zig +++ b/src/javascript/jsc/event_loop.zig @@ -287,7 +287,7 @@ pub const EventLoop = struct { concurrent_lock: Lock = Lock.init(), global: *JSGlobalObject = undefined, virtual_machine: *VirtualMachine = undefined, - pub const Queue = std.fifo.LinearFifo(Task, .Dynamic); + pub const Queue = bun.LinearFifo(Task, .Dynamic); pub fn tickWithCount(this: *EventLoop) u32 { var finished: u32 = 0; diff --git a/src/linear_fifo.zig b/src/linear_fifo.zig new file mode 100644 index 000000000..8dc633394 --- /dev/null +++ b/src/linear_fifo.zig @@ -0,0 +1,536 @@ +// clone of zig stdlib +// except, this one vectorizes + +// FIFO of fixed size items +// Usually used for e.g. byte buffers + +const std = @import("std"); +const math = std.math; +const mem = std.mem; +const Allocator = mem.Allocator; +const debug = std.debug; +const assert = debug.assert; +const testing = std.testing; +const bun = @import("./global.zig"); + +pub const LinearFifoBufferType = union(enum) { + /// The buffer is internal to the fifo; it is of the specified size. + Static: usize, + + /// The buffer is passed as a slice to the initialiser. + Slice, + + /// The buffer is managed dynamically using a `mem.Allocator`. + Dynamic, +}; + +pub fn LinearFifo( + comptime T: type, + comptime buffer_type: LinearFifoBufferType, +) type { + const powers_of_two = switch (buffer_type) { + .Static => std.math.isPowerOfTwo(buffer_type.Static), + .Slice => false, // Any size slice could be passed in + .Dynamic => true, // This could be configurable in future + }; + + return struct { + allocator: if (buffer_type == .Dynamic) Allocator else void, + buf: if (buffer_type == .Static) [buffer_type.Static]T else []T, + head: usize, + count: usize, + + const Self = @This(); + pub const Reader = std.io.Reader(*Self, error{}, readFn); + pub const Writer = std.io.Writer(*Self, error{OutOfMemory}, appendWrite); + + // Type of Self argument for slice operations. + // If buffer is inline (Static) then we need to ensure we haven't + // returned a slice into a copy on the stack + const SliceSelfArg = if (buffer_type == .Static) *Self else Self; + + pub usingnamespace switch (buffer_type) { + .Static => struct { + pub fn init() Self { + return .{ + .allocator = {}, + .buf = undefined, + .head = 0, + .count = 0, + }; + } + }, + .Slice => struct { + pub fn init(buf: []T) Self { + return .{ + .allocator = {}, + .buf = buf, + .head = 0, + .count = 0, + }; + } + }, + .Dynamic => struct { + pub fn init(allocator: Allocator) Self { + return .{ + .allocator = allocator, + .buf = &[_]T{}, + .head = 0, + .count = 0, + }; + } + }, + }; + + pub fn deinit(self: Self) void { + if (buffer_type == .Dynamic) self.allocator.free(self.buf); + } + + pub fn realign(self: *Self) void { + if (self.buf.len - self.head >= self.count) { + // this copy overlaps + bun.copy(T, self.buf[0..self.count], self.buf[self.head..][0..self.count]); + self.head = 0; + } else { + var tmp: [mem.page_size / 2 / @sizeOf(T)]T = undefined; + + while (self.head != 0) { + const n = math.min(self.head, tmp.len); + const m = self.buf.len - n; + bun.copy(T, tmp[0..n], self.buf[0..n]); + // this middle copy overlaps; the others here don't + bun.copy(T, self.buf[0..m], self.buf[n..][0..m]); + bun.copy(T, self.buf[m..], tmp[0..n]); + self.head -= n; + } + } + { // set unused area to undefined + const unused = mem.sliceAsBytes(self.buf[self.count..]); + @memset(unused.ptr, undefined, unused.len); + } + } + + /// Reduce allocated capacity to `size`. + pub fn shrink(self: *Self, size: usize) void { + assert(size >= self.count); + if (buffer_type == .Dynamic) { + self.realign(); + self.buf = self.allocator.realloc(self.buf, size) catch |e| switch (e) { + error.OutOfMemory => return, // no problem, capacity is still correct then. + }; + } + } + + pub const ensureCapacity = @compileError("deprecated; call `ensureUnusedCapacity` or `ensureTotalCapacity`"); + + /// Ensure that the buffer can fit at least `size` items + pub fn ensureTotalCapacity(self: *Self, size: usize) !void { + if (self.buf.len >= size) return; + if (buffer_type == .Dynamic) { + const new_size = if (powers_of_two) math.ceilPowerOfTwo(usize, size) catch return error.OutOfMemory else size; + var buf = try self.allocator.alloc(T, new_size); + var new_bytes = std.mem.sliceAsBytes(buf); + var old_bytes = std.mem.sliceAsBytes(self.readableSlice(0)); + @memcpy(new_bytes.ptr, old_bytes.ptr, old_bytes.len); + self.head = 0; + self.allocator.free(self.buf); + self.buf = buf; + } else { + return error.OutOfMemory; + } + } + + /// Makes sure at least `size` items are unused + pub fn ensureUnusedCapacity(self: *Self, size: usize) error{OutOfMemory}!void { + if (self.writableLength() >= size) return; + + return try self.ensureTotalCapacity(math.add(usize, self.count, size) catch return error.OutOfMemory); + } + + /// Returns number of items currently in fifo + pub fn readableLength(self: Self) usize { + return self.count; + } + + /// Returns a writable slice from the 'read' end of the fifo + fn readableSliceMut(self: SliceSelfArg, offset: usize) []T { + if (offset > self.count) return &[_]T{}; + + var start = self.head + offset; + if (start >= self.buf.len) { + start -= self.buf.len; + return self.buf[start .. start + (self.count - offset)]; + } else { + const end = math.min(self.head + self.count, self.buf.len); + return self.buf[start..end]; + } + } + + /// Returns a readable slice from `offset` + pub fn readableSlice(self: SliceSelfArg, offset: usize) []const T { + return self.readableSliceMut(offset); + } + + /// Discard first `count` items in the fifo + pub fn discard(self: *Self, count: usize) void { + assert(count <= self.count); + + if (comptime bun.Environment.allow_assert) { + // set old range to undefined. Note: may be wrapped around + const slice = self.readableSliceMut(0); + if (slice.len >= count) { + const unused = mem.sliceAsBytes(slice[0..count]); + @memset(unused.ptr, undefined, unused.len); + } else { + const unused = mem.sliceAsBytes(slice[0..]); + @memset(unused.ptr, undefined, unused.len); + const unused2 = mem.sliceAsBytes(self.readableSliceMut(slice.len)[0 .. count - slice.len]); + @memset(unused2.ptr, undefined, unused2.len); + } + } + + if (self.count == count) { + self.head = 0; + self.count = 0; + } else { + var head = self.head + count; + if (powers_of_two) { + // Note it is safe to do a wrapping subtract as + // bitwise & with all 1s is a noop + head &= self.buf.len -% 1; + } else { + head %= self.buf.len; + } + self.head = head; + self.count -= count; + } + } + + /// Read the next item from the fifo + pub fn readItem(self: *Self) ?T { + if (self.count == 0) return null; + + const c = self.buf[self.head]; + self.discard(1); + return c; + } + + /// Read data from the fifo into `dst`, returns number of items copied. + pub fn read(self: *Self, dst: []T) usize { + var dst_left = dst; + + while (dst_left.len > 0) { + const slice = self.readableSlice(0); + if (slice.len == 0) break; + const n = math.min(slice.len, dst_left.len); + bun.copy(T, dst_left, slice[0..n]); + self.discard(n); + dst_left = dst_left[n..]; + } + + return dst.len - dst_left.len; + } + + /// Same as `read` except it returns an error union + /// The purpose of this function existing is to match `std.io.Reader` API. + fn readFn(self: *Self, dest: []u8) error{}!usize { + return self.read(dest); + } + + pub fn reader(self: *Self) Reader { + return .{ .context = self }; + } + + /// Returns number of items available in fifo + pub fn writableLength(self: Self) usize { + return self.buf.len - self.count; + } + + /// Returns the first section of writable buffer + /// Note that this may be of length 0 + pub fn writableSlice(self: SliceSelfArg, offset: usize) []T { + if (offset > self.buf.len) return &[_]T{}; + + const tail = self.head + offset + self.count; + if (tail < self.buf.len) { + return self.buf[tail..]; + } else { + return self.buf[tail - self.buf.len ..][0 .. self.writableLength() - offset]; + } + } + + /// Returns a writable buffer of at least `size` items, allocating memory as needed. + /// Use `fifo.update` once you've written data to it. + pub fn writableWithSize(self: *Self, size: usize) ![]T { + try self.ensureUnusedCapacity(size); + + // try to avoid realigning buffer + var slice = self.writableSlice(0); + + if (slice.len < size) { + self.realign(); + slice = self.writableSlice(0); + } + + std.debug.assert(slice.len >= size); + return slice; + } + + /// Update the tail location of the buffer (usually follows use of writable/writableWithSize) + pub fn update(self: *Self, count: usize) void { + assert(self.count + count <= self.buf.len); + self.count += count; + } + + /// Appends the data in `src` to the fifo. + /// You must have ensured there is enough space. + pub fn writeAssumeCapacity(self: *Self, src: []const T) void { + assert(self.writableLength() >= src.len); + + var src_left = src; + while (src_left.len > 0) { + const writable_slice = self.writableSlice(0); + assert(writable_slice.len != 0); + const n = math.min(writable_slice.len, src_left.len); + bun.copy(T, writable_slice, src_left[0..n]); + self.update(n); + src_left = src_left[n..]; + } + } + + /// Write a single item to the fifo + pub fn writeItem(self: *Self, item: T) !void { + try self.ensureUnusedCapacity(1); + return self.writeItemAssumeCapacity(item); + } + + pub fn writeItemAssumeCapacity(self: *Self, item: T) void { + var tail = self.head + self.count; + if (powers_of_two) { + tail &= self.buf.len - 1; + } else { + tail %= self.buf.len; + } + self.buf[tail] = item; + self.update(1); + } + + /// Appends the data in `src` to the fifo. + /// Allocates more memory as necessary + pub fn write(self: *Self, src: []const T) !void { + try self.ensureUnusedCapacity(src.len); + + return self.writeAssumeCapacity(src); + } + + /// Same as `write` except it returns the number of bytes written, which is always the same + /// as `bytes.len`. The purpose of this function existing is to match `std.io.Writer` API. + fn appendWrite(self: *Self, bytes: []const u8) error{OutOfMemory}!usize { + try self.write(bytes); + return bytes.len; + } + + pub fn writer(self: *Self) Writer { + return .{ .context = self }; + } + + /// Make `count` items available before the current read location + fn rewind(self: *Self, count: usize) void { + assert(self.writableLength() >= count); + + var head = self.head + (self.buf.len - count); + if (powers_of_two) { + head &= self.buf.len - 1; + } else { + head %= self.buf.len; + } + self.head = head; + self.count += count; + } + + /// Place data back into the read stream + pub fn unget(self: *Self, src: []const T) !void { + try self.ensureUnusedCapacity(src.len); + + self.rewind(src.len); + + const slice = self.readableSliceMut(0); + if (src.len < slice.len) { + bun.copy(T, slice, src); + } else { + bun.copy(T, slice, src[0..slice.len]); + const slice2 = self.readableSliceMut(slice.len); + bun.copy(T, slice2, src[slice.len..]); + } + } + + /// Returns the item at `offset`. + /// Asserts offset is within bounds. + pub fn peekItem(self: Self, offset: usize) T { + assert(offset < self.count); + + var index = self.head + offset; + if (powers_of_two) { + index &= self.buf.len - 1; + } else { + index %= self.buf.len; + } + return self.buf[index]; + } + + /// Pump data from a reader into a writer + /// stops when reader returns 0 bytes (EOF) + /// Buffer size must be set before calling; a buffer length of 0 is invalid. + pub fn pump(self: *Self, src_reader: anytype, dest_writer: anytype) !void { + assert(self.buf.len > 0); + while (true) { + if (self.writableLength() > 0) { + const n = try src_reader.read(self.writableSlice(0)); + if (n == 0) break; // EOF + self.update(n); + } + self.discard(try dest_writer.write(self.readableSlice(0))); + } + // flush remaining data + while (self.readableLength() > 0) { + self.discard(try dest_writer.write(self.readableSlice(0))); + } + } + }; +} + +test "LinearFifo(u8, .Dynamic) discard(0) from empty buffer should not error on overflow" { + var fifo = LinearFifo(u8, .Dynamic).init(testing.allocator); + defer fifo.deinit(); + + // If overflow is not explicitly allowed this will crash in debug / safe mode + fifo.discard(0); +} + +test "LinearFifo(u8, .Dynamic)" { + var fifo = LinearFifo(u8, .Dynamic).init(testing.allocator); + defer fifo.deinit(); + + try fifo.write("HELLO"); + try testing.expectEqual(@as(usize, 5), fifo.readableLength()); + try testing.expectEqualSlices(u8, "HELLO", fifo.readableSlice(0)); + + { + var i: usize = 0; + while (i < 5) : (i += 1) { + try fifo.write(&[_]u8{fifo.peekItem(i)}); + } + try testing.expectEqual(@as(usize, 10), fifo.readableLength()); + try testing.expectEqualSlices(u8, "HELLOHELLO", fifo.readableSlice(0)); + } + + { + try testing.expectEqual(@as(u8, 'H'), fifo.readItem().?); + try testing.expectEqual(@as(u8, 'E'), fifo.readItem().?); + try testing.expectEqual(@as(u8, 'L'), fifo.readItem().?); + try testing.expectEqual(@as(u8, 'L'), fifo.readItem().?); + try testing.expectEqual(@as(u8, 'O'), fifo.readItem().?); + } + try testing.expectEqual(@as(usize, 5), fifo.readableLength()); + + { // Writes that wrap around + try testing.expectEqual(@as(usize, 11), fifo.writableLength()); + try testing.expectEqual(@as(usize, 6), fifo.writableSlice(0).len); + fifo.writeAssumeCapacity("6<chars<11"); + try testing.expectEqualSlices(u8, "HELLO6<char", fifo.readableSlice(0)); + try testing.expectEqualSlices(u8, "s<11", fifo.readableSlice(11)); + try testing.expectEqualSlices(u8, "11", fifo.readableSlice(13)); + try testing.expectEqualSlices(u8, "", fifo.readableSlice(15)); + fifo.discard(11); + try testing.expectEqualSlices(u8, "s<11", fifo.readableSlice(0)); + fifo.discard(4); + try testing.expectEqual(@as(usize, 0), fifo.readableLength()); + } + + { + const buf = try fifo.writableWithSize(12); + try testing.expectEqual(@as(usize, 12), buf.len); + var i: u8 = 0; + while (i < 10) : (i += 1) { + buf[i] = i + 'a'; + } + fifo.update(10); + try testing.expectEqualSlices(u8, "abcdefghij", fifo.readableSlice(0)); + } + + { + try fifo.unget("prependedstring"); + var result: [30]u8 = undefined; + try testing.expectEqualSlices(u8, "prependedstringabcdefghij", result[0..fifo.read(&result)]); + try fifo.unget("b"); + try fifo.unget("a"); + try testing.expectEqualSlices(u8, "ab", result[0..fifo.read(&result)]); + } + + fifo.shrink(0); + + { + try fifo.writer().print("{s}, {s}!", .{ "Hello", "World" }); + var result: [30]u8 = undefined; + try testing.expectEqualSlices(u8, "Hello, World!", result[0..fifo.read(&result)]); + try testing.expectEqual(@as(usize, 0), fifo.readableLength()); + } + + { + try fifo.writer().writeAll("This is a test"); + var result: [30]u8 = undefined; + try testing.expectEqualSlices(u8, "This", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?); + try testing.expectEqualSlices(u8, "is", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?); + try testing.expectEqualSlices(u8, "a", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?); + try testing.expectEqualSlices(u8, "test", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?); + } + + { + try fifo.ensureTotalCapacity(1); + var in_fbs = std.io.fixedBufferStream("pump test"); + var out_buf: [50]u8 = undefined; + var out_fbs = std.io.fixedBufferStream(&out_buf); + try fifo.pump(in_fbs.reader(), out_fbs.writer()); + try testing.expectEqualSlices(u8, in_fbs.buffer, out_fbs.getWritten()); + } +} + +test "LinearFifo" { + inline for ([_]type{ u1, u8, u16, u64 }) |T| { + inline for ([_]LinearFifoBufferType{ LinearFifoBufferType{ .Static = 32 }, .Slice, .Dynamic }) |bt| { + const FifoType = LinearFifo(T, bt); + var buf: if (bt == .Slice) [32]T else void = undefined; + var fifo = switch (bt) { + .Static => FifoType.init(), + .Slice => FifoType.init(buf[0..]), + .Dynamic => FifoType.init(testing.allocator), + }; + defer fifo.deinit(); + + try fifo.write(&[_]T{ 0, 1, 1, 0, 1 }); + try testing.expectEqual(@as(usize, 5), fifo.readableLength()); + + { + try testing.expectEqual(@as(T, 0), fifo.readItem().?); + try testing.expectEqual(@as(T, 1), fifo.readItem().?); + try testing.expectEqual(@as(T, 1), fifo.readItem().?); + try testing.expectEqual(@as(T, 0), fifo.readItem().?); + try testing.expectEqual(@as(T, 1), fifo.readItem().?); + try testing.expectEqual(@as(usize, 0), fifo.readableLength()); + } + + { + try fifo.writeItem(1); + try fifo.writeItem(1); + try fifo.writeItem(1); + try testing.expectEqual(@as(usize, 3), fifo.readableLength()); + } + + { + var readBuf: [3]T = undefined; + const n = fifo.read(&readBuf); + try testing.expectEqual(@as(usize, 3), n); // NOTE: It should be the number of items. + } + } + } +} |