diff options
author | 2023-08-29 22:13:50 -0300 | |
---|---|---|
committer | 2023-08-29 22:13:50 -0300 | |
commit | d134a261d5f710efd040122074fcfb9ed5dea23e (patch) | |
tree | 0639dc44b0f9f4a258ed8c13324a4c1d2cec7699 | |
parent | 557e912d9a9914bd1962ef632d146f760f5ffa39 (diff) | |
download | bun-d134a261d5f710efd040122074fcfb9ed5dea23e.tar.gz bun-d134a261d5f710efd040122074fcfb9ed5dea23e.tar.zst bun-d134a261d5f710efd040122074fcfb9ed5dea23e.zip |
init
-rw-r--r-- | src/bun.js/api/bun/h2_frame_parser.zig | 544 | ||||
-rw-r--r-- | src/bun.js/api/h2.classes.ts | 21 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses.cpp | 306 | ||||
-rw-r--r-- | src/bun.js/bindings/generated_classes.zig | 75 | ||||
-rw-r--r-- | src/bun.js/bindings/generated_classes_list.zig | 1 | ||||
-rw-r--r-- | src/jsc.zig | 1 |
6 files changed, 948 insertions, 0 deletions
diff --git a/src/bun.js/api/bun/h2_frame_parser.zig b/src/bun.js/api/bun/h2_frame_parser.zig new file mode 100644 index 000000000..2041aad69 --- /dev/null +++ b/src/bun.js/api/bun/h2_frame_parser.zig @@ -0,0 +1,544 @@ +const getAllocator = @import("../../base.zig").getAllocator; +const bun = @import("root").bun; +const Output = bun.Output; +const std = @import("std"); +const Allocator = std.mem.Allocator; +const JSC = bun.JSC; +const MutableString = bun.MutableString; +const native_endian = @import("builtin").target.cpu.arch.endian(); + +const JSValue = JSC.JSValue; + +const BinaryType = JSC.BinaryType; + +const FrameType = enum(u8) { + HTTP_FRAME_DATA = 0x00, + HTTP_FRAME_HEADERS = 0x01, + HTTP_FRAME_PRIORITY = 0x02, + HTTP_FRAME_RST_STREAM = 0x03, + HTTP_FRAME_SETTINGS = 0x04, + HTTP_FRAME_PUSH_PROMISE = 0x05, + HTTP_FRAME_PING = 0x06, + HTTP_FRAME_GOAWAY = 0x07, + HTTP_FRAME_WINDOW_UPDATE = 0x08, + HTTP_FRAME_CONTINUATION = 0x09, +}; + +const ErrorCode = enum(u32) { + NO_ERROR = 0x0, + PROTOCOL_ERROR = 0x1, + INTERNAL_ERROR = 0x2, + FLOW_CONTROL_ERROR = 0x3, + SETTINGS_TIMEOUT = 0x4, + STREAM_CLOSED = 0x5, + FRAME_SIZE_ERROR = 0x6, + REFUSED_STREAM = 0x7, + CANCEL = 0x8, + COMPRESSION_ERROR = 0x9, + CONNECT_ERROR = 0xa, + ENHANCE_YOUR_CALM = 0xb, + INADEQUATE_SECURITY = 0xc, + HTTP_1_1_REQUIRED = 0xd, +}; + +const SettingsType = enum(u16) { + SETTINGS_HEADER_TABLE_SIZE = 0x1, + SETTINGS_ENABLE_PUSH = 0x2, + SETTINGS_MAX_CONCURRENT_STREAMS = 0x3, + SETTINGS_INITIAL_WINDOW_SIZE = 0x4, + SETTINGS_MAX_FRAME_SIZE = 0x5, + SETTINGS_MAX_HEADER_LIST_SIZE = 0x6, +}; + +const UInt31WithReserved = packed struct(u32) { + reserved: bool = false, + uint31: u31 = 0, + + pub fn from(value: u32) UInt31WithReserved { + return @bitCast(value); + } +}; + +const FrameHeader = packed struct(u72) { + length: u24 = 0, + type: u8 = @intFromEnum(FrameType.HTTP_FRAME_SETTINGS), + flags: u8 = 0, + streamIdentifier: u32 = 0, + + pub const byteSize: usize = 9; + pub inline fn write(this: *FrameHeader, comptime Writer: type, writer: Writer) void { + var swap = this.*; + if (native_endian != .Big) { + std.mem.byteSwapAllFields(FrameHeader, &swap); + } + + _ = writer.write(std.mem.asBytes(&swap)[0..FrameHeader.byteSize]) catch 0; + } + + pub inline fn from(dst: *FrameHeader, src: []const u8, offset: usize, comptime end: bool) void { + @memcpy(@as(*[FrameHeader.byteSize]u8, @ptrCast(dst))[offset .. src.len + offset], src); + if (comptime end) { + if (native_endian != .Big) { + std.mem.byteSwapAllFields(FrameHeader, dst); + } + } + } +}; + +const SettingsPayloadUnit = packed struct(u48) { + type: u16, + value: u32, + pub const byteSize: usize = 6; + pub inline fn from(dst: *SettingsPayloadUnit, src: []const u8, offset: usize, comptime end: bool) void { + @memcpy(@as(*[SettingsPayloadUnit.byteSize]u8, @ptrCast(dst))[offset .. src.len + offset], src); + if (comptime end) { + if (native_endian != .Big) { + std.mem.byteSwapAllFields(SettingsPayloadUnit, dst); + } + } + } +}; + +const FullSettingsPayload = packed struct(u288) { + _headerTableSizeType: u16 = @intFromEnum(SettingsType.SETTINGS_HEADER_TABLE_SIZE), + headerTableSize: u32 = 4096, + _enablePushType: u16 = @intFromEnum(SettingsType.SETTINGS_ENABLE_PUSH), + enablePush: u32 = 1, + _maxConcurrentStreamsType: u16 = @intFromEnum(SettingsType.SETTINGS_MAX_CONCURRENT_STREAMS), + maxConcurrentStreams: u32 = 100, + _initialWindowSizeType: u16 = @intFromEnum(SettingsType.SETTINGS_INITIAL_WINDOW_SIZE), + initialWindowSize: u32 = 65535, + _maxFrameSizeType: u16 = @intFromEnum(SettingsType.SETTINGS_MAX_FRAME_SIZE), + maxFrameSize: u32 = 16384, + _maxHeaderListSizeType: u16 = @intFromEnum(SettingsType.SETTINGS_MAX_HEADER_LIST_SIZE), + maxHeaderListSize: u32 = 65535, + + pub const byteSize: usize = 36; + pub fn toJS(this: *FullSettingsPayload, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + var result = JSValue.createEmptyObject(globalObject, 6); + result.put(globalObject, JSC.ZigString.static("headerTableSize"), JSC.JSValue.jsNumber(this.headerTableSize)); + result.put(globalObject, JSC.ZigString.static("enablePush"), JSC.JSValue.jsNumber(this.enablePush)); + result.put(globalObject, JSC.ZigString.static("maxConcurrentStreams"), JSC.JSValue.jsNumber(this.maxConcurrentStreams)); + result.put(globalObject, JSC.ZigString.static("initialWindowSize"), JSC.JSValue.jsNumber(this.initialWindowSize)); + result.put(globalObject, JSC.ZigString.static("maxFrameSize"), JSC.JSValue.jsNumber(this.maxFrameSize)); + result.put(globalObject, JSC.ZigString.static("maxHeaderListSize"), JSC.JSValue.jsNumber(this.maxHeaderListSize)); + + return result; + } + + pub fn updateWith(this: *FullSettingsPayload, option: SettingsPayloadUnit) void { + switch (option.type) { + .SETTINGS_HEADER_TABLE_SIZE => this.headerTableSize = option.value, + .SETTINGS_ENABLE_PUSH => this.enablePush = option.value, + .SETTINGS_MAX_CONCURRENT_STREAMS => this.maxConcurrentStreams = option.value, + .SETTINGS_INITIAL_WINDOW_SIZE => this.initialWindowSize = option.value, + .SETTINGS_MAX_FRAME_SIZE => this.maxFrameSize = option.value, + .SETTINGS_MAX_HEADER_LIST_SIZE => this.maxHeaderListSize = option.value, + } + } + pub fn write(this: *FullSettingsPayload, comptime Writer: type, writer: Writer) void { + var swap = this.*; + + if (native_endian != .Big) { + std.mem.byteSwapAllFields(FullSettingsPayload, &swap); + } + _ = writer.write(std.mem.asBytes(&swap)[0..FullSettingsPayload.byteSize]) catch 0; + } +}; + +const Handlers = struct { + onError: JSC.JSValue = .zero, + onWrite: JSC.JSValue = .zero, + onStreamError: JSC.JSValue = .zero, + onStreamStart: JSC.JSValue = .zero, + onStreamHeaders: JSC.JSValue = .zero, + onStreamEnd: JSC.JSValue = .zero, + onStreamData: JSC.JSValue = .zero, + onRemoteSettings: JSC.JSValue = .zero, + onLocalSettings: JSC.JSValue = .zero, + binary_type: BinaryType = .Buffer, + + vm: *JSC.VirtualMachine, + globalObject: *JSC.JSGlobalObject, + + pub fn callEventHandler(this: *Handlers, comptime event: []const u8, thisValue: JSValue, data: []const JSValue) bool { + const callback = @field(this, event); + if (callback == .zero) { + return false; + } + + const result = callback.callWithThis(this.globalObject, thisValue, data); + if (result.isAnyError()) { + this.vm.onUnhandledError(this.globalObject, result); + } + + return true; + } + + pub fn callErrorHandler(this: *Handlers, thisValue: JSValue, err: []const JSValue) bool { + const onError = this.onError; + if (onError == .zero) { + if (err.len > 0) + this.vm.onUnhandledError(this.globalObject, err[0]); + + return false; + } + + const result = onError.callWithThis(this.globalObject, thisValue, err); + if (result.isAnyError()) { + this.vm.onUnhandledError(this.globalObject, result); + } + + return true; + } + + pub fn fromJS(globalObject: *JSC.JSGlobalObject, opts: JSC.JSValue, exception: JSC.C.ExceptionRef) ?Handlers { + var handlers = Handlers{ + .vm = globalObject.bunVM(), + .globalObject = globalObject, + }; + + if (opts.isEmptyOrUndefinedOrNull() or opts.isBoolean() or !opts.isObject()) { + exception.* = JSC.toInvalidArguments("Expected \"handlers\" to be an object", .{}, globalObject).asObjectRef(); + return null; + } + + const pairs = .{ + .{ "onStreamStart", "streamStart" }, + .{ "onStreamHeaders", "streamHeaders" }, + .{ "onStreamEnd", "streamEnd" }, + .{ "onStreamData", "streamData" }, + .{ "onStreamError", "streamError" }, + .{ "onRemoteSettings", "remoteSettings" }, + .{ "onLocalSettings", "localSettings" }, + .{ "onError", "error" }, + .{ "onWrite", "write" }, + }; + inline for (pairs) |pair| { + if (opts.getTruthy(globalObject, pair.@"1")) |callback_value| { + if (!callback_value.isCell() or !callback_value.isCallable(globalObject.vm())) { + exception.* = JSC.toInvalidArguments(comptime std.fmt.comptimePrint("Expected \"{s}\" callback to be a function", .{pair.@"1"}), .{}, globalObject).asObjectRef(); + return null; + } + + @field(handlers, pair.@"0") = callback_value; + } + } + + if (handlers.onWrite == .zero) { + exception.* = JSC.toInvalidArguments("Expected at least \"write\" callback", .{}, globalObject).asObjectRef(); + return null; + } + + if (opts.getTruthy(globalObject, "binaryType")) |binary_type_value| { + if (!binary_type_value.isString()) { + exception.* = JSC.toInvalidArguments("Expected \"binaryType\" to be a string", .{}, globalObject).asObjectRef(); + return null; + } + + handlers.binary_type = BinaryType.fromJSValue(globalObject, binary_type_value) orelse { + exception.* = JSC.toInvalidArguments("Expected 'binaryType' to be 'arraybuffer', 'uint8array', 'buffer'", .{}, globalObject).asObjectRef(); + return null; + }; + } + + return handlers; + } + + pub fn unprotect(this: *Handlers) void { + this.onError.unprotect(); + this.onWrite.unprotect(); + this.onStreamError.unprotect(); + this.onStreamStart.unprotect(); + this.onStreamHeaders.unprotect(); + this.onStreamEnd.unprotect(); + this.onStreamData.unprotect(); + this.onStreamError.unprotect(); + this.onLocalSettings.unprotect(); + this.onRemoteSettings.unprotect(); + } + + pub fn clear(this: *Handlers) void { + this.onError = .zero; + this.onWrite = .zero; + this.onStreamError = .zero; + this.onStreamStart = .zero; + this.onStreamHeaders = .zero; + this.onStreamEnd = .zero; + this.onStreamData = .zero; + this.onStreamError = .zero; + this.onLocalSettings = .zero; + this.onRemoteSettings = .zero; + } + + pub fn protect(this: *Handlers) void { + this.onError.protect(); + this.onWrite.protect(); + this.onStreamError.protect(); + this.onStreamStart.protect(); + this.onStreamHeaders.protect(); + this.onStreamEnd.protect(); + this.onStreamData.protect(); + this.onStreamError.protect(); + this.onLocalSettings.protect(); + this.onRemoteSettings.protect(); + } +}; + +pub const H2FrameParser = struct { + pub const log = Output.scoped(.H2FrameParser, false); + pub usingnamespace JSC.Codegen.JSH2FrameParser; + + strong_ctx: JSC.Strong = .{}, + allocator: Allocator, + handlers: Handlers, + localSettings: FullSettingsPayload = .{}, + // only available after receiving settings or ACK + remoteSettings: ?FullSettingsPayload = null, + // current frame being read + currentFrame: ?FrameHeader = null, + // remaining bytes to read for the current frame + remainingLength: i32 = 0, + // buffer if more data is needed for the current frame + readBuffer: MutableString, + + pub fn dispatch(this: *H2FrameParser, comptime event: []const u8, value: JSC.JSValue) void { + JSC.markBinding(@src()); + const ctx_value = this.strong_ctx.get() orelse JSC.JSValue.jsUndefined(); + value.ensureStillAlive(); + _ = this.handlers.callEvent(event, ctx_value, &[_]JSC.JSValue{ ctx_value, value }); + } + + 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); + } + + pub fn detach(this: *H2FrameParser, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue { + JSC.markBinding(@src()); + log("detach", .{}); + var handler = this.handlers; + defer handler.unprotect(); + this.handlers.clear(); + + return JSC.JSValue.jsUndefined(); + } + pub fn handleSettingsFrame(this: *H2FrameParser, frame: FrameHeader, data: []const u8) usize { + if (frame.streamIdentifier != 0) { + // this.#connection.write(createGoAwayFrameBuffer(this.#lastStreamID, ErrorCode.PROTOCOL_ERROR, Buffer.alloc(0))); + + // throw new Error("PROTOCOL_ERROR"); + log("PROTOCOL_ERROR", .{}); + return data.len; + } + const settingByteSize = SettingsPayloadUnit.byteSize; + if (frame.length > 0) { + if (frame.flags & 0x1 or frame.length % settingByteSize != 0) { + // this.#connection.write( + // createGoAwayFrameBuffer(this.#lastStreamID, ErrorCode.FRAME_SIZE_ERROR, Buffer.alloc(0)), + // ); + log("FRAME_SIZE_ERROR", .{}); + return data.len; + } + } else { + if (frame.flags & 0x1) { + // we received an ACK + this.remoteSettings = &this.localSettings; + this.dispatch(.onLocalSettings, this.localSettings.toJS(this.handlers.globalObject)); + } + return 0; + } + + const end: i32 = @min(frame.remainingLength, data.len); + const payload = data[0..end]; + this.remainingLength -= end; + if (this.remainingLength > 0) { + // buffer more data + _ = this.readBuffer.appendSlice(payload) catch @panic("OOM"); + return data.len; + } else if (this.remainingLength < 0) { + // this.#connection.write( + // createGoAwayFrameBuffer(this.#lastStreamID, ErrorCode.FRAME_SIZE_ERROR, Buffer.alloc(0)), + // ); + log("FRAME_SIZE_ERROR", .{}); + return data.len; + } + + this.currentFrame = null; + var remoteSettings = this.remoteSettings orelse this.localSettings; + var i: usize = 0; + while (i < payload.len) { + defer i += settingByteSize; + var unit: SettingsPayloadUnit = undefined; + SettingsPayloadUnit.from(&unit, payload[i .. i + settingByteSize], 0, true); + remoteSettings.updateWith(unit); + } + this.remoteSettings = remoteSettings; + this.dispatch(.onRemoteSettings, remoteSettings.toJS(this.handlers.globalObject)); + + // console.log(this.#settings); + // this.#compressor = hpack.compressor.create({ table: { size: this.#settings.headerTableSize } }); + // this.#decompressor = hpack.decompressor.create({ table: { size: this.#settings.headerTableSize } }); + + return end; + } + + pub fn readBytes(this: *H2FrameParser, bytes: []u8) usize { + log("read", .{}); + if (this.currentFrame) |header| { + log("header {} {} {} {}", .{ header.type, header.length, header.flags, header.streamIdentifier }); + return bytes.len; + } + + // nothing to do + if (bytes.len == 0) return bytes.len; + + const buffered_data = this.readBuffer.list.items.len; + + var header: FrameHeader = undefined; + // we can have less than 9 bytes buffered + if (buffered_data > 0) { + const total = buffered_data + bytes.len; + if (total < FrameHeader.byteSize) { + // buffer more data + _ = this.readBuffer.appendSlice(bytes) catch @panic("OOM"); + return bytes.len; + } + FrameHeader.from(&header, this.readBuffer.list.items[0..buffered_data], 0, false); + const needed = FrameHeader.byteSize - buffered_data; + FrameHeader.from(&header, bytes[0..needed], buffered_data, true); + // ignore the reserved bit + const id = UInt31WithReserved.from(header.streamIdentifier); + header.streamIdentifier = @intCast(id.uint31); + // reset for later use + this.readBuffer.reset(); + + this.currentFrame = header; + this.remainingLength = header.length; + log("header {} {} {} {}", .{ header.type, header.length, header.flags, header.streamIdentifier }); + + return switch (@as(FrameType, @enumFromInt(header.type))) { + FrameType.HTTP_FRAME_SETTINGS => this.handleSettingsFrame(header, bytes[needed..]) + needed, + else => { + log("not implemented {}", .{header.type}); + return bytes.len; + }, + }; + } + + if (bytes.len < FrameHeader.byteSize) { + // buffer more dheaderata + this.readBuffer.appendSlice(bytes) catch @panic("OOM"); + return bytes.len; + } + + FrameHeader.from(&header, bytes[0..FrameHeader.byteSize], 0, true); + + log("header {} {} {} {}", .{ header.type, header.length, header.flags, header.streamIdentifier }); + this.currentFrame = header; + this.remainingLength = header.length; + return switch (@as(FrameType, @enumFromInt(header.type))) { + FrameType.HTTP_FRAME_SETTINGS => this.handleSettingsFrame(header, bytes[FrameHeader.byteSize..]) + FrameHeader.byteSize, + else => { + log("not implemented {}", .{header.type}); + return bytes.len; + }, + }; + } + + pub fn read(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue { + JSC.markBinding(@src()); + const args_list = callframe.arguments(1); + if (args_list.len < 1) { + globalObject.throw("Expected 1 argument", .{}); + return .zero; + } + const buffer = args_list.ptr[0]; + if (buffer.asArrayBuffer(globalObject)) |array_buffer| { + var bytes = array_buffer.slice(); + + // read all the bytes + while (bytes.len > 0) { + const result = this.readBytes(bytes); + if (result >= bytes.len) { + break; + } + bytes = bytes[result..]; + } + return JSC.JSValue.jsUndefined(); + } + globalObject.throw("Expected data to be a Buffer or ArrayBuffer", .{}); + return .zero; + } + + pub fn constructor(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) ?*H2FrameParser { + const args_list = callframe.arguments(1); + if (args_list.len < 1) { + globalObject.throw("Expected 1 argument", .{}); + return null; + } + + const options = args_list.ptr[0]; + if (options.isEmptyOrUndefinedOrNull() or options.isBoolean() or !options.isObject()) { + globalObject.throwInvalidArguments("expected options as argument", .{}); + return null; + } + + var exception: JSC.C.JSValueRef = null; + var context_obj = options.get(globalObject, "context") orelse { + globalObject.throw("Expected \"context\" option", .{}); + return null; + }; + const handlers = Handlers.fromJS(globalObject, options, &exception) orelse { + globalObject.throwValue(exception.?.value()); + return null; + }; + const allocator = getAllocator(globalObject); + var this = allocator.create(H2FrameParser) catch unreachable; + + this.* = H2FrameParser{ + .handlers = handlers, + .allocator = allocator, + .readBuffer = .{ + .allocator = bun.default_allocator, + .list = .{ + .items = &.{}, + .capacity = 0, + }, + }, + }; + this.handlers.protect(); + + this.strong_ctx.set(globalObject, context_obj); + + // PREFACE + Settings Frame + var preface_buffer: [24 + FrameHeader.byteSize + FullSettingsPayload.byteSize]u8 = undefined; + @memset(&preface_buffer, 0); + var preface_stream = std.io.fixedBufferStream(&preface_buffer); + const writer = preface_stream.writer(); + _ = writer.write("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") catch 0; + var settingsHeader: FrameHeader = .{ + .type = @intFromEnum(FrameType.HTTP_FRAME_SETTINGS), + .flags = 0, + .streamIdentifier = 0, + .length = 36, + }; + settingsHeader.write(@TypeOf(writer), writer); + this.localSettings.write(@TypeOf(writer), writer); + this.write(&preface_buffer); + return this; + } + + pub fn finalize( + this: *H2FrameParser, + ) callconv(.C) void { + var allocator = this.allocator; + this.strong_ctx.deinit(); + this.handlers.unprotect(); + this.readBuffer.deinit(); + allocator.destroy(this); + } +}; diff --git a/src/bun.js/api/h2.classes.ts b/src/bun.js/api/h2.classes.ts new file mode 100644 index 000000000..caadb5c40 --- /dev/null +++ b/src/bun.js/api/h2.classes.ts @@ -0,0 +1,21 @@ +import { define } from "../scripts/class-definitions"; + +export default [ + define({ + name: "H2FrameParser", + JSType: "0b11101110", + proto: { + read: { + fn: "read", + length: 1, + }, + detach: { + fn: "detach", + length: 0, + }, + }, + finalize: true, + construct: true, + klass: {}, + }), +]; diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp index 0ab7a1b5d..376260383 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.cpp +++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp @@ -10976,6 +10976,312 @@ void JSFileSystemRouter::visitOutputConstraintsImpl(JSCell* cell, Visitor& visit } DEFINE_VISIT_OUTPUT_CONSTRAINTS(JSFileSystemRouter); +class JSH2FrameParserPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSH2FrameParserPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSH2FrameParserPrototype* ptr = new (NotNull, JSC::allocateCell<JSH2FrameParserPrototype>(vm)) JSH2FrameParserPrototype(vm, globalObject, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + template<typename CellType, JSC::SubspaceAccess> + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + +private: + JSH2FrameParserPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +class JSH2FrameParserConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + static JSH2FrameParserConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSH2FrameParserPrototype* prototype); + + static constexpr unsigned StructureFlags = Base::StructureFlags; + static constexpr bool needsDestruction = false; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); + } + + template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl<JSH2FrameParserConstructor, WebCore::UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForH2FrameParserConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForH2FrameParserConstructor = std::forward<decltype(space)>(space); }, + [](auto& spaces) { return spaces.m_subspaceForH2FrameParserConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForH2FrameParserConstructor = std::forward<decltype(space)>(space); }); + } + + void initializeProperties(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSH2FrameParserPrototype* prototype); + + // Must be defined for each specialization class. + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); + + DECLARE_EXPORT_INFO; + +private: + JSH2FrameParserConstructor(JSC::VM& vm, JSC::Structure* structure); + void finishCreation(JSC::VM&, JSC::JSGlobalObject* globalObject, JSH2FrameParserPrototype* prototype); +}; + +extern "C" void* H2FrameParserClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); +JSC_DECLARE_CUSTOM_GETTER(jsH2FrameParserConstructor); + +extern "C" void H2FrameParserClass__finalize(void*); + +extern "C" EncodedJSValue H2FrameParserPrototype__detach(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(H2FrameParserPrototype__detachCallback); + +extern "C" EncodedJSValue H2FrameParserPrototype__read(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(H2FrameParserPrototype__readCallback); + +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSH2FrameParserPrototype, JSH2FrameParserPrototype::Base); + +static const HashTableValue JSH2FrameParserPrototypeTableValues[] = { + { "detach"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, H2FrameParserPrototype__detachCallback, 0 } }, + { "read"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, H2FrameParserPrototype__readCallback, 1 } } +}; + +const ClassInfo JSH2FrameParserPrototype::s_info = { "H2FrameParser"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSH2FrameParserPrototype) }; + +JSC_DEFINE_CUSTOM_GETTER(jsH2FrameParserConstructor, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject); + auto* prototype = jsDynamicCast<JSH2FrameParserPrototype*>(JSValue::decode(thisValue)); + + if (UNLIKELY(!prototype)) + return throwVMTypeError(lexicalGlobalObject, throwScope, "Cannot get constructor for H2FrameParser"_s); + return JSValue::encode(globalObject->JSH2FrameParserConstructor()); +} + +JSC_DEFINE_HOST_FUNCTION(H2FrameParserPrototype__detachCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSH2FrameParser* thisObject = jsDynamicCast<JSH2FrameParser*>(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + throwVMTypeError(lexicalGlobalObject, throwScope, "Expected 'this' to be instanceof H2FrameParser"_s); + return JSValue::encode({}); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + +#ifdef BUN_DEBUG + /** View the file name of the JS file that called this function + * from a debugger */ + SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm); + const char* fileName = sourceOrigin.string().utf8().data(); + static const char* lastFileName = nullptr; + if (lastFileName != fileName) { + lastFileName = fileName; + } +#endif + + return H2FrameParserPrototype__detach(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + +JSC_DEFINE_HOST_FUNCTION(H2FrameParserPrototype__readCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSH2FrameParser* thisObject = jsDynamicCast<JSH2FrameParser*>(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + throwVMTypeError(lexicalGlobalObject, throwScope, "Expected 'this' to be instanceof H2FrameParser"_s); + return JSValue::encode({}); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + +#ifdef BUN_DEBUG + /** View the file name of the JS file that called this function + * from a debugger */ + SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm); + const char* fileName = sourceOrigin.string().utf8().data(); + static const char* lastFileName = nullptr; + if (lastFileName != fileName) { + lastFileName = fileName; + } +#endif + + return H2FrameParserPrototype__read(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + +void JSH2FrameParserPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSH2FrameParser::info(), JSH2FrameParserPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +void JSH2FrameParserConstructor::finishCreation(VM& vm, JSC::JSGlobalObject* globalObject, JSH2FrameParserPrototype* prototype) +{ + Base::finishCreation(vm, 0, "H2FrameParser"_s, PropertyAdditionMode::WithoutStructureTransition); + + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + ASSERT(inherits(info())); +} + +JSH2FrameParserConstructor::JSH2FrameParserConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, construct, construct) +{ +} + +JSH2FrameParserConstructor* JSH2FrameParserConstructor::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSH2FrameParserPrototype* prototype) +{ + JSH2FrameParserConstructor* ptr = new (NotNull, JSC::allocateCell<JSH2FrameParserConstructor>(vm)) JSH2FrameParserConstructor(vm, structure); + ptr->finishCreation(vm, globalObject, prototype); + return ptr; +} + +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSH2FrameParserConstructor::construct(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) +{ + Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject); + JSC::VM& vm = globalObject->vm(); + JSObject* newTarget = asObject(callFrame->newTarget()); + auto* constructor = globalObject->JSH2FrameParserConstructor(); + Structure* structure = globalObject->JSH2FrameParserStructure(); + if (constructor != newTarget) { + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* functionGlobalObject = reinterpret_cast<Zig::GlobalObject*>( + // ShadowRealm functions belong to a different global object. + getFunctionRealm(globalObject, newTarget)); + RETURN_IF_EXCEPTION(scope, {}); + structure = InternalFunction::createSubclassStructure( + globalObject, + newTarget, + functionGlobalObject->JSH2FrameParserStructure()); + } + + void* ptr = H2FrameParserClass__construct(globalObject, callFrame); + + if (UNLIKELY(!ptr)) { + return JSValue::encode(JSC::jsUndefined()); + } + + JSH2FrameParser* instance = JSH2FrameParser::create(vm, globalObject, structure, ptr); + + return JSValue::encode(instance); +} + +void JSH2FrameParserConstructor::initializeProperties(VM& vm, JSC::JSGlobalObject* globalObject, JSH2FrameParserPrototype* prototype) +{ +} + +const ClassInfo JSH2FrameParserConstructor::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSH2FrameParserConstructor) }; + +extern "C" EncodedJSValue H2FrameParser__getConstructor(Zig::GlobalObject* globalObject) +{ + return JSValue::encode(globalObject->JSH2FrameParserConstructor()); +} + +JSH2FrameParser::~JSH2FrameParser() +{ + if (m_ctx) { + H2FrameParserClass__finalize(m_ctx); + } +} +void JSH2FrameParser::destroy(JSCell* cell) +{ + static_cast<JSH2FrameParser*>(cell)->JSH2FrameParser::~JSH2FrameParser(); +} + +const ClassInfo JSH2FrameParser::s_info = { "H2FrameParser"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSH2FrameParser) }; + +void JSH2FrameParser::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); +} + +JSH2FrameParser* JSH2FrameParser::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx) +{ + JSH2FrameParser* ptr = new (NotNull, JSC::allocateCell<JSH2FrameParser>(vm)) JSH2FrameParser(vm, structure, ctx); + ptr->finishCreation(vm); + return ptr; +} + +extern "C" void* H2FrameParser__fromJS(JSC::EncodedJSValue value) +{ + JSC::JSValue decodedValue = JSC::JSValue::decode(value); + if (decodedValue.isEmpty() || !decodedValue.isCell()) + return nullptr; + + JSC::JSCell* cell = decodedValue.asCell(); + JSH2FrameParser* object = JSC::jsDynamicCast<JSH2FrameParser*>(cell); + + if (!object) + return nullptr; + + return object->wrapped(); +} + +extern "C" bool H2FrameParser__dangerouslySetPtr(JSC::EncodedJSValue value, void* ptr) +{ + JSH2FrameParser* object = JSC::jsDynamicCast<JSH2FrameParser*>(JSValue::decode(value)); + if (!object) + return false; + + object->m_ctx = ptr; + return true; +} + +extern "C" const size_t H2FrameParser__ptrOffset = JSH2FrameParser::offsetOfWrapped(); + +void JSH2FrameParser::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) +{ + auto* thisObject = jsCast<JSH2FrameParser*>(cell); + if (void* wrapped = thisObject->wrapped()) { + // if (thisObject->scriptExecutionContext()) + // analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string()); + } + Base::analyzeHeap(cell, analyzer); +} + +JSObject* JSH2FrameParser::createConstructor(VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ + return WebCore::JSH2FrameParserConstructor::create(vm, globalObject, WebCore::JSH2FrameParserConstructor::createStructure(vm, globalObject, globalObject->functionPrototype()), jsCast<WebCore::JSH2FrameParserPrototype*>(prototype)); +} + +JSObject* JSH2FrameParser::createPrototype(VM& vm, JSDOMGlobalObject* globalObject) +{ + return JSH2FrameParserPrototype::create(vm, globalObject, JSH2FrameParserPrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); +} + +extern "C" EncodedJSValue H2FrameParser__create(Zig::GlobalObject* globalObject, void* ptr) +{ + auto& vm = globalObject->vm(); + JSC::Structure* structure = globalObject->JSH2FrameParserStructure(); + JSH2FrameParser* instance = JSH2FrameParser::create(vm, globalObject, structure, ptr); + + return JSValue::encode(instance); +} class JSHTMLRewriterPrototype final : public JSC::JSNonFinalObject { public: using Base = JSC::JSNonFinalObject; diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig index e98b8f973..7d5571ff3 100644 --- a/src/bun.js/bindings/generated_classes.zig +++ b/src/bun.js/bindings/generated_classes.zig @@ -2945,6 +2945,80 @@ pub const JSFileSystemRouter = struct { } } }; +pub const JSH2FrameParser = struct { + const H2FrameParser = Classes.H2FrameParser; + const GetterType = fn (*H2FrameParser, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; + const GetterTypeWithThisValue = fn (*H2FrameParser, JSC.JSValue, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; + const SetterType = fn (*H2FrameParser, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool; + const SetterTypeWithThisValue = fn (*H2FrameParser, JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool; + const CallbackType = fn (*H2FrameParser, *JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) JSC.JSValue; + + /// Return the pointer to the wrapped object. + /// If the object does not match the type, return null. + pub fn fromJS(value: JSC.JSValue) ?*H2FrameParser { + JSC.markBinding(@src()); + return H2FrameParser__fromJS(value); + } + + /// Get the H2FrameParser constructor value. + /// This loads lazily from the global object. + pub fn getConstructor(globalObject: *JSC.JSGlobalObject) JSC.JSValue { + JSC.markBinding(@src()); + return H2FrameParser__getConstructor(globalObject); + } + + /// Create a new instance of H2FrameParser + pub fn toJS(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + JSC.markBinding(@src()); + if (comptime Environment.allow_assert) { + const value__ = H2FrameParser__create(globalObject, this); + std.debug.assert(value__.as(H2FrameParser).? == this); // If this fails, likely a C ABI issue. + return value__; + } else { + return H2FrameParser__create(globalObject, this); + } + } + + /// Modify the internal ptr to point to a new instance of H2FrameParser. + pub fn dangerouslySetPtr(value: JSC.JSValue, ptr: ?*H2FrameParser) bool { + JSC.markBinding(@src()); + return H2FrameParser__dangerouslySetPtr(value, ptr); + } + + /// Detach the ptr from the thisValue + pub fn detachPtr(_: *H2FrameParser, value: JSC.JSValue) void { + JSC.markBinding(@src()); + std.debug.assert(H2FrameParser__dangerouslySetPtr(value, null)); + } + + extern fn H2FrameParser__fromJS(JSC.JSValue) ?*H2FrameParser; + extern fn H2FrameParser__getConstructor(*JSC.JSGlobalObject) JSC.JSValue; + + extern fn H2FrameParser__create(globalObject: *JSC.JSGlobalObject, ptr: ?*H2FrameParser) JSC.JSValue; + + extern fn H2FrameParser__dangerouslySetPtr(JSC.JSValue, ?*H2FrameParser) bool; + + comptime { + if (@TypeOf(H2FrameParser.constructor) != (fn (*JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) ?*H2FrameParser)) { + @compileLog("H2FrameParser.constructor is not a constructor"); + } + + if (@TypeOf(H2FrameParser.finalize) != (fn (*H2FrameParser) callconv(.C) void)) { + @compileLog("H2FrameParser.finalize is not a finalizer"); + } + + if (@TypeOf(H2FrameParser.detach) != CallbackType) + @compileLog("Expected H2FrameParser.detach to be a callback but received " ++ @typeName(@TypeOf(H2FrameParser.detach))); + if (@TypeOf(H2FrameParser.read) != CallbackType) + @compileLog("Expected H2FrameParser.read to be a callback but received " ++ @typeName(@TypeOf(H2FrameParser.read))); + if (!JSC.is_bindgen) { + @export(H2FrameParser.constructor, .{ .name = "H2FrameParserClass__construct" }); + @export(H2FrameParser.detach, .{ .name = "H2FrameParserPrototype__detach" }); + @export(H2FrameParser.finalize, .{ .name = "H2FrameParserClass__finalize" }); + @export(H2FrameParser.read, .{ .name = "H2FrameParserPrototype__read" }); + } + } +}; pub const JSHTMLRewriter = struct { const HTMLRewriter = Classes.HTMLRewriter; const GetterType = fn (*HTMLRewriter, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; @@ -6804,6 +6878,7 @@ comptime { _ = JSFFI; _ = JSFSWatcher; _ = JSFileSystemRouter; + _ = JSH2FrameParser; _ = JSHTMLRewriter; _ = JSHTTPSServer; _ = JSHTTPServer; diff --git a/src/bun.js/bindings/generated_classes_list.zig b/src/bun.js/bindings/generated_classes_list.zig index 20f36a458..2cb3d8d39 100644 --- a/src/bun.js/bindings/generated_classes_list.zig +++ b/src/bun.js/bindings/generated_classes_list.zig @@ -53,4 +53,5 @@ pub const Classes = struct { pub const DebugHTTPSServer = JSC.API.DebugHTTPSServer; pub const Crypto = JSC.WebCore.Crypto; pub const FFI = JSC.FFI; + pub const H2FrameParser = JSC.API.H2FrameParser; }; diff --git a/src/jsc.zig b/src/jsc.zig index 1e844464a..b6c3f1323 100644 --- a/src/jsc.zig +++ b/src/jsc.zig @@ -44,6 +44,7 @@ pub const API = struct { pub const TCPSocket = @import("./bun.js/api/bun/socket.zig").TCPSocket; pub const TLSSocket = @import("./bun.js/api/bun/socket.zig").TLSSocket; pub const Listener = @import("./bun.js/api/bun/socket.zig").Listener; + pub const H2FrameParser = @import("./bun.js/api/bun/h2_frame_parser.zig").H2FrameParser; }; pub const DNS = @import("./bun.js/api/bun/dns_resolver.zig"); pub const FFI = @import("./bun.js/api/ffi.zig").FFI; |