diff --git a/CMakeLists.txt b/CMakeLists.txt index 123e9c746d..a9fd51864c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1286,7 +1286,9 @@ else() endif() if(USE_CUSTOM_LSHPACK) + include_directories(${BUN_DEPS_DIR}/ls-hpack) if(WIN32) + include_directories(${BUN_DEPS_DIR}/ls-hpack/compat/queue) target_link_libraries(${bun} PRIVATE "${BUN_DEPS_OUT_DIR}/lshpack.lib") else() target_link_libraries(${bun} PRIVATE "${BUN_DEPS_OUT_DIR}/liblshpack.a") @@ -1332,4 +1334,4 @@ endif() if(NO_CODEGEN) message(STATUS "NOTE: NO_CODEGEN is ON, this build expects ./codegen to exist") -endif() +endif() \ No newline at end of file diff --git a/src/bun.js/api/bun/h2_frame_parser.zig b/src/bun.js/api/bun/h2_frame_parser.zig index 7e09a813e9..c0591116a2 100644 --- a/src/bun.js/api/bun/h2_frame_parser.zig +++ b/src/bun.js/api/bun/h2_frame_parser.zig @@ -5,7 +5,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const JSC = bun.JSC; const MutableString = bun.MutableString; -const lshpack = @import("./lshpack.translated.zig"); +const lshpack = @import("./lshpack.zig"); const JSValue = JSC.JSValue; @@ -540,10 +540,15 @@ pub const H2FrameParser = struct { // used window size for the connection usedWindowSize: u32 = 0, lastStreamID: u32 = 0, + firstSettingsACK: bool = false, + // we buffer requests until we get the first settings ACK + writeBuffer: bun.ByteList = .{}, + streams: bun.U32HashMap(Stream), - decoder: lshpack.lshpack_dec = undefined, - encoder: lshpack.lshpack_enc = undefined, + hpack: ?*lshpack.HPACK = null, + + threadlocal var shared_request_buffer: [16384]u8 = undefined; const Stream = struct { id: u32 = 0, @@ -620,50 +625,18 @@ pub const H2FrameParser = struct { } }; - const HeaderValue = struct { - name: []const u8, - value: []const u8, - next: usize, - }; + const HeaderValue = lshpack.HPACK.DecodeResult; - pub fn decode(this: *H2FrameParser, header_buffer: *[MAX_HPACK_HEADER_SIZE]u8, src_buffer: []const u8) !HeaderValue { - var xhdr: lshpack.lsxpack_header = .{}; - - lshpack.lsxpack_header_prepare_decode(&xhdr, header_buffer.ptr, 0, header_buffer.len); - const next = try lshpack.lshpack_decode(&this.decoder, src_buffer.ptr, src_buffer.len, &xhdr); - - const name = lshpack.lsxpack_header_get_name(&xhdr); - if (name.len == 0) { - return error.EmptyHeaderName; + pub fn decode(this: *H2FrameParser, src_buffer: []const u8) !HeaderValue { + if (this.hpack) |hpack| { + return try hpack.decode(src_buffer); } - - return .{ - .name = name, - .value = lshpack.lsxpack_header_get_value(&xhdr), - .next = next, - }; + return error.UnableToDecode; } - pub fn encode(this: *H2FrameParser, header_buffer: *[MAX_HPACK_HEADER_SIZE]u8, dst_buffer: []const u8, name: []const u8, value: []const u8, never_index: bool) !usize { - var xhdr: lshpack.lsxpack_header = .{ .indexed_type = if (never_index) 2 else 0 }; - const size = name.len + value.len; - if (size > MAX_HPACK_HEADER_SIZE) { - return error.HeaderTooLarge; - } - - @memcpy(header_buffer[0..name.len], name); - @memcpy(header_buffer[name.len..size], value); - lshpack.lsxpack_header_set_offset2(&xhdr, header_buffer.ptr, 0, name.len, name.len, value.len); - if (never_index) { - xhdr.indexed_type = 2; - } - - const start = @intFromPtr(dst_buffer.ptr); - log("encode xhdr name {} {} val {} {}", .{ xhdr.name_offset, xhdr.name_len, xhdr.val_offset, xhdr.val_len }); - const ptr = lshpack.lshpack_enc_encode(&this.encoder, dst_buffer.ptr, @ptrFromInt(start + dst_buffer.len), &xhdr); - const end = @intFromPtr(ptr) - start; - if (end > 0) { - return end; + pub fn encode(this: *H2FrameParser, dst_buffer: []u8, dst_offset: usize, name: []const u8, value: []const u8, never_index: bool) !usize { + if (this.hpack) |hpack| { + return try hpack.encode(name, value, never_index, dst_buffer, dst_offset); } return error.UnableToEncode; } @@ -846,6 +819,7 @@ pub const H2FrameParser = struct { JSC.markBinding(@src()); const ctx_value = this.strong_ctx.get() orelse return; value.ensureStillAlive(); + extra.ensureStillAlive(); _ = this.handlers.callEventHandler(event, ctx_value, &[_]JSC.JSValue{ ctx_value, value, extra }); } @@ -853,13 +827,19 @@ pub const H2FrameParser = struct { JSC.markBinding(@src()); const ctx_value = this.strong_ctx.get() orelse return; value.ensureStillAlive(); + extra.ensureStillAlive(); + extra2.ensureStillAlive(); _ = this.handlers.callEventHandler(event, ctx_value, &[_]JSC.JSValue{ ctx_value, value, extra, extra2 }); } + fn bufferWrite(this: *H2FrameParser, bytes: []const u8) void { + log("bufferWrite", .{}); + _ = this.writeBuffer.write(this.allocator, bytes) catch 0; + } + pub fn write(this: *H2FrameParser, bytes: []const u8) void { JSC.markBinding(@src()); log("write", .{}); - const output_value = this.handlers.binary_type.toJS(bytes, this.handlers.globalObject); this.dispatch(.onWrite, output_value); } @@ -877,7 +857,7 @@ pub const H2FrameParser = struct { this.remainingLength -= @intCast(end); if (this.remainingLength > 0) { // buffer more data - _ = this.readBuffer.appendSlice(payload) catch @panic("OOM"); + _ = this.readBuffer.appendSlice(payload) catch bun.outOfMemory(); return null; } else if (this.remainingLength < 0) { this.sendGoAway(streamIdentifier, ErrorCode.FRAME_SIZE_ERROR, "Invalid frame size", this.lastStreamID); @@ -885,6 +865,16 @@ pub const H2FrameParser = struct { } this.currentFrame = null; + + if (this.readBuffer.list.items.len > 0) { + // return buffered data + _ = this.readBuffer.appendSlice(payload) catch bun.outOfMemory(); + return .{ + .data = this.readBuffer.list.items, + .end = end, + }; + } + return .{ .data = payload, .end = end, @@ -919,14 +909,13 @@ pub const H2FrameParser = struct { pub fn decodeHeaderBlock(this: *H2FrameParser, payload: []const u8, stream_id: u32, flags: u8) void { log("decodeHeaderBlock", .{}); - var header_buffer: [MAX_HPACK_HEADER_SIZE]u8 = undefined; var offset: usize = 0; const globalObject = this.handlers.globalObject; const headers = JSC.JSValue.createEmptyObject(globalObject, 0); while (true) { - const header = this.decode(&header_buffer, payload[offset..]) catch break; + const header = this.decode(payload[offset..]) catch break; offset += header.next; log("header {s} {s}", .{ header.name, header.value }); const value = JSC.ZigString.fromUTF8(header.value).toValueGC(globalObject); @@ -1042,12 +1031,12 @@ pub const H2FrameParser = struct { const last_stream_id: u32 = @intCast(UInt31WithReserved.fromBytes(payload[0..4]).uint31); const error_code = UInt31WithReserved.fromBytes(payload[4..8]).toUInt32(); const chunk = this.handlers.binary_type.toJS(payload[8..], this.handlers.globalObject); + this.readBuffer.reset(); if (error_code != @intFromEnum(ErrorCode.NO_ERROR)) { this.dispatchWith2Extra(.onGoAway, JSC.JSValue.jsNumber(error_code), JSC.JSValue.jsNumber(last_stream_id), chunk); } else { this.dispatchWithExtra(.onGoAway, JSC.JSValue.jsNumber(last_stream_id), chunk); } - this.readBuffer.reset(); return content.end; } return data.len; @@ -1096,12 +1085,13 @@ pub const H2FrameParser = struct { if (handleIncommingPayload(this, data, frame.streamIdentifier)) |content| { const payload = content.data; const isNotACK = frame.flags & @intFromEnum(PingFrameFlags.ACK) == 0; - this.dispatchWithExtra(.onPing, this.handlers.binary_type.toJS(payload, this.handlers.globalObject), JSC.JSValue.jsBoolean(!isNotACK)); // if is not ACK send response if (isNotACK) { this.sendPing(true, payload); } + const buffer = this.handlers.binary_type.toJS(payload, this.handlers.globalObject); this.readBuffer.reset(); + this.dispatchWithExtra(.onPing, buffer, JSC.JSValue.jsBoolean(!isNotACK)); return content.end; } return data.len; @@ -1156,7 +1146,6 @@ pub const H2FrameParser = struct { stream.isWaitingMoreHeaders = false; } - this.readBuffer.reset(); return content.end; } @@ -1196,10 +1185,12 @@ pub const H2FrameParser = struct { } const end = payload.len - padding; if (offset > end) { + this.readBuffer.reset(); this.sendGoAway(frame.streamIdentifier, ErrorCode.FRAME_SIZE_ERROR, "invalid Headers frame size", this.lastStreamID); return data.len; } this.decodeHeaderBlock(payload[offset..end], stream.id, frame.flags); + this.readBuffer.reset(); stream.isWaitingMoreHeaders = frame.flags & @intFromEnum(HeadersFrameFlags.END_HEADERS) == 0; if (frame.flags & @intFromEnum(HeadersFrameFlags.END_STREAM) != 0) { if (stream.isWaitingMoreHeaders) { @@ -1214,8 +1205,6 @@ pub const H2FrameParser = struct { if (stream.endAfterHeaders) { this.endStream(stream, ErrorCode.NO_ERROR); } - - this.readBuffer.reset(); return content.end; } @@ -1239,6 +1228,9 @@ pub const H2FrameParser = struct { if (frame.flags & 0x1 != 0) { // we received an ACK log("settings frame ACK", .{}); + // we can now write any request + this.firstSettingsACK = true; + this.flush(); this.remoteSettings = this.localSettings; this.dispatch(.onLocalSettings, this.localSettings.toJS(this.handlers.globalObject)); } @@ -1282,7 +1274,7 @@ pub const H2FrameParser = struct { // new stream open const settings = this.remoteSettings orelse this.localSettings; - const entry = this.streams.getOrPut(streamIdentifier) catch @panic("OOM"); + const entry = this.streams.getOrPut(streamIdentifier) catch bun.outOfMemory(); entry.value_ptr.* = Stream.init(streamIdentifier, settings.initialWindowSize, this); this.dispatch(.onStreamStart, JSC.JSValue.jsNumber(streamIdentifier)); @@ -1295,16 +1287,16 @@ pub const H2FrameParser = struct { log("current frame {} {} {} {}", .{ header.type, header.length, header.flags, header.streamIdentifier }); const stream = this.handleReceivedStreamID(header.streamIdentifier); - return switch (@as(FrameType, @enumFromInt(header.type))) { - FrameType.HTTP_FRAME_SETTINGS => this.handleSettingsFrame(header, bytes), - FrameType.HTTP_FRAME_WINDOW_UPDATE => this.handleWindowUpdateFrame(header, bytes, stream), - FrameType.HTTP_FRAME_HEADERS => this.handleHeadersFrame(header, bytes, stream), - FrameType.HTTP_FRAME_DATA => this.handleDataFrame(header, bytes, stream), - FrameType.HTTP_FRAME_CONTINUATION => this.handleContinuationFrame(header, bytes, stream), - FrameType.HTTP_FRAME_PRIORITY => this.handlePriorityFrame(header, bytes, stream), - FrameType.HTTP_FRAME_PING => this.handlePingFrame(header, bytes, stream), - FrameType.HTTP_FRAME_GOAWAY => this.handleGoAwayFrame(header, bytes, stream), - FrameType.HTTP_FRAME_RST_STREAM => this.handleRSTStreamFrame(header, bytes, stream), + return switch (header.type) { + @intFromEnum(FrameType.HTTP_FRAME_SETTINGS) => this.handleSettingsFrame(header, bytes), + @intFromEnum(FrameType.HTTP_FRAME_WINDOW_UPDATE) => this.handleWindowUpdateFrame(header, bytes, stream), + @intFromEnum(FrameType.HTTP_FRAME_HEADERS) => this.handleHeadersFrame(header, bytes, stream), + @intFromEnum(FrameType.HTTP_FRAME_DATA) => this.handleDataFrame(header, bytes, stream), + @intFromEnum(FrameType.HTTP_FRAME_CONTINUATION) => this.handleContinuationFrame(header, bytes, stream), + @intFromEnum(FrameType.HTTP_FRAME_PRIORITY) => this.handlePriorityFrame(header, bytes, stream), + @intFromEnum(FrameType.HTTP_FRAME_PING) => this.handlePingFrame(header, bytes, stream), + @intFromEnum(FrameType.HTTP_FRAME_GOAWAY) => this.handleGoAwayFrame(header, bytes, stream), + @intFromEnum(FrameType.HTTP_FRAME_RST_STREAM) => this.handleRSTStreamFrame(header, bytes, stream), else => { this.sendGoAway(header.streamIdentifier, ErrorCode.PROTOCOL_ERROR, "Unknown frame type", this.lastStreamID); return bytes.len; @@ -1323,7 +1315,7 @@ pub const H2FrameParser = struct { const total = buffered_data + bytes.len; if (total < FrameHeader.byteSize) { // buffer more data - _ = this.readBuffer.appendSlice(bytes) catch @panic("OOM"); + _ = this.readBuffer.appendSlice(bytes) catch bun.outOfMemory(); return bytes.len; } FrameHeader.from(&header, this.readBuffer.list.items[0..buffered_data], 0, false); @@ -1340,16 +1332,16 @@ pub const H2FrameParser = struct { log("new frame {} {} {} {}", .{ header.type, header.length, header.flags, header.streamIdentifier }); const stream = this.handleReceivedStreamID(header.streamIdentifier); this.ajustWindowSize(stream, header.length); - return switch (@as(FrameType, @enumFromInt(header.type))) { - FrameType.HTTP_FRAME_SETTINGS => this.handleSettingsFrame(header, bytes[needed..]) + needed, - FrameType.HTTP_FRAME_WINDOW_UPDATE => this.handleWindowUpdateFrame(header, bytes[needed..], stream) + needed, - FrameType.HTTP_FRAME_HEADERS => this.handleHeadersFrame(header, bytes[needed..], stream) + needed, - FrameType.HTTP_FRAME_DATA => this.handleDataFrame(header, bytes[needed..], stream) + needed, - FrameType.HTTP_FRAME_CONTINUATION => this.handleContinuationFrame(header, bytes[needed..], stream) + needed, - FrameType.HTTP_FRAME_PRIORITY => this.handlePriorityFrame(header, bytes[needed..], stream) + needed, - FrameType.HTTP_FRAME_PING => this.handlePingFrame(header, bytes[needed..], stream) + needed, - FrameType.HTTP_FRAME_GOAWAY => this.handleGoAwayFrame(header, bytes[needed..], stream) + needed, - FrameType.HTTP_FRAME_RST_STREAM => this.handleRSTStreamFrame(header, bytes[needed..], stream) + needed, + return switch (header.type) { + @intFromEnum(FrameType.HTTP_FRAME_SETTINGS) => this.handleSettingsFrame(header, bytes[needed..]) + needed, + @intFromEnum(FrameType.HTTP_FRAME_WINDOW_UPDATE) => this.handleWindowUpdateFrame(header, bytes[needed..], stream) + needed, + @intFromEnum(FrameType.HTTP_FRAME_HEADERS) => this.handleHeadersFrame(header, bytes[needed..], stream) + needed, + @intFromEnum(FrameType.HTTP_FRAME_DATA) => this.handleDataFrame(header, bytes[needed..], stream) + needed, + @intFromEnum(FrameType.HTTP_FRAME_CONTINUATION) => this.handleContinuationFrame(header, bytes[needed..], stream) + needed, + @intFromEnum(FrameType.HTTP_FRAME_PRIORITY) => this.handlePriorityFrame(header, bytes[needed..], stream) + needed, + @intFromEnum(FrameType.HTTP_FRAME_PING) => this.handlePingFrame(header, bytes[needed..], stream) + needed, + @intFromEnum(FrameType.HTTP_FRAME_GOAWAY) => this.handleGoAwayFrame(header, bytes[needed..], stream) + needed, + @intFromEnum(FrameType.HTTP_FRAME_RST_STREAM) => this.handleRSTStreamFrame(header, bytes[needed..], stream) + needed, else => { this.sendGoAway(header.streamIdentifier, ErrorCode.PROTOCOL_ERROR, "Unknown frame type", this.lastStreamID); return bytes.len; @@ -1359,7 +1351,7 @@ pub const H2FrameParser = struct { if (bytes.len < FrameHeader.byteSize) { // buffer more dheaderata - this.readBuffer.appendSlice(bytes) catch @panic("OOM"); + this.readBuffer.appendSlice(bytes) catch bun.outOfMemory(); return bytes.len; } @@ -1370,16 +1362,16 @@ pub const H2FrameParser = struct { this.remainingLength = header.length; const stream = this.handleReceivedStreamID(header.streamIdentifier); this.ajustWindowSize(stream, header.length); - return switch (@as(FrameType, @enumFromInt(header.type))) { - FrameType.HTTP_FRAME_SETTINGS => this.handleSettingsFrame(header, bytes[FrameHeader.byteSize..]) + FrameHeader.byteSize, - FrameType.HTTP_FRAME_WINDOW_UPDATE => this.handleWindowUpdateFrame(header, bytes[FrameHeader.byteSize..], stream) + FrameHeader.byteSize, - FrameType.HTTP_FRAME_HEADERS => this.handleHeadersFrame(header, bytes[FrameHeader.byteSize..], stream) + FrameHeader.byteSize, - FrameType.HTTP_FRAME_DATA => this.handleDataFrame(header, bytes[FrameHeader.byteSize..], stream) + FrameHeader.byteSize, - FrameType.HTTP_FRAME_CONTINUATION => this.handleContinuationFrame(header, bytes[FrameHeader.byteSize..], stream) + FrameHeader.byteSize, - FrameType.HTTP_FRAME_PRIORITY => this.handlePriorityFrame(header, bytes[FrameHeader.byteSize..], stream) + FrameHeader.byteSize, - FrameType.HTTP_FRAME_PING => this.handlePingFrame(header, bytes[FrameHeader.byteSize..], stream) + FrameHeader.byteSize, - FrameType.HTTP_FRAME_GOAWAY => this.handleGoAwayFrame(header, bytes[FrameHeader.byteSize..], stream) + FrameHeader.byteSize, - FrameType.HTTP_FRAME_RST_STREAM => this.handleRSTStreamFrame(header, bytes[FrameHeader.byteSize..], stream) + FrameHeader.byteSize, + return switch (header.type) { + @intFromEnum(FrameType.HTTP_FRAME_SETTINGS) => this.handleSettingsFrame(header, bytes[FrameHeader.byteSize..]) + FrameHeader.byteSize, + @intFromEnum(FrameType.HTTP_FRAME_WINDOW_UPDATE) => this.handleWindowUpdateFrame(header, bytes[FrameHeader.byteSize..], stream) + FrameHeader.byteSize, + @intFromEnum(FrameType.HTTP_FRAME_HEADERS) => this.handleHeadersFrame(header, bytes[FrameHeader.byteSize..], stream) + FrameHeader.byteSize, + @intFromEnum(FrameType.HTTP_FRAME_DATA) => this.handleDataFrame(header, bytes[FrameHeader.byteSize..], stream) + FrameHeader.byteSize, + @intFromEnum(FrameType.HTTP_FRAME_CONTINUATION) => this.handleContinuationFrame(header, bytes[FrameHeader.byteSize..], stream) + FrameHeader.byteSize, + @intFromEnum(FrameType.HTTP_FRAME_PRIORITY) => this.handlePriorityFrame(header, bytes[FrameHeader.byteSize..], stream) + FrameHeader.byteSize, + @intFromEnum(FrameType.HTTP_FRAME_PING) => this.handlePingFrame(header, bytes[FrameHeader.byteSize..], stream) + FrameHeader.byteSize, + @intFromEnum(FrameType.HTTP_FRAME_GOAWAY) => this.handleGoAwayFrame(header, bytes[FrameHeader.byteSize..], stream) + FrameHeader.byteSize, + @intFromEnum(FrameType.HTTP_FRAME_RST_STREAM) => this.handleRSTStreamFrame(header, bytes[FrameHeader.byteSize..], stream) + FrameHeader.byteSize, else => { this.sendGoAway(header.streamIdentifier, ErrorCode.PROTOCOL_ERROR, "Unknown frame type", this.lastStreamID); return bytes.len; @@ -1389,15 +1381,34 @@ pub const H2FrameParser = struct { const DirectWriterStruct = struct { writer: *H2FrameParser, + shouldBuffer: bool = true, pub fn write(this: *const DirectWriterStruct, data: []const u8) !bool { + if (this.shouldBuffer) { + _ = this.writer.writeBuffer.write(this.writer.allocator, data) catch return false; + return true; + } this.writer.write(data); return true; } }; fn toWriter(this: *H2FrameParser) DirectWriterStruct { - return DirectWriterStruct{ .writer = this }; + return DirectWriterStruct{ .writer = this, .shouldBuffer = false }; } + + fn getBufferWriter(this: *H2FrameParser) DirectWriterStruct { + return DirectWriterStruct{ .writer = this, .shouldBuffer = true }; + } + + fn flush(this: *H2FrameParser) void { + if (this.writeBuffer.len > 0) { + const slice = this.writeBuffer.slice(); + this.write(slice); + // we will only flush one time + this.writeBuffer.deinitWithAllocator(this.allocator); + } + } + pub fn setEncoding(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue { JSC.markBinding(@src()); const args_list = callframe.arguments(1); @@ -1887,7 +1898,9 @@ pub const H2FrameParser = struct { return JSC.JSValue.jsBoolean(true); } fn sendData(this: *H2FrameParser, stream_id: u32, payload: []const u8, close: bool) void { - const writer = this.toWriter(); + log("sendData({}, {}, {})", .{ stream_id, payload.len, close }); + + const writer = if (this.firstSettingsACK) this.toWriter() else this.getBufferWriter(); if (payload.len == 0) { // empty payload we still need to send a frame var dataHeader: FrameHeader = .{ @@ -1914,7 +1927,7 @@ pub const H2FrameParser = struct { .length = size, }; dataHeader.write(@TypeOf(writer), writer); - this.write(slice); + _ = writer.write(slice) catch 0; } } } @@ -1958,9 +1971,7 @@ pub const H2FrameParser = struct { } // max frame size will be always at least 16384 - var buffer: [16384 - FrameHeader.byteSize]u8 = undefined; - var header_buffer: [MAX_HPACK_HEADER_SIZE]u8 = undefined; - @memset(&buffer, 0); + var buffer = shared_request_buffer[0 .. shared_request_buffer.len - FrameHeader.byteSize]; var encoded_size: usize = 0; @@ -2016,7 +2027,7 @@ pub const H2FrameParser = struct { defer value_slice.deinit(); const value = value_slice.slice(); log("encode header {s} {s}", .{ name, value }); - encoded_size += this.encode(&header_buffer, buffer[encoded_size..], name, value_slice.slice(), never_index) catch { + encoded_size += this.encode(buffer, encoded_size, name, value, never_index) catch { stream.state = .CLOSED; stream.rstCode = @intFromEnum(ErrorCode.COMPRESSION_ERROR); this.dispatchWithExtra(.onStreamError, JSC.JSValue.jsNumber(stream_id), JSC.JSValue.jsNumber(stream.rstCode)); @@ -2036,7 +2047,7 @@ pub const H2FrameParser = struct { defer value_slice.deinit(); const value = value_slice.slice(); log("encode header {s} {s}", .{ name, value }); - encoded_size += this.encode(&header_buffer, buffer[encoded_size..], name, value_slice.slice(), never_index) catch { + encoded_size += this.encode(buffer, encoded_size, name, value, never_index) catch { stream.state = .CLOSED; stream.rstCode = @intFromEnum(ErrorCode.COMPRESSION_ERROR); this.dispatchWithExtra(.onStreamError, JSC.JSValue.jsNumber(stream_id), JSC.JSValue.jsNumber(stream.rstCode)); @@ -2053,9 +2064,9 @@ pub const H2FrameParser = struct { .streamIdentifier = stream.id, .length = @intCast(encoded_size), }; - const writer = this.toWriter(); + const writer = if (this.firstSettingsACK) this.toWriter() else this.getBufferWriter(); frame.write(@TypeOf(writer), writer); - this.write(buffer[0..encoded_size]); + _ = writer.write(buffer[0..encoded_size]) catch 0; return JSC.JSValue.jsUndefined(); } @@ -2154,9 +2165,7 @@ pub const H2FrameParser = struct { } // max frame size will be always at least 16384 - var buffer: [16384 - FrameHeader.byteSize - 5]u8 = undefined; - var header_buffer: [MAX_HPACK_HEADER_SIZE]u8 = undefined; - @memset(&buffer, 0); + var buffer = shared_request_buffer[0 .. shared_request_buffer.len - FrameHeader.byteSize - 5]; var encoded_size: usize = 0; @@ -2227,7 +2236,7 @@ pub const H2FrameParser = struct { defer value_slice.deinit(); const value = value_slice.slice(); log("encode header {s} {s}", .{ name, value }); - encoded_size += this.encode(&header_buffer, buffer[encoded_size..], name, value_slice.slice(), never_index) catch { + encoded_size += this.encode(buffer, encoded_size, name, value, never_index) catch { const stream = this.handleReceivedStreamID(stream_id) orelse { return JSC.JSValue.jsNumber(-1); }; @@ -2251,7 +2260,7 @@ pub const H2FrameParser = struct { defer value_slice.deinit(); const value = value_slice.slice(); log("encode header {s} {s}", .{ name, value }); - encoded_size += this.encode(&header_buffer, buffer[encoded_size..], name, value_slice.slice(), never_index) catch { + encoded_size += this.encode(buffer, encoded_size, name, value, never_index) catch { const stream = this.handleReceivedStreamID(stream_id) orelse { return JSC.JSValue.jsNumber(-1); }; @@ -2374,7 +2383,8 @@ pub const H2FrameParser = struct { .streamIdentifier = stream.id, .length = @intCast(encoded_size), }; - const writer = this.toWriter(); + + const writer = if (this.firstSettingsACK) this.toWriter() else this.getBufferWriter(); frame.write(@TypeOf(writer), writer); //https://datatracker.ietf.org/doc/html/rfc7540#section-6.2 if (has_priority) { @@ -2391,7 +2401,7 @@ pub const H2FrameParser = struct { priority.write(@TypeOf(writer), writer); } - this.write(buffer[0..encoded_size]); + _ = writer.write(buffer[0..encoded_size]) catch 0; if (end_stream) { stream.state = .HALF_CLOSED_LOCAL; @@ -2414,9 +2424,9 @@ pub const H2FrameParser = struct { return .zero; } const buffer = args_list.ptr[0]; + buffer.ensureStillAlive(); if (buffer.asArrayBuffer(globalObject)) |array_buffer| { var bytes = array_buffer.byteSlice(); - // read all the bytes while (bytes.len > 0) { const result = this.readBytes(bytes); @@ -2482,13 +2492,7 @@ pub const H2FrameParser = struct { this.strong_ctx.set(globalObject, context_obj); - if (lshpack.lshpack_enc_init(&this.encoder) != 0) { - @panic("OOM"); - } - lshpack.lshpack_dec_init(&this.decoder); - lshpack.lshpack_enc_set_max_capacity(&this.encoder, this.localSettings.headerTableSize); - lshpack.lshpack_dec_set_max_capacity(&this.decoder, this.localSettings.headerTableSize); - + this.hpack = lshpack.HPACK.init(this.localSettings.headerTableSize); this.sendPrefaceAndSettings(); return this; } @@ -2499,9 +2503,12 @@ pub const H2FrameParser = struct { this.strong_ctx.deinit(); this.handlers.deinit(); this.readBuffer.deinit(); + this.writeBuffer.deinitWithAllocator(allocator); - lshpack.lshpack_dec_cleanup(&this.decoder); - lshpack.lshpack_enc_cleanup(&this.encoder); + if (this.hpack) |hpack| { + hpack.deinit(); + this.hpack = null; + } var it = this.streams.iterator(); while (it.next()) |*entry| { diff --git a/src/bun.js/api/bun/lshpack.translated.zig b/src/bun.js/api/bun/lshpack.translated.zig deleted file mode 100644 index 19af2f0445..0000000000 --- a/src/bun.js/api/bun/lshpack.translated.zig +++ /dev/null @@ -1,448 +0,0 @@ -pub const __builtin_bswap16 = @import("std").zig.c_builtins.__builtin_bswap16; -pub const __builtin_bswap32 = @import("std").zig.c_builtins.__builtin_bswap32; -pub const __builtin_bswap64 = @import("std").zig.c_builtins.__builtin_bswap64; -pub const __builtin_signbit = @import("std").zig.c_builtins.__builtin_signbit; -pub const __builtin_signbitf = @import("std").zig.c_builtins.__builtin_signbitf; -pub const __builtin_popcount = @import("std").zig.c_builtins.__builtin_popcount; -pub const __builtin_ctz = @import("std").zig.c_builtins.__builtin_ctz; -pub const __builtin_clz = @import("std").zig.c_builtins.__builtin_clz; -pub const __builtin_sqrt = @import("std").zig.c_builtins.__builtin_sqrt; -pub const __builtin_sqrtf = @import("std").zig.c_builtins.__builtin_sqrtf; -pub const __builtin_sin = @import("std").zig.c_builtins.__builtin_sin; -pub const __builtin_sinf = @import("std").zig.c_builtins.__builtin_sinf; -pub const __builtin_cos = @import("std").zig.c_builtins.__builtin_cos; -pub const __builtin_cosf = @import("std").zig.c_builtins.__builtin_cosf; -pub const __builtin_exp = @import("std").zig.c_builtins.__builtin_exp; -pub const __builtin_expf = @import("std").zig.c_builtins.__builtin_expf; -pub const __builtin_exp2 = @import("std").zig.c_builtins.__builtin_exp2; -pub const __builtin_exp2f = @import("std").zig.c_builtins.__builtin_exp2f; -pub const __builtin_log = @import("std").zig.c_builtins.__builtin_log; -pub const __builtin_logf = @import("std").zig.c_builtins.__builtin_logf; -pub const __builtin_log2 = @import("std").zig.c_builtins.__builtin_log2; -pub const __builtin_log2f = @import("std").zig.c_builtins.__builtin_log2f; -pub const __builtin_log10 = @import("std").zig.c_builtins.__builtin_log10; -pub const __builtin_log10f = @import("std").zig.c_builtins.__builtin_log10f; -pub const __builtin_abs = @import("std").zig.c_builtins.__builtin_abs; -pub const __builtin_fabs = @import("std").zig.c_builtins.__builtin_fabs; -pub const __builtin_fabsf = @import("std").zig.c_builtins.__builtin_fabsf; -pub const __builtin_floor = @import("std").zig.c_builtins.__builtin_floor; -pub const __builtin_floorf = @import("std").zig.c_builtins.__builtin_floorf; -pub const __builtin_ceil = @import("std").zig.c_builtins.__builtin_ceil; -pub const __builtin_ceilf = @import("std").zig.c_builtins.__builtin_ceilf; -pub const __builtin_trunc = @import("std").zig.c_builtins.__builtin_trunc; -pub const __builtin_truncf = @import("std").zig.c_builtins.__builtin_truncf; -pub const __builtin_round = @import("std").zig.c_builtins.__builtin_round; -pub const __builtin_roundf = @import("std").zig.c_builtins.__builtin_roundf; -pub const __builtin_strlen = @import("std").zig.c_builtins.__builtin_strlen; -pub const __builtin_strcmp = @import("std").zig.c_builtins.__builtin_strcmp; -pub const __builtin_object_size = @import("std").zig.c_builtins.__builtin_object_size; -pub const __builtin___memset_chk = @import("std").zig.c_builtins.__builtin___memset_chk; -pub const __builtin_memset = @import("std").zig.c_builtins.__builtin_memset; -pub const __builtin___memcpy_chk = @import("std").zig.c_builtins.__builtin___memcpy_chk; -pub const __builtin_memcpy = @import("std").zig.c_builtins.__builtin_memcpy; -pub const __builtin_expect = @import("std").zig.c_builtins.__builtin_expect; -pub const __builtin_nanf = @import("std").zig.c_builtins.__builtin_nanf; -pub const __builtin_huge_valf = @import("std").zig.c_builtins.__builtin_huge_valf; -pub const __builtin_inff = @import("std").zig.c_builtins.__builtin_inff; -pub const __builtin_isnan = @import("std").zig.c_builtins.__builtin_isnan; -pub const __builtin_isinf = @import("std").zig.c_builtins.__builtin_isinf; -pub const __builtin_isinf_sign = @import("std").zig.c_builtins.__builtin_isinf_sign; -pub const __has_builtin = @import("std").zig.c_builtins.__has_builtin; -pub const __builtin_assume = @import("std").zig.c_builtins.__builtin_assume; -pub const __builtin_unreachable = @import("std").zig.c_builtins.__builtin_unreachable; -pub const __builtin_constant_p = @import("std").zig.c_builtins.__builtin_constant_p; -pub const __builtin_mul_overflow = @import("std").zig.c_builtins.__builtin_mul_overflow; -pub const __u_char = u8; -pub const __u_short = c_ushort; -pub const __u_int = c_uint; -pub const __u_long = c_ulong; -pub const __int8_t = i8; -pub const __uint8_t = u8; -pub const __int16_t = c_short; -pub const __uint16_t = c_ushort; -pub const __int32_t = i32; -pub const __uint32_t = u32; -pub const __int64_t = i64; -pub const __uint64_t = u64; -pub const __int_least8_t = __int8_t; -pub const __uint_least8_t = __uint8_t; -pub const __int_least16_t = __int16_t; -pub const __uint_least16_t = __uint16_t; -pub const __int_least32_t = __int32_t; -pub const __uint_least32_t = __uint32_t; -pub const __int_least64_t = __int64_t; -pub const __uint_least64_t = __uint64_t; -pub const __quad_t = c_long; -pub const __u_quad_t = c_ulong; -pub const __intmax_t = c_long; -pub const __uintmax_t = c_ulong; -pub const __dev_t = c_ulong; -pub const __uid_t = c_uint; -pub const __gid_t = c_uint; -pub const __ino_t = c_ulong; -pub const __ino64_t = c_ulong; -pub const __mode_t = c_uint; -pub const __nlink_t = c_ulong; -pub const __off_t = c_long; -pub const __off64_t = c_long; -pub const __pid_t = c_int; -pub const __fsid_t = extern struct { - __val: [2]c_int, -}; -pub const __clock_t = c_long; -pub const __rlim_t = c_ulong; -pub const __rlim64_t = c_ulong; -pub const __id_t = c_uint; -pub const __time_t = c_long; -pub const __useconds_t = c_uint; -pub const __suseconds_t = c_long; -pub const __suseconds64_t = c_long; -pub const __daddr_t = c_int; -pub const __key_t = c_int; -pub const __clockid_t = c_int; -pub const __timer_t = ?*anyopaque; -pub const __blksize_t = c_long; -pub const __blkcnt_t = c_long; -pub const __blkcnt64_t = c_long; -pub const __fsblkcnt_t = c_ulong; -pub const __fsblkcnt64_t = c_ulong; -pub const __fsfilcnt_t = c_ulong; -pub const __fsfilcnt64_t = c_ulong; -pub const __fsword_t = c_long; -pub const __ssize_t = c_long; -pub const __syscall_slong_t = c_long; -pub const __syscall_ulong_t = c_ulong; -pub const __loff_t = __off64_t; -pub const __caddr_t = [*c]u8; -pub const __intptr_t = c_long; -pub const __socklen_t = c_uint; -pub const __sig_atomic_t = c_int; -pub const int_least8_t = __int_least8_t; -pub const int_least16_t = __int_least16_t; -pub const int_least32_t = __int_least32_t; -pub const int_least64_t = __int_least64_t; -pub const uint_least8_t = __uint_least8_t; -pub const uint_least16_t = __uint_least16_t; -pub const uint_least32_t = __uint_least32_t; -pub const uint_least64_t = __uint_least64_t; -pub const int_fast8_t = i8; -pub const int_fast16_t = c_long; -pub const int_fast32_t = c_long; -pub const int_fast64_t = c_long; -pub const uint_fast8_t = u8; -pub const uint_fast16_t = c_ulong; -pub const uint_fast32_t = c_ulong; -pub const uint_fast64_t = c_ulong; -pub const intmax_t = __intmax_t; -pub const uintmax_t = __uintmax_t; -pub const lsxpack_strlen_t = u16; -pub const lsxpack_offset_t = i32; -pub const LSXPACK_HPACK_VAL_MATCHED: c_int = 1; -pub const LSXPACK_QPACK_IDX: c_int = 2; -pub const LSXPACK_APP_IDX: c_int = 4; -pub const LSXPACK_NAME_HASH: c_int = 8; -pub const LSXPACK_NAMEVAL_HASH: c_int = 16; -pub const LSXPACK_VAL_MATCHED: c_int = 32; -pub const LSXPACK_NEVER_INDEX: c_int = 64; -pub const enum_lsxpack_flag = c_uint; - -// /// When header are decoded, it should be stored to @buf starting from @name_offset, -// /// : \r\n -// /// So, it can be used directly as HTTP/1.1 header. there are 4 extra characters -// /// added. -// /// -// /// limitation: we currently does not support total header size > 64KB. -pub const struct_lsxpack_header = extern struct { - /// the buffer for headers - buf: ?[*]u8 = null, - /// hash value for name - name_hash: __uint32_t = 0, - /// hash value for name + value - nameval_hash: __uint32_t = 0, - /// the offset for name in the buffer - name_offset: lsxpack_offset_t = 0, - /// the offset for value in the buffer - val_offset: lsxpack_offset_t = 0, - /// the length of name - name_len: lsxpack_strlen_t = 0, - /// the length of value - val_len: lsxpack_strlen_t = 0, - /// mainly for cookie value chain - chain_next_idx: __uint16_t = 0, - /// HPACK static table index - hpack_index: __uint8_t = 0, - /// QPACK static table index - qpack_index: __uint8_t = 0, - /// APP header index - app_index: __uint8_t = 0, - /// combination of lsxpack_flag - flags: u8 = 0, - /// control to disable index or not - indexed_type: __uint8_t = 0, - /// num of extra bytes written to decoded buffer - dec_overhead: __uint8_t = 0, -}; -pub const lsxpack_header_t = struct_lsxpack_header; - -pub fn lsxpack_header_set_offset2(arg_hdr: *lsxpack_header_t, arg_buf: [*c]u8, arg_name_offset: usize, arg_name_len: usize, arg_val_offset: usize, arg_val_len: usize) callconv(.C) void { - arg_hdr.* = .{}; - arg_hdr.buf = arg_buf; - arg_hdr.name_offset = @intCast(arg_name_offset); - arg_hdr.val_offset = @intCast(arg_val_offset); - arg_hdr.name_len = @truncate(arg_name_len); - arg_hdr.val_len = @truncate(arg_val_len); -} - -pub fn lsxpack_header_prepare_decode(arg_hdr: *lsxpack_header_t, arg_out: [*c]u8, arg_offset: usize, arg_len: usize) callconv(.C) void { - arg_hdr.* = .{}; - arg_hdr.buf = arg_out; - arg_hdr.name_offset = @intCast(arg_offset); - if (arg_len > LSXPACK_MAX_STRLEN) { - arg_hdr.val_len = LSXPACK_MAX_STRLEN; - } else { - arg_hdr.val_len = @truncate(arg_len); - } -} - -pub fn lsxpack_header_get_name(hdr: *lsxpack_header_t) []const u8 { - if (hdr.name_len != 0) return hdr.buf.?[@as(usize, @intCast(hdr.name_offset)) .. @as(usize, @intCast(hdr.name_offset)) + hdr.name_len]; - return ""; -} -pub fn lsxpack_header_get_value(hdr: *lsxpack_header_t) []const u8 { - if (hdr.val_len != 0) return hdr.buf.?[@as(usize, @intCast(hdr.val_offset)) .. @as(usize, @intCast(hdr.val_offset)) + hdr.val_len]; - return ""; -} -pub fn lsxpack_header_get_dec_size(hdr: ?*const lsxpack_header_t) callconv(.C) usize { - return @as(usize, @bitCast(@as(c_long, (@as(c_int, @bitCast(@as(c_uint, hdr.*.name_len))) + @as(c_int, @bitCast(@as(c_uint, hdr.*.val_len)))) + @as(c_int, @bitCast(@as(c_uint, hdr.*.dec_overhead)))))); -} -pub fn lsxpack_header_mark_val_changed(hdr: ?*lsxpack_header_t) callconv(.C) void { - hdr.*.flags = @as(c_uint, @bitCast(@as(c_int, @bitCast(hdr.*.flags)) & ~((LSXPACK_HPACK_VAL_MATCHED | LSXPACK_VAL_MATCHED) | LSXPACK_NAMEVAL_HASH))); -} -pub const struct_lshpack_enc_table_entry = opaque {}; -pub const struct_lshpack_enc_head = extern struct { - stqh_first: ?*struct_lshpack_enc_table_entry = @import("std").mem.zeroes(?*struct_lshpack_enc_table_entry), - stqh_last: [*c]?*struct_lshpack_enc_table_entry = @import("std").mem.zeroes([*c]?*struct_lshpack_enc_table_entry), -}; -pub const struct_lshpack_double_enc_head = opaque {}; -pub const LSHPACK_ENC_USE_HIST: c_int = 1; -const enum_unnamed_1 = c_uint; -pub const struct_lshpack_enc = extern struct { - hpe_cur_capacity: c_uint = @import("std").mem.zeroes(c_uint), - hpe_max_capacity: c_uint = @import("std").mem.zeroes(c_uint), - hpe_next_id: c_uint = @import("std").mem.zeroes(c_uint), - hpe_nelem: c_uint = @import("std").mem.zeroes(c_uint), - hpe_nbits: c_uint = @import("std").mem.zeroes(c_uint), - hpe_all_entries: struct_lshpack_enc_head = @import("std").mem.zeroes(struct_lshpack_enc_head), - hpe_buckets: ?*struct_lshpack_double_enc_head = @import("std").mem.zeroes(?*struct_lshpack_double_enc_head), - hpe_hist_buf: [*c]u32 = @import("std").mem.zeroes([*c]u32), - hpe_hist_size: c_uint = @import("std").mem.zeroes(c_uint), - hpe_hist_idx: c_uint = @import("std").mem.zeroes(c_uint), - hpe_hist_wrapped: c_int = @import("std").mem.zeroes(c_int), - hpe_flags: enum_unnamed_1 = @import("std").mem.zeroes(enum_unnamed_1), -}; -pub const struct_lshpack_arr = extern struct { - nalloc: c_uint = @import("std").mem.zeroes(c_uint), - nelem: c_uint = @import("std").mem.zeroes(c_uint), - off: c_uint = @import("std").mem.zeroes(c_uint), - els: [*c]usize = @import("std").mem.zeroes([*c]usize), -}; -pub const struct_lshpack_dec = extern struct { - hpd_dyn_table: struct_lshpack_arr = @import("std").mem.zeroes(struct_lshpack_arr), - hpd_max_capacity: c_uint = @import("std").mem.zeroes(c_uint), - hpd_cur_max_capacity: c_uint = @import("std").mem.zeroes(c_uint), - hpd_cur_capacity: c_uint = @import("std").mem.zeroes(c_uint), - hpd_state: c_uint = @import("std").mem.zeroes(c_uint), -}; -pub const LSHPACK_HDR_UNKNOWN: c_int = 0; -pub const LSHPACK_HDR_AUTHORITY: c_int = 1; -pub const LSHPACK_HDR_METHOD_GET: c_int = 2; -pub const LSHPACK_HDR_METHOD_POST: c_int = 3; -pub const LSHPACK_HDR_PATH: c_int = 4; -pub const LSHPACK_HDR_PATH_INDEX_HTML: c_int = 5; -pub const LSHPACK_HDR_SCHEME_HTTP: c_int = 6; -pub const LSHPACK_HDR_SCHEME_HTTPS: c_int = 7; -pub const LSHPACK_HDR_STATUS_200: c_int = 8; -pub const LSHPACK_HDR_STATUS_204: c_int = 9; -pub const LSHPACK_HDR_STATUS_206: c_int = 10; -pub const LSHPACK_HDR_STATUS_304: c_int = 11; -pub const LSHPACK_HDR_STATUS_400: c_int = 12; -pub const LSHPACK_HDR_STATUS_404: c_int = 13; -pub const LSHPACK_HDR_STATUS_500: c_int = 14; -pub const LSHPACK_HDR_ACCEPT_CHARSET: c_int = 15; -pub const LSHPACK_HDR_ACCEPT_ENCODING: c_int = 16; -pub const LSHPACK_HDR_ACCEPT_LANGUAGE: c_int = 17; -pub const LSHPACK_HDR_ACCEPT_RANGES: c_int = 18; -pub const LSHPACK_HDR_ACCEPT: c_int = 19; -pub const LSHPACK_HDR_ACCESS_CONTROL_ALLOW_ORIGIN: c_int = 20; -pub const LSHPACK_HDR_AGE: c_int = 21; -pub const LSHPACK_HDR_ALLOW: c_int = 22; -pub const LSHPACK_HDR_AUTHORIZATION: c_int = 23; -pub const LSHPACK_HDR_CACHE_CONTROL: c_int = 24; -pub const LSHPACK_HDR_CONTENT_DISPOSITION: c_int = 25; -pub const LSHPACK_HDR_CONTENT_ENCODING: c_int = 26; -pub const LSHPACK_HDR_CONTENT_LANGUAGE: c_int = 27; -pub const LSHPACK_HDR_CONTENT_LENGTH: c_int = 28; -pub const LSHPACK_HDR_CONTENT_LOCATION: c_int = 29; -pub const LSHPACK_HDR_CONTENT_RANGE: c_int = 30; -pub const LSHPACK_HDR_CONTENT_TYPE: c_int = 31; -pub const LSHPACK_HDR_COOKIE: c_int = 32; -pub const LSHPACK_HDR_DATE: c_int = 33; -pub const LSHPACK_HDR_ETAG: c_int = 34; -pub const LSHPACK_HDR_EXPECT: c_int = 35; -pub const LSHPACK_HDR_EXPIRES: c_int = 36; -pub const LSHPACK_HDR_FROM: c_int = 37; -pub const LSHPACK_HDR_HOST: c_int = 38; -pub const LSHPACK_HDR_IF_MATCH: c_int = 39; -pub const LSHPACK_HDR_IF_MODIFIED_SINCE: c_int = 40; -pub const LSHPACK_HDR_IF_NONE_MATCH: c_int = 41; -pub const LSHPACK_HDR_IF_RANGE: c_int = 42; -pub const LSHPACK_HDR_IF_UNMODIFIED_SINCE: c_int = 43; -pub const LSHPACK_HDR_LAST_MODIFIED: c_int = 44; -pub const LSHPACK_HDR_LINK: c_int = 45; -pub const LSHPACK_HDR_LOCATION: c_int = 46; -pub const LSHPACK_HDR_MAX_FORWARDS: c_int = 47; -pub const LSHPACK_HDR_PROXY_AUTHENTICATE: c_int = 48; -pub const LSHPACK_HDR_PROXY_AUTHORIZATION: c_int = 49; -pub const LSHPACK_HDR_RANGE: c_int = 50; -pub const LSHPACK_HDR_REFERER: c_int = 51; -pub const LSHPACK_HDR_REFRESH: c_int = 52; -pub const LSHPACK_HDR_RETRY_AFTER: c_int = 53; -pub const LSHPACK_HDR_SERVER: c_int = 54; -pub const LSHPACK_HDR_SET_COOKIE: c_int = 55; -pub const LSHPACK_HDR_STRICT_TRANSPORT_SECURITY: c_int = 56; -pub const LSHPACK_HDR_TRANSFER_ENCODING: c_int = 57; -pub const LSHPACK_HDR_USER_AGENT: c_int = 58; -pub const LSHPACK_HDR_VARY: c_int = 59; -pub const LSHPACK_HDR_VIA: c_int = 60; -pub const LSHPACK_HDR_WWW_AUTHENTICATE: c_int = 61; -pub const LSHPACK_HDR_TOBE_INDEXED: c_int = 255; -pub const enum_lshpack_static_hdr_idx = c_uint; -pub extern fn lshpack_enc_init([*c]struct_lshpack_enc) c_int; -pub extern fn lshpack_enc_cleanup([*c]struct_lshpack_enc) void; -pub extern fn lshpack_enc_encode(henc: [*c]struct_lshpack_enc, dst: [*c]const u8, dst_end: [*c]u8, input: ?*struct_lsxpack_header) [*c]u8; -pub extern fn lshpack_enc_set_max_capacity([*c]struct_lshpack_enc, c_uint) void; -pub extern fn lshpack_enc_use_hist([*c]struct_lshpack_enc, on: c_int) c_int; -pub extern fn lshpack_enc_hist_used([*c]const struct_lshpack_enc) c_int; -pub extern fn lshpack_dec_init([*c]struct_lshpack_dec) void; -pub extern fn lshpack_dec_cleanup([*c]struct_lshpack_dec) void; -pub extern fn lshpack_dec_decode(dec: [*c]struct_lshpack_dec, src: *[*c]const u8, src_end: [*c]const u8, output: ?*struct_lsxpack_header) c_int; -pub extern fn lshpack_dec_set_max_capacity([*c]struct_lshpack_dec, c_uint) void; -pub extern fn lshpack_enc_get_stx_tab_id(?*struct_lsxpack_header) c_uint; -pub fn lshpack_decode(dec: [*c]struct_lshpack_dec, src: [*]const u8, src_len: usize, output: ?*struct_lsxpack_header) !usize { - var s: [*c]const u8 = src; - const rc: c_int = lshpack_dec_decode(dec, &s, s + src_len, output); - if (rc != 0) { - return error.UnableToDecode; - } - return @intFromPtr(s) - @intFromPtr(src); -} -pub const __INT64_C = @import("std").zig.c_translation.Macros.L_SUFFIX; -pub const __UINT64_C = @import("std").zig.c_translation.Macros.UL_SUFFIX; -pub const INT8_MIN = -@as(c_int, 128); -pub const INT16_MIN = -@as(c_int, 32767) - @as(c_int, 1); -pub const INT32_MIN = -@import("std").zig.c_translation.promoteIntLiteral(c_int, 2147483647, .decimal) - @as(c_int, 1); -pub const INT64_MIN = -__INT64_C(@import("std").zig.c_translation.promoteIntLiteral(c_int, 9223372036854775807, .decimal)) - @as(c_int, 1); -pub const INT8_MAX = @as(c_int, 127); -pub const INT16_MAX = @as(c_int, 32767); -pub const INT32_MAX = @import("std").zig.c_translation.promoteIntLiteral(c_int, 2147483647, .decimal); -pub const INT64_MAX = __INT64_C(@import("std").zig.c_translation.promoteIntLiteral(c_int, 9223372036854775807, .decimal)); -pub const UINT8_MAX = @as(c_int, 255); -pub const UINT16_MAX = @import("std").zig.c_translation.promoteIntLiteral(c_int, 65535, .decimal); -pub const UINT32_MAX = @import("std").zig.c_translation.promoteIntLiteral(c_uint, 4294967295, .decimal); -pub const UINT64_MAX = __UINT64_C(@import("std").zig.c_translation.promoteIntLiteral(c_int, 18446744073709551615, .decimal)); -pub const INT_LEAST8_MIN = -@as(c_int, 128); -pub const INT_LEAST16_MIN = -@as(c_int, 32767) - @as(c_int, 1); -pub const INT_LEAST32_MIN = -@import("std").zig.c_translation.promoteIntLiteral(c_int, 2147483647, .decimal) - @as(c_int, 1); -pub const INT_LEAST64_MIN = -__INT64_C(@import("std").zig.c_translation.promoteIntLiteral(c_int, 9223372036854775807, .decimal)) - @as(c_int, 1); -pub const INT_LEAST8_MAX = @as(c_int, 127); -pub const INT_LEAST16_MAX = @as(c_int, 32767); -pub const INT_LEAST32_MAX = @import("std").zig.c_translation.promoteIntLiteral(c_int, 2147483647, .decimal); -pub const INT_LEAST64_MAX = __INT64_C(@import("std").zig.c_translation.promoteIntLiteral(c_int, 9223372036854775807, .decimal)); -pub const UINT_LEAST8_MAX = @as(c_int, 255); -pub const UINT_LEAST16_MAX = @import("std").zig.c_translation.promoteIntLiteral(c_int, 65535, .decimal); -pub const UINT_LEAST32_MAX = @import("std").zig.c_translation.promoteIntLiteral(c_uint, 4294967295, .decimal); -pub const UINT_LEAST64_MAX = __UINT64_C(@import("std").zig.c_translation.promoteIntLiteral(c_int, 18446744073709551615, .decimal)); -pub const INT_FAST8_MIN = -@as(c_int, 128); -pub const INT_FAST16_MIN = -@import("std").zig.c_translation.promoteIntLiteral(c_long, 9223372036854775807, .decimal) - @as(c_int, 1); -pub const INT_FAST32_MIN = -@import("std").zig.c_translation.promoteIntLiteral(c_long, 9223372036854775807, .decimal) - @as(c_int, 1); -pub const INT_FAST64_MIN = -__INT64_C(@import("std").zig.c_translation.promoteIntLiteral(c_int, 9223372036854775807, .decimal)) - @as(c_int, 1); -pub const INT_FAST8_MAX = @as(c_int, 127); -pub const INT_FAST16_MAX = @import("std").zig.c_translation.promoteIntLiteral(c_long, 9223372036854775807, .decimal); -pub const INT_FAST32_MAX = @import("std").zig.c_translation.promoteIntLiteral(c_long, 9223372036854775807, .decimal); -pub const INT_FAST64_MAX = __INT64_C(@import("std").zig.c_translation.promoteIntLiteral(c_int, 9223372036854775807, .decimal)); -pub const UINT_FAST8_MAX = @as(c_int, 255); -pub const UINT_FAST16_MAX = @import("std").zig.c_translation.promoteIntLiteral(c_ulong, 18446744073709551615, .decimal); -pub const UINT_FAST32_MAX = @import("std").zig.c_translation.promoteIntLiteral(c_ulong, 18446744073709551615, .decimal); -pub const UINT_FAST64_MAX = __UINT64_C(@import("std").zig.c_translation.promoteIntLiteral(c_int, 18446744073709551615, .decimal)); -pub const INTPTR_MIN = -@import("std").zig.c_translation.promoteIntLiteral(c_long, 9223372036854775807, .decimal) - @as(c_int, 1); -pub const INTPTR_MAX = @import("std").zig.c_translation.promoteIntLiteral(c_long, 9223372036854775807, .decimal); -pub const UINTPTR_MAX = @import("std").zig.c_translation.promoteIntLiteral(c_ulong, 18446744073709551615, .decimal); -pub const INTMAX_MIN = -__INT64_C(@import("std").zig.c_translation.promoteIntLiteral(c_int, 9223372036854775807, .decimal)) - @as(c_int, 1); -pub const INTMAX_MAX = __INT64_C(@import("std").zig.c_translation.promoteIntLiteral(c_int, 9223372036854775807, .decimal)); -pub const UINTMAX_MAX = __UINT64_C(@import("std").zig.c_translation.promoteIntLiteral(c_int, 18446744073709551615, .decimal)); -pub const PTRDIFF_MIN = -@import("std").zig.c_translation.promoteIntLiteral(c_long, 9223372036854775807, .decimal) - @as(c_int, 1); -pub const PTRDIFF_MAX = @import("std").zig.c_translation.promoteIntLiteral(c_long, 9223372036854775807, .decimal); -pub const SIG_ATOMIC_MIN = -@import("std").zig.c_translation.promoteIntLiteral(c_int, 2147483647, .decimal) - @as(c_int, 1); -pub const SIG_ATOMIC_MAX = @import("std").zig.c_translation.promoteIntLiteral(c_int, 2147483647, .decimal); -pub const SIZE_MAX = @import("std").zig.c_translation.promoteIntLiteral(c_ulong, 18446744073709551615, .decimal); -pub const WINT_MIN = @as(c_uint, 0); -pub const WINT_MAX = @import("std").zig.c_translation.promoteIntLiteral(c_uint, 4294967295, .decimal); -pub inline fn INT8_C(c: anytype) @TypeOf(c) { - return c; -} -pub inline fn INT16_C(c: anytype) @TypeOf(c) { - return c; -} -pub inline fn INT32_C(c: anytype) @TypeOf(c) { - return c; -} -pub const INT64_C = @import("std").zig.c_translation.Macros.L_SUFFIX; -pub inline fn UINT8_C(c: anytype) @TypeOf(c) { - return c; -} -pub inline fn UINT16_C(c: anytype) @TypeOf(c) { - return c; -} -pub const UINT32_C = @import("std").zig.c_translation.Macros.U_SUFFIX; -pub const UINT64_C = @import("std").zig.c_translation.Macros.UL_SUFFIX; -pub const INTMAX_C = @import("std").zig.c_translation.Macros.L_SUFFIX; -pub const UINTMAX_C = @import("std").zig.c_translation.Macros.UL_SUFFIX; -pub const LSXPACK_HEADER_H_v206 = ""; -pub const _ASSERT_H = @as(c_int, 1); -pub const _ASSERT_H_DECLS = ""; -pub const _STRING_H = @as(c_int, 1); -pub const __need_size_t = ""; -pub const __need_NULL = ""; -pub const _SIZE_T = ""; -pub const NULL = @import("std").zig.c_translation.cast(?*anyopaque, @as(c_int, 0)); -pub const _BITS_TYPES_LOCALE_T_H = @as(c_int, 1); -pub const _BITS_TYPES___LOCALE_T_H = @as(c_int, 1); -pub const _STRINGS_H = @as(c_int, 1); -pub const LSXPACK_MAX_STRLEN = UINT16_MAX; -pub const LSXPACK_DEL = @import("std").zig.c_translation.cast([*c]u8, NULL); -pub const LSHPACK_MAJOR_VERSION = @as(c_int, 2); -pub const LSHPACK_MINOR_VERSION = @as(c_int, 3); -pub const LSHPACK_PATCH_VERSION = @as(c_int, 0); -pub const lshpack_strlen_t = lsxpack_strlen_t; -pub const LSHPACK_MAX_STRLEN = LSXPACK_MAX_STRLEN; -pub const LSHPACK_DEC_HTTP1X_OUTPUT = @as(c_int, 1); -pub const LSHPACK_DEC_CALC_HASH = @as(c_int, 1); -pub const LSHPACK_MAX_INDEX = @as(c_int, 61); -pub const LSHPACK_ERR_MORE_BUF = -@as(c_int, 3); -pub const LSHPACK_ERR_TOO_LARGE = -@as(c_int, 2); -pub const LSHPACK_ERR_BAD_DATA = -@as(c_int, 1); -pub const LSHPACK_OK = @as(c_int, 0); -pub const LSHPACK_DEC_HTTP1X_EXTRA = @as(c_int, 2); -pub inline fn lshpack_dec_extra_bytes(dec_: anytype) @TypeOf(@as(c_int, 4)) { - _ = @TypeOf(dec_); - return @as(c_int, 4); -} -pub const lsxpack_flag = enum_lsxpack_flag; -pub const lsxpack_header = struct_lsxpack_header; -pub const lshpack_enc_table_entry = struct_lshpack_enc_table_entry; -pub const lshpack_enc_head = struct_lshpack_enc_head; -pub const lshpack_double_enc_head = struct_lshpack_double_enc_head; -pub const lshpack_enc = struct_lshpack_enc; -pub const lshpack_arr = struct_lshpack_arr; -pub const lshpack_dec = struct_lshpack_dec; -pub const lshpack_static_hdr_idx = enum_lshpack_static_hdr_idx; diff --git a/src/bun.js/api/bun/lshpack.zig b/src/bun.js/api/bun/lshpack.zig new file mode 100644 index 0000000000..d9215f1542 --- /dev/null +++ b/src/bun.js/api/bun/lshpack.zig @@ -0,0 +1,61 @@ +const bun = @import("root").bun; + +const lshpack_header = extern struct { + name: [*]const u8 = undefined, + name_len: usize = 0, + value: [*]const u8 = undefined, + value_len: usize = 0, +}; + +/// wrapper implemented at src/bun.js/bindings/c-bindings.cpp +pub const HPACK = extern struct { + self: *anyopaque, + + const mimalloc = @import("../../../allocators/mimalloc.zig"); + + pub const DecodeResult = struct { + name: []const u8, + value: []const u8, + // offset of the next header position in src + next: usize, + }; + + pub const LSHPACK_MAX_HEADER_SIZE: usize = 65536; + + pub fn init(max_capacity: u32) *HPACK { + return lshpack_wrapper_init(mimalloc.mi_malloc, mimalloc.mi_free, max_capacity) orelse bun.outOfMemory(); + } + + /// DecodeResult name and value uses a thread_local shared buffer and should be copy/cloned before the next decode/encode call + pub fn decode(self: *HPACK, src: []const u8) !DecodeResult { + var header: lshpack_header = .{}; + const offset = lshpack_wrapper_decode(self, src.ptr, src.len, &header); + if (offset == 0) return error.UnableToDecode; + if (header.name_len == 0) return error.EmptyHeaderName; + + return .{ + .name = header.name[0..header.name_len], + .value = header.value[0..header.value_len], + .next = offset, + }; + } + + /// encode name, value with never_index option into dst_buffer + /// if name + value length is greater than LSHPACK_MAX_HEADER_SIZE this will return UnableToEncode + pub fn encode(self: *HPACK, name: []const u8, value: []const u8, never_index: bool, dst_buffer: []u8, dst_buffer_offset: usize) !usize { + const offset = lshpack_wrapper_encode(self, name.ptr, name.len, value.ptr, value.len, @intFromBool(never_index), dst_buffer.ptr, dst_buffer.len, dst_buffer_offset); + if (offset <= 0) return error.UnableToEncode; + return offset; + } + + pub fn deinit(self: *HPACK) void { + lshpack_wrapper_deinit(self); + } +}; + +const lshpack_wrapper_alloc = ?*const fn (size: usize) callconv(.C) ?*anyopaque; +const lshpack_wrapper_free = ?*const fn (ptr: ?*anyopaque) callconv(.C) void; +extern fn lshpack_wrapper_init(alloc: lshpack_wrapper_alloc, free: lshpack_wrapper_free, capacity: usize) ?*HPACK; +extern fn lshpack_wrapper_deinit(self: *HPACK) void; +extern fn lshpack_wrapper_decode(self: *HPACK, src: [*]const u8, src_len: usize, output: *lshpack_header) usize; +extern fn lshpack_wrapper_encode(self: *HPACK, name: [*]const u8, name_len: usize, value: [*]const u8, value_len: usize, never_index: c_int, buffer: [*]u8, buffer_len: usize, buffer_offset: usize) usize; diff --git a/src/bun.js/bindings/c-bindings.cpp b/src/bun.js/bindings/c-bindings.cpp index 91f646e7cc..520032edd8 100644 --- a/src/bun.js/bindings/c-bindings.cpp +++ b/src/bun.js/bindings/c-bindings.cpp @@ -13,6 +13,7 @@ #include #include #endif // !OS(WINDOWS) +#include #if CPU(X86_64) && !OS(WINDOWS) extern "C" void bun_warn_avx_missing(const char* url) @@ -183,4 +184,91 @@ extern "C" void on_before_reload_process_linux() sigprocmask(SIG_SETMASK, &signal_set, nullptr); } -#endif \ No newline at end of file +#endif + +#define LSHPACK_MAX_HEADER_SIZE 65536 + +static thread_local char shared_header_buffer[LSHPACK_MAX_HEADER_SIZE]; + +extern "C" { +typedef void* (*lshpack_wrapper_alloc)(size_t size); +typedef void (*lshpack_wrapper_free)(void*); +typedef struct { + struct lshpack_enc enc; + struct lshpack_dec dec; + lshpack_wrapper_free free; +} lshpack_wrapper; + +typedef struct { + const char* name; + size_t name_len; + const char* value; + size_t value_len; +} lshpack_header; + +lshpack_wrapper* lshpack_wrapper_init(lshpack_wrapper_alloc alloc, lshpack_wrapper_free free, unsigned max_capacity) +{ + lshpack_wrapper* coders = (lshpack_wrapper*)alloc(sizeof(lshpack_wrapper)); + if (!coders) + return nullptr; + coders->free = free; + if (lshpack_enc_init(&coders->enc) != 0) + return nullptr; + lshpack_dec_init(&coders->dec); + lshpack_enc_set_max_capacity(&coders->enc, max_capacity); + lshpack_dec_set_max_capacity(&coders->dec, max_capacity); + return coders; +} + +size_t lshpack_wrapper_encode(lshpack_wrapper* self, + const unsigned char* name, size_t name_len, + const unsigned char* val, size_t val_len, + int never_index, + unsigned char* buffer, size_t buffer_len, size_t buffer_offset) +{ + if (name_len + val_len > LSHPACK_MAX_HEADER_SIZE) + return 0; + + lsxpack_header_t hdr; + memset(&hdr, 0, sizeof(lsxpack_header_t)); + memcpy(&shared_header_buffer[0], name, name_len); + memcpy(&shared_header_buffer[name_len], val, val_len); + lsxpack_header_set_offset2(&hdr, &shared_header_buffer[0], 0, name_len, name_len, val_len); + if (never_index) { + hdr.indexed_type = 2; + } + auto* start = buffer + buffer_offset; + auto* ptr = lshpack_enc_encode(&self->enc, start, buffer + buffer_len, &hdr); + if (!ptr) + return 0; + return ptr - start; +} + +size_t lshpack_wrapper_decode(lshpack_wrapper* self, + const unsigned char* src, size_t src_len, + lshpack_header* output) +{ + lsxpack_header_t hdr; + memset(&hdr, 0, sizeof(lsxpack_header_t)); + lsxpack_header_prepare_decode(&hdr, &shared_header_buffer[0], 0, LSHPACK_MAX_HEADER_SIZE); + + const unsigned char* s = src; + + auto rc = lshpack_dec_decode(&self->dec, &s, s + src_len, &hdr); + if (rc != 0) + return 0; + + output->name = lsxpack_header_get_name(&hdr); + output->name_len = hdr.name_len; + output->value = lsxpack_header_get_value(&hdr); + output->value_len = hdr.val_len; + return s - src; +} + +void lshpack_wrapper_deinit(lshpack_wrapper* self) +{ + lshpack_dec_cleanup(&self->dec); + lshpack_enc_cleanup(&self->enc); + self->free(self); +} +} \ No newline at end of file diff --git a/src/js/node/http2.ts b/src/js/node/http2.ts index 4375f15869..6909caa1f0 100644 --- a/src/js/node/http2.ts +++ b/src/js/node/http2.ts @@ -679,7 +679,10 @@ function emitAbortedNT(self, streams, streamId, error) { self.emit("streamError", error_instance); } class ClientHttp2Session extends Http2Session { + /// close indicates that we called closed #closed: boolean = false; + /// connected indicates that the connection/socket is connected + #connected: boolean = false; #queue: Array = []; #connections: number = 0; [bunHTTP2Socket]: TLSSocket | Socket | null; @@ -763,33 +766,33 @@ class ClientHttp2Session extends Http2Session { ) { if (!self) return; var stream = self.#streams.get(streamId); - if (stream) { - let status: string | number = headers[":status"] as string; - if (status) { - // client status is always number - status = parseInt(status as string, 10); - (headers as Record)[":status"] = status; - } + if (!stream) return; - let set_cookies = headers["set-cookie"]; - if (typeof set_cookies === "string") { - (headers as Record)["set-cookie"] = [set_cookies]; - } + let status: string | number = headers[":status"] as string; + if (status) { + // client status is always number + status = parseInt(status as string, 10); + (headers as Record)[":status"] = status; + } - let cookie = headers["cookie"]; - if ($isArray(cookie)) { - headers["cookie"] = (headers["cookie"] as string[]).join(";"); - } - if (stream[bunHTTP2StreamResponded]) { - try { - stream.emit("trailers", headers, flags); - } catch { - process.nextTick(emitStreamErrorNT, self, self.#streams, streamId, constants.NGHTTP2_PROTOCOL_ERROR, true); - } - } else { - stream[bunHTTP2StreamResponded] = true; - stream.emit("response", headers, flags); + let set_cookies = headers["set-cookie"]; + if (typeof set_cookies === "string") { + (headers as Record)["set-cookie"] = [set_cookies]; + } + + let cookie = headers["cookie"]; + if ($isArray(cookie)) { + headers["cookie"] = (headers["cookie"] as string[]).join(";"); + } + if (stream[bunHTTP2StreamResponded]) { + try { + stream.emit("trailers", headers, flags); + } catch { + process.nextTick(emitStreamErrorNT, self, self.#streams, streamId, constants.NGHTTP2_PROTOCOL_ERROR, true); } + } else { + stream[bunHTTP2StreamResponded] = true; + stream.emit("response", headers, flags); } }, localSettings(self: ClientHttp2Session, settings: Settings) { @@ -874,12 +877,13 @@ class ClientHttp2Session extends Http2Session { write(self: ClientHttp2Session, buffer: Buffer) { if (!self) return; const socket = self[bunHTTP2Socket]; - if (self.#closed) { - //queue - self.#queue.push(buffer); - } else { + if (!socket) return; + if (self.#connected) { // redirect writes to socket socket.write(buffer); + } else { + //queue + self.#queue.push(buffer); } }, }; @@ -899,6 +903,7 @@ class ClientHttp2Session extends Http2Session { #onConnect() { const socket = this[bunHTTP2Socket]; if (!socket) return; + this.#connected = true; // check if h2 is supported only for TLSSocket if (socket instanceof TLSSocket) { if (socket.alpnProtocol !== "h2") { @@ -918,6 +923,7 @@ class ClientHttp2Session extends Http2Session { // TODO: make a native bindings on data and write and fallback to non-native socket.on("data", this.#onRead.bind(this)); + // redirect the queued buffers const queue = this.#queue; while (queue.length) { @@ -1129,6 +1135,7 @@ class ClientHttp2Session extends Http2Session { destroy(error?: Error, code?: number) { const socket = this[bunHTTP2Socket]; this.#closed = true; + this.#connected = false; code = code || constants.NGHTTP2_NO_ERROR; if (socket) { this.goaway(code, 0, Buffer.alloc(0)); diff --git a/test/js/node/http2/node-http2.test.js b/test/js/node/http2/node-http2.test.js index 6fd2ea7091..a509c92e31 100644 --- a/test/js/node/http2/node-http2.test.js +++ b/test/js/node/http2/node-http2.test.js @@ -1064,7 +1064,7 @@ describe("Client Basics", () => { const settings = new http2utils.SettingsFrame(true); socket.write(settings.data); const frame = new http2utils.Frame(7, 7, 0, 0).data; - socket.end(Buffer.concat([frame, Buffer.alloc(7)])); + socket.write(Buffer.concat([frame, Buffer.alloc(7)])); }); server.listen(0, "127.0.0.1", async () => { const url = `http://127.0.0.1:${server.address().port}`; @@ -1095,7 +1095,7 @@ describe("Client Basics", () => { socket.write(settings.data); await waitToWrite; const frame = new http2utils.DataFrame(1, Buffer.alloc(16384 * 2), 0, 1).data; - socket.end(frame); + socket.write(frame); }); server.listen(0, "127.0.0.1", async () => { const url = `http://127.0.0.1:${server.address().port}`; @@ -1127,7 +1127,7 @@ describe("Client Basics", () => { socket.write(settings.data); await waitToWrite; const frame = new http2utils.Frame(4, 3, 0, 0).data; - socket.end(Buffer.concat([frame, Buffer.alloc(4)])); + socket.write(Buffer.concat([frame, Buffer.alloc(4)])); }); server.listen(0, "127.0.0.1", async () => { const url = `http://127.0.0.1:${server.address().port}`; @@ -1159,7 +1159,7 @@ describe("Client Basics", () => { socket.write(settings.data); await waitToWrite; const frame = new http2utils.Frame(3, 3, 0, 1).data; - socket.end(Buffer.concat([frame, Buffer.alloc(3)])); + socket.write(Buffer.concat([frame, Buffer.alloc(3)])); }); server.listen(0, "127.0.0.1", async () => { const url = `http://127.0.0.1:${server.address().port}`; @@ -1192,7 +1192,7 @@ describe("Client Basics", () => { await waitToWrite; const buffer = Buffer.alloc(16384 * 2); const frame = new http2utils.Frame(buffer.byteLength, 3, 0, 1).data; - socket.end(Buffer.concat([frame, buffer])); + socket.write(Buffer.concat([frame, buffer])); }); server.listen(0, "127.0.0.1", async () => { const url = `http://127.0.0.1:${server.address().port}`; @@ -1262,7 +1262,7 @@ describe("Client Basics", () => { await waitToWrite; const frame = new http2utils.Frame(4, 2, 0, 1).data; - socket.end(Buffer.concat([frame, Buffer.alloc(4)])); + socket.write(Buffer.concat([frame, Buffer.alloc(4)])); }); server.listen(0, "127.0.0.1", async () => { const url = `http://127.0.0.1:${server.address().port}`;