diff --git a/cmake/sources/ZigSources.txt b/cmake/sources/ZigSources.txt index 14a169f1f8..180597e00f 100644 --- a/cmake/sources/ZigSources.txt +++ b/cmake/sources/ZigSources.txt @@ -399,7 +399,22 @@ src/deps/picohttp.zig src/deps/picohttpparser.zig src/deps/tcc.zig src/deps/uws.zig +src/deps/uws/App.zig +src/deps/uws/BodyReaderMixin.zig +src/deps/uws/ConnectingSocket.zig +src/deps/uws/InternalLoopData.zig +src/deps/uws/ListenSocket.zig +src/deps/uws/Loop.zig +src/deps/uws/Request.zig +src/deps/uws/Response.zig src/deps/uws/socket.zig +src/deps/uws/SocketContext.zig +src/deps/uws/Timer.zig +src/deps/uws/udp.zig +src/deps/uws/UpgradedDuplex.zig +src/deps/uws/us_socket_t.zig +src/deps/uws/WebSocket.zig +src/deps/uws/WindowsNamedPipe.zig src/deps/zig-clap/clap.zig src/deps/zig-clap/clap/args.zig src/deps/zig-clap/clap/comptime.zig diff --git a/src/bake/DevServer.zig b/src/bake/DevServer.zig index d5457488c4..1b33417f85 100644 --- a/src/bake/DevServer.zig +++ b/src/bake/DevServer.zig @@ -6043,7 +6043,7 @@ pub fn onWebSocketUpgrade( dev: *DevServer, res: anytype, req: *Request, - upgrade_ctx: *uws.uws_socket_context_t, + upgrade_ctx: *uws.SocketContext, id: usize, ) void { assert(id == 0); diff --git a/src/bun.js/VirtualMachine.zig b/src/bun.js/VirtualMachine.zig index 34098583ea..575c706ea0 100644 --- a/src/bun.js/VirtualMachine.zig +++ b/src/bun.js/VirtualMachine.zig @@ -3382,7 +3382,7 @@ pub const IPCInstance = struct { Process__emitDisconnectEvent(vm.global); event_loop.exit(); if (Environment.isPosix) { - uws.us_socket_context_free(0, this.context); + this.context.deinit(false); } vm.channel_ref.disable(); } @@ -3412,7 +3412,7 @@ pub fn getIPCInstance(this: *VirtualMachine) ?*IPCInstance { const instance = switch (Environment.os) { else => instance: { - const context = uws.us_create_bun_nossl_socket_context(this.event_loop_handle.?, @sizeOf(usize)).?; + const context = uws.SocketContext.createNoSSLContext(this.event_loop_handle.?, @sizeOf(usize)).?; IPC.Socket.configure(context, true, *IPC.SendQueue, IPC.IPCHandlers.PosixSocket); var instance = IPCInstance.new(.{ diff --git a/src/bun.js/api/bun/dns_resolver.zig b/src/bun.js/api/bun/dns_resolver.zig index 0f65a9d69e..e32ba69da2 100644 --- a/src/bun.js/api/bun/dns_resolver.zig +++ b/src/bun.js/api/bun/dns_resolver.zig @@ -1427,7 +1427,7 @@ pub const InternalDNS = struct { pub fn loop(this: DNSRequestOwner) *bun.uws.Loop { return switch (this) { .prefetch => this.prefetch, - .socket => bun.uws.us_connecting_socket_get_loop(this.socket), + .socket => this.socket.loop(), }; } }; diff --git a/src/bun.js/api/bun/socket.zig b/src/bun.js/api/bun/socket.zig index af2fb6c7c5..d194e67048 100644 --- a/src/bun.js/api/bun/socket.zig +++ b/src/bun.js/api/bun/socket.zig @@ -682,7 +682,7 @@ pub const Listener = struct { } } } - const ctx_opts: uws.us_bun_socket_context_options_t = if (ssl != null) + const ctx_opts: uws.SocketContext.BunSocketContextOptions = if (ssl != null) JSC.API.ServerConfig.SSLConfig.asUSockets(ssl.?) else .{}; @@ -691,8 +691,8 @@ pub const Listener = struct { var create_err: uws.create_bun_socket_error_t = .none; const socket_context = switch (ssl_enabled) { - true => uws.us_create_bun_ssl_socket_context(uws.Loop.get(), @sizeOf(usize), ctx_opts, &create_err), - false => uws.us_create_bun_nossl_socket_context(uws.Loop.get(), @sizeOf(usize)), + true => uws.SocketContext.createSSLContext(uws.Loop.get(), @sizeOf(usize), ctx_opts, &create_err), + false => uws.SocketContext.createNoSSLContext(uws.Loop.get(), @sizeOf(usize)), } orelse { var err = globalObject.createErrorInstance("Failed to listen on {s}:{d}", .{ hostname_or_unix.slice(), port orelse 0 }); defer { @@ -763,15 +763,7 @@ pub const Listener = struct { const host = bun.default_allocator.dupeZ(u8, c.host) catch bun.outOfMemory(); defer bun.default_allocator.free(host); - const socket = uws.us_socket_context_listen( - @intFromBool(ssl_enabled), - socket_context, - if (host.len == 0) null else host.ptr, - c.port, - socket_flags, - 8, - &errno, - ); + const socket = socket_context.listen(ssl_enabled, host.ptr, c.port, socket_flags, 8, &errno); // should return the assigned port if (socket) |s| { connection.host.port = @as(u16, @intCast(s.getLocalPort(ssl_enabled))); @@ -781,7 +773,7 @@ pub const Listener = struct { .unix => |u| { const host = bun.default_allocator.dupeZ(u8, u) catch bun.outOfMemory(); defer bun.default_allocator.free(host); - break :brk uws.us_socket_context_listen_unix(@intFromBool(ssl_enabled), socket_context, host, host.len, socket_flags, 8, &errno); + break :brk socket_context.listenUnix(ssl_enabled, host, host.len, socket_flags, 8, &errno); }, .fd => |fd| { _ = fd; @@ -791,7 +783,7 @@ pub const Listener = struct { } orelse { defer { hostname_or_unix.deinit(); - uws.us_socket_context_free(@intFromBool(ssl_enabled), socket_context); + socket_context.free(ssl_enabled); } const err = globalObject.createErrorInstance("Failed to listen at {s}", .{bun.span(hostname_or_unix.slice())}); @@ -827,7 +819,7 @@ pub const Listener = struct { if (ssl_config.server_name) |server_name| { const slice = bun.asByteSlice(server_name); if (slice.len > 0) - uws.us_bun_socket_context_add_server_name(1, socket.socket_context, server_name, ctx_opts, null); + socket.socket_context.?.addServerName(true, server_name, ctx_opts); } } @@ -925,8 +917,8 @@ pub const Listener = struct { if (try JSC.API.ServerConfig.SSLConfig.fromJS(JSC.VirtualMachine.get(), global, tls)) |ssl_config| { // to keep nodejs compatibility, we allow to replace the server name - uws.us_socket_context_remove_server_name(1, this.socket_context, server_name); - uws.us_bun_socket_context_add_server_name(1, this.socket_context, server_name, ssl_config.asUSockets(), null); + this.socket_context.?.removeServerName(true, server_name); + this.socket_context.?.addServerName(true, server_name, ssl_config.asUSockets()); } return JSValue.jsUndefined(); @@ -1215,15 +1207,15 @@ pub const Listener = struct { } } - const ctx_opts: uws.us_bun_socket_context_options_t = if (ssl != null) + const ctx_opts: uws.SocketContext.BunSocketContextOptions = if (ssl != null) JSC.API.ServerConfig.SSLConfig.asUSockets(ssl.?) else .{}; var create_err: uws.create_bun_socket_error_t = .none; const socket_context = switch (ssl_enabled) { - true => uws.us_create_bun_ssl_socket_context(uws.Loop.get(), @sizeOf(usize), ctx_opts, &create_err), - false => uws.us_create_bun_nossl_socket_context(uws.Loop.get(), @sizeOf(usize)), + true => uws.SocketContext.createSSLContext(uws.Loop.get(), @sizeOf(usize), ctx_opts, &create_err), + false => uws.SocketContext.createNoSSLContext(uws.Loop.get(), @sizeOf(usize)), } orelse { const err = JSC.SystemError{ .message = bun.String.static("Failed to connect"), @@ -2100,7 +2092,7 @@ fn NewSocket(comptime ssl: bool) type { // this error can change if called in different stages of hanshake // is very usefull to have this feature depending on the user workflow - const ssl_error = this.socket.verifyError(); + const ssl_error = this.socket.getVerifyError(); if (ssl_error.error_no == 0) { return JSValue.jsNull(); } @@ -3876,10 +3868,10 @@ pub const WindowsNamedPipeListeningContext = if (Environment.isWindows) struct { if (ssl_config) |ssl_options| { bun.BoringSSL.load(); - const ctx_opts: uws.us_bun_socket_context_options_t = JSC.API.ServerConfig.SSLConfig.asUSockets(ssl_options); + const ctx_opts: uws.SocketContext.BunSocketContextOptions = JSC.API.ServerConfig.SSLConfig.asUSockets(ssl_options); var err: uws.create_bun_socket_error_t = .none; // Create SSL context using uSockets to match behavior of node.js - const ctx = uws.create_ssl_context_from_bun_options(ctx_opts, &err) orelse return error.InvalidOptions; // invalid options + const ctx = ctx_opts.createSSLContext(&err) orelse return error.InvalidOptions; // invalid options this.ctx = ctx; } diff --git a/src/bun.js/api/bun/ssl_wrapper.zig b/src/bun.js/api/bun/ssl_wrapper.zig index 0e23184657..580e90760f 100644 --- a/src/bun.js/api/bun/ssl_wrapper.zig +++ b/src/bun.js/api/bun/ssl_wrapper.zig @@ -98,10 +98,10 @@ pub fn SSLWrapper(comptime T: type) type { pub fn init(ssl_options: JSC.API.ServerConfig.SSLConfig, is_client: bool, handlers: Handlers) !This { bun.BoringSSL.load(); - const ctx_opts: uws.us_bun_socket_context_options_t = JSC.API.ServerConfig.SSLConfig.asUSockets(ssl_options); + const ctx_opts: uws.SocketContext.BunSocketContextOptions = JSC.API.ServerConfig.SSLConfig.asUSockets(ssl_options); var err: uws.create_bun_socket_error_t = .none; // Create SSL context using uSockets to match behavior of node.js - const ctx = uws.create_ssl_context_from_bun_options(ctx_opts, &err) orelse return error.InvalidOptions; // invalid options + const ctx = ctx_opts.createSSLContext(&err) orelse return error.InvalidOptions; // invalid options errdefer BoringSSL.SSL_CTX_free(ctx); return try This.initWithCTX(ctx, is_client, handlers); } @@ -311,7 +311,7 @@ pub fn SSLWrapper(comptime T: type) type { return .{}; } const ssl = this.ssl orelse return .{}; - return uws.us_ssl_socket_verify_error_from_ssl(ssl); + return ssl.getVerifyError(); } /// Update the handshake state diff --git a/src/bun.js/api/bun/subprocess.zig b/src/bun.js/api/bun/subprocess.zig index b548de0c99..defdc134e6 100644 --- a/src/bun.js/api/bun/subprocess.zig +++ b/src/bun.js/api/bun/subprocess.zig @@ -2391,7 +2391,7 @@ pub fn spawnMaybeSync( var posix_ipc_info: if (Environment.isPosix) IPC.Socket else void = undefined; if (Environment.isPosix and !is_sync) { if (maybe_ipc_mode) |mode| { - if (uws.us_socket_from_fd( + if (uws.us_socket_t.fromFd( jsc_vm.rareData().spawnIPCContext(jsc_vm), @sizeOf(*IPC.SendQueue), posix_ipc_fd.cast(), diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index f47f911967..e8864f1b4e 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -671,8 +671,8 @@ pub const ServerConfig = struct { const log = Output.scoped(.SSLConfig, false); - pub fn asUSockets(this: SSLConfig) uws.us_bun_socket_context_options_t { - var ctx_opts: uws.us_bun_socket_context_options_t = .{}; + pub fn asUSockets(this: SSLConfig) uws.SocketContext.BunSocketContextOptions { + var ctx_opts: uws.SocketContext.BunSocketContextOptions = .{}; if (this.key_file_name != null) ctx_opts.key_file_name = this.key_file_name; @@ -2292,7 +2292,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp flags: NewFlags(debug_mode) = .{}, - upgrade_context: ?*uws.uws_socket_context_t = null, + upgrade_context: ?*uws.SocketContext = null, /// We can only safely free once the request body promise is finalized /// and the response is rejected @@ -4668,10 +4668,12 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp comptime { const export_prefix = "Bun__HTTPRequestContext" ++ (if (debug_mode) "Debug" else "") ++ (if (ThisServer.ssl_enabled) "TLS" else ""); - @export(&JSC.toJSHostFn(onResolve), .{ .name = export_prefix ++ "__onResolve" }); - @export(&JSC.toJSHostFn(onReject), .{ .name = export_prefix ++ "__onReject" }); - @export(&JSC.toJSHostFn(onResolveStream), .{ .name = export_prefix ++ "__onResolveStream" }); - @export(&JSC.toJSHostFn(onRejectStream), .{ .name = export_prefix ++ "__onRejectStream" }); + if (bun.Environment.export_cpp_apis) { + @export(&JSC.toJSHostFn(onResolve), .{ .name = export_prefix ++ "__onResolve" }); + @export(&JSC.toJSHostFn(onReject), .{ .name = export_prefix ++ "__onReject" }); + @export(&JSC.toJSHostFn(onResolveStream), .{ .name = export_prefix ++ "__onResolveStream" }); + @export(&JSC.toJSHostFn(onRejectStream), .{ .name = export_prefix ++ "__onRejectStream" }); + } } }; } @@ -5683,7 +5685,7 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d // See https://github.com/oven-sh/bun/issues/1339 // obviously invalid pointer marks it as used - upgrader.upgrade_context = @as(*uws.uws_socket_context_s, @ptrFromInt(std.math.maxInt(usize))); + upgrader.upgrade_context = @as(*uws.SocketContext, @ptrFromInt(std.math.maxInt(usize))); const signal = upgrader.signal; upgrader.signal = null; @@ -6568,7 +6570,7 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d this.pending_requests += 1; } - pub fn onNodeHTTPRequestWithUpgradeCtx(this: *ThisServer, req: *uws.Request, resp: *App.Response, upgrade_ctx: ?*uws.uws_socket_context_t) void { + pub fn onNodeHTTPRequestWithUpgradeCtx(this: *ThisServer, req: *uws.Request, resp: *App.Response, upgrade_ctx: ?*uws.SocketContext) void { this.onPendingRequest(); if (comptime Environment.isDebug) { this.vm.eventLoop().debug.enter(); @@ -7001,7 +7003,7 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d }; } - fn upgradeWebSocketUserRoute(this: *UserRoute, resp: *App.Response, req: *uws.Request, upgrade_ctx: *uws.uws_socket_context_t, method: ?bun.http.Method) void { + fn upgradeWebSocketUserRoute(this: *UserRoute, resp: *App.Response, req: *uws.Request, upgrade_ctx: *uws.SocketContext, method: ?bun.http.Method) void { const server = this.server; const index = this.id; @@ -7022,7 +7024,7 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d this: *ThisServer, resp: *App.Response, req: *uws.Request, - upgrade_ctx: *uws.uws_socket_context_t, + upgrade_ctx: *uws.SocketContext, id: usize, ) void { JSC.markBinding(@src()); @@ -8059,7 +8061,7 @@ extern fn NodeHTTPServer__onRequest_http( methodString: JSC.JSValue, request: *uws.Request, response: *uws.NewApp(false).Response, - upgrade_ctx: ?*uws.uws_socket_context_t, + upgrade_ctx: ?*uws.SocketContext, node_response_ptr: *?*NodeHTTPResponse, ) JSC.JSValue; @@ -8071,7 +8073,7 @@ extern fn NodeHTTPServer__onRequest_https( methodString: JSC.JSValue, request: *uws.Request, response: *uws.NewApp(true).Response, - upgrade_ctx: ?*uws.uws_socket_context_t, + upgrade_ctx: ?*uws.SocketContext, node_response_ptr: *?*NodeHTTPResponse, ) JSC.JSValue; diff --git a/src/bun.js/api/server/NodeHTTPResponse.zig b/src/bun.js/api/server/NodeHTTPResponse.zig index 007e443d0b..426c8b90ec 100644 --- a/src/bun.js/api/server/NodeHTTPResponse.zig +++ b/src/bun.js/api/server/NodeHTTPResponse.zig @@ -45,7 +45,7 @@ pub const Flags = packed struct(u8) { }; pub const UpgradeCTX = struct { - context: ?*uws.uws_socket_context_t = null, + context: ?*uws.SocketContext = null, // request will be detached when go async request: ?*uws.Request = null, diff --git a/src/bun.js/rare_data.zig b/src/bun.js/rare_data.zig index e4c37db712..6db3cb8f52 100644 --- a/src/bun.js/rare_data.zig +++ b/src/bun.js/rare_data.zig @@ -436,7 +436,7 @@ pub fn spawnIPCContext(rare: *RareData, vm: *JSC.VirtualMachine) *uws.SocketCont return ctx; } - const ctx = uws.us_create_bun_nossl_socket_context(vm.event_loop_handle.?, @sizeOf(usize)).?; + const ctx = uws.SocketContext.createNoSSLContext(vm.event_loop_handle.?, @sizeOf(usize)).?; IPC.Socket.configure(ctx, true, *IPC.SendQueue, IPC.IPCHandlers.PosixSocket); rare.spawn_ipc_usockets_context = ctx; return ctx; diff --git a/src/deps/boringssl.translated.zig b/src/deps/boringssl.translated.zig index 4691be5e81..3c1cdca6aa 100644 --- a/src/deps/boringssl.translated.zig +++ b/src/deps/boringssl.translated.zig @@ -19011,6 +19011,10 @@ pub const SSL = opaque { WantRenegotiate, HandshakeHintsReady, }; + extern fn us_ssl_socket_verify_error_from_ssl(ssl: *SSL) bun.uws.us_bun_verify_error_t; + pub fn getVerifyError(this: *SSL) bun.uws.us_bun_verify_error_t { + return us_ssl_socket_verify_error_from_ssl(this); + } pub fn shutdown(this: *SSL) void { _ = SSL_shutdown(this); diff --git a/src/deps/uws.zig b/src/deps/uws.zig index c8357ae8cc..31475f8a48 100644 --- a/src/deps/uws.zig +++ b/src/deps/uws.zig @@ -1,4117 +1,36 @@ -const bun = @import("bun"); -const std = @import("std"); -const Environment = bun.Environment; -pub const LIBUS_LISTEN_DEFAULT: i32 = 0; -pub const LIBUS_LISTEN_EXCLUSIVE_PORT: i32 = 1; -pub const LIBUS_SOCKET_ALLOW_HALF_OPEN: i32 = 2; -pub const LIBUS_LISTEN_REUSE_PORT: i32 = 4; -pub const LIBUS_SOCKET_IPV6_ONLY: i32 = 8; -pub const LIBUS_LISTEN_REUSE_ADDR: i32 = 16; -pub const LIBUS_LISTEN_DISALLOW_REUSE_PORT_FAILURE: i32 = 32; - -pub const Socket = @import("uws/socket.zig").Socket; -pub const ConnectingSocket = opaque {}; -const debug = bun.Output.scoped(.uws, false); const uws = @This(); -const SSLWrapper = @import("../bun.js/api/bun/ssl_wrapper.zig").SSLWrapper; -const JSC = bun.JSC; -const EventLoopTimer = @import("../bun.js/api/Timer.zig").EventLoopTimer; -const WriteResult = union(enum) { - want_more: usize, - backpressure: usize, -}; -const BoringSSL = bun.BoringSSL.c; +pub const us_socket_t = @import("uws/us_socket_t.zig").us_socket_t; +pub const SocketTLS = @import("./uws/socket.zig").SocketTLS; +pub const SocketTCP = @import("./uws/socket.zig").SocketTCP; +pub const InternalSocket = @import("./uws/socket.zig").InternalSocket; +pub const Socket = us_socket_t; +pub const Timer = @import("./uws/Timer.zig").Timer; +pub const SocketContext = @import("./uws/SocketContext.zig").SocketContext; +pub const ConnectingSocket = @import("./uws/ConnectingSocket.zig").ConnectingSocket; +pub const InternalLoopData = @import("./uws/InternalLoopData.zig").InternalLoopData; +pub const WindowsNamedPipe = @import("./uws/WindowsNamedPipe.zig"); +pub const PosixLoop = @import("./uws/Loop.zig").PosixLoop; +pub const WindowsLoop = @import("./uws/Loop.zig").WindowsLoop; +pub const Request = @import("./uws/Request.zig").Request; +pub const AnyResponse = @import("./uws/Response.zig").AnyResponse; +pub const NewApp = @import("./uws/App.zig").NewApp; +pub const uws_res = @import("./uws/Response.zig").uws_res; +pub const RawWebSocket = @import("./uws/WebSocket.zig").RawWebSocket; +pub const AnyWebSocket = @import("./uws/WebSocket.zig").AnyWebSocket; +pub const WebSocketBehavior = @import("./uws/WebSocket.zig").WebSocketBehavior; +pub const AnySocket = @import("./uws/socket.zig").AnySocket; +pub const NewSocketHandler = @import("./uws/socket.zig").NewSocketHandler; +pub const UpgradedDuplex = @import("./uws/UpgradedDuplex.zig"); +pub const ListenSocket = @import("./uws/ListenSocket.zig").ListenSocket; +pub const State = @import("./uws/Response.zig").State; +pub const Loop = @import("./uws/Loop.zig").Loop; +pub const udp = @import("./uws/udp.zig"); +pub const BodyReaderMixin = @import("./uws/BodyReaderMixin.zig").BodyReaderMixin; -fn NativeSocketHandleType(comptime ssl: bool) type { - if (ssl) { - return BoringSSL.SSL; - } else { - return anyopaque; - } -} -pub const InternalLoopData = extern struct { - pub const us_internal_async = opaque {}; - - sweep_timer: ?*Timer, - wakeup_async: ?*us_internal_async, - last_write_failed: i32, - head: ?*SocketContext, - iterator: ?*SocketContext, - closed_context_head: ?*SocketContext, - recv_buf: [*]u8, - send_buf: [*]u8, - ssl_data: ?*anyopaque, - pre_cb: ?*fn (?*Loop) callconv(.C) void, - post_cb: ?*fn (?*Loop) callconv(.C) void, - closed_udp_head: ?*udp.Socket, - closed_head: ?*Socket, - low_prio_head: ?*Socket, - low_prio_budget: i32, - dns_ready_head: *ConnectingSocket, - closed_connecting_head: *ConnectingSocket, - mutex: bun.Mutex.ReleaseImpl.Type, - parent_ptr: ?*anyopaque, - parent_tag: c_char, - iteration_nr: usize, - jsc_vm: ?*JSC.VM, - - pub fn recvSlice(this: *InternalLoopData) []u8 { - return this.recv_buf[0..LIBUS_RECV_BUFFER_LENGTH]; - } - - pub fn setParentEventLoop(this: *InternalLoopData, parent: JSC.EventLoopHandle) void { - switch (parent) { - .js => |ptr| { - this.parent_tag = 1; - this.parent_ptr = ptr; - }, - .mini => |ptr| { - this.parent_tag = 2; - this.parent_ptr = ptr; - }, - } - } - - pub fn getParent(this: *InternalLoopData) JSC.EventLoopHandle { - const parent = this.parent_ptr orelse @panic("Parent loop not set - pointer is null"); - return switch (this.parent_tag) { - 0 => @panic("Parent loop not set - tag is zero"), - 1 => .{ .js = bun.cast(*JSC.EventLoop, parent) }, - 2 => .{ .mini = bun.cast(*JSC.MiniEventLoop, parent) }, - else => @panic("Parent loop data corrupted - tag is invalid"), - }; - } -}; - -pub const UpgradedDuplex = struct { - pub const CertError = struct { - error_no: i32 = 0, - code: [:0]const u8 = "", - reason: [:0]const u8 = "", - - pub fn deinit(this: *CertError) void { - if (this.code.len > 0) { - bun.default_allocator.free(this.code); - } - if (this.reason.len > 0) { - bun.default_allocator.free(this.reason); - } - } - }; - - const WrapperType = SSLWrapper(*UpgradedDuplex); - - wrapper: ?WrapperType, - origin: JSC.Strong.Optional = .empty, // any duplex - global: ?*JSC.JSGlobalObject = null, - ssl_error: CertError = .{}, - vm: *JSC.VirtualMachine, - handlers: Handlers, - onDataCallback: JSC.Strong.Optional = .empty, - onEndCallback: JSC.Strong.Optional = .empty, - onWritableCallback: JSC.Strong.Optional = .empty, - onCloseCallback: JSC.Strong.Optional = .empty, - event_loop_timer: EventLoopTimer = .{ - .next = .{}, - .tag = .UpgradedDuplex, - }, - current_timeout: u32 = 0, - - pub const Handlers = struct { - ctx: *anyopaque, - onOpen: *const fn (*anyopaque) void, - onHandshake: *const fn (*anyopaque, bool, uws.us_bun_verify_error_t) void, - onData: *const fn (*anyopaque, []const u8) void, - onClose: *const fn (*anyopaque) void, - onEnd: *const fn (*anyopaque) void, - onWritable: *const fn (*anyopaque) void, - onError: *const fn (*anyopaque, JSC.JSValue) void, - onTimeout: *const fn (*anyopaque) void, - }; - - const log = bun.Output.scoped(.UpgradedDuplex, false); - fn onOpen(this: *UpgradedDuplex) void { - log("onOpen", .{}); - this.handlers.onOpen(this.handlers.ctx); - } - - fn onData(this: *UpgradedDuplex, decoded_data: []const u8) void { - log("onData ({})", .{decoded_data.len}); - this.handlers.onData(this.handlers.ctx, decoded_data); - } - - fn onHandshake(this: *UpgradedDuplex, handshake_success: bool, ssl_error: uws.us_bun_verify_error_t) void { - log("onHandshake", .{}); - - this.ssl_error = .{ - .error_no = ssl_error.error_no, - .code = if (ssl_error.code == null or ssl_error.error_no == 0) "" else bun.default_allocator.dupeZ(u8, ssl_error.code[0..bun.len(ssl_error.code) :0]) catch bun.outOfMemory(), - .reason = if (ssl_error.reason == null or ssl_error.error_no == 0) "" else bun.default_allocator.dupeZ(u8, ssl_error.reason[0..bun.len(ssl_error.reason) :0]) catch bun.outOfMemory(), - }; - this.handlers.onHandshake(this.handlers.ctx, handshake_success, ssl_error); - } - - fn onClose(this: *UpgradedDuplex) void { - log("onClose", .{}); - defer this.deinit(); - - this.handlers.onClose(this.handlers.ctx); - // closes the underlying duplex - this.callWriteOrEnd(null, false); - } - - fn callWriteOrEnd(this: *UpgradedDuplex, data: ?[]const u8, msg_more: bool) void { - if (this.vm.isShuttingDown()) { - return; - } - if (this.origin.get()) |duplex| { - const globalThis = this.global.?; - const writeOrEnd = if (msg_more) duplex.getFunction(globalThis, "write") catch return orelse return else duplex.getFunction(globalThis, "end") catch return orelse return; - if (data) |data_| { - const buffer = JSC.ArrayBuffer.BinaryType.toJS(.Buffer, data_, globalThis); - buffer.ensureStillAlive(); - - _ = writeOrEnd.call(globalThis, duplex, &.{buffer}) catch |err| { - this.handlers.onError(this.handlers.ctx, globalThis.takeException(err)); - }; - } else { - _ = writeOrEnd.call(globalThis, duplex, &.{.null}) catch |err| { - this.handlers.onError(this.handlers.ctx, globalThis.takeException(err)); - }; - } - } - } - - fn internalWrite(this: *UpgradedDuplex, encoded_data: []const u8) void { - this.resetTimeout(); - - // Possible scenarios: - // Scenario 1: will not write if vm is shutting down (we cannot do anything about it) - // Scenario 2: will not write if a exception is thrown (will be handled by onError) - // Scenario 3: will be queued in memory and will be flushed later - // Scenario 4: no write/end function exists (will be handled by onError) - this.callWriteOrEnd(encoded_data, true); - } - - pub fn flush(this: *UpgradedDuplex) void { - if (this.wrapper) |*wrapper| { - _ = wrapper.flush(); - } - } - - fn onInternalReceiveData(this: *UpgradedDuplex, data: []const u8) void { - if (this.wrapper) |*wrapper| { - this.resetTimeout(); - wrapper.receiveData(data); - } - } - - fn onReceivedData( - globalObject: *JSC.JSGlobalObject, - callframe: *JSC.CallFrame, - ) bun.JSError!JSC.JSValue { - log("onReceivedData", .{}); - - const function = callframe.callee(); - const args = callframe.arguments_old(1); - - if (JSC.host_fn.getFunctionData(function)) |self| { - const this = @as(*UpgradedDuplex, @ptrCast(@alignCast(self))); - if (args.len >= 1) { - const data_arg = args.ptr[0]; - if (this.origin.has()) { - if (data_arg.isEmptyOrUndefinedOrNull()) { - return JSC.JSValue.jsUndefined(); - } - if (data_arg.asArrayBuffer(globalObject)) |array_buffer| { - // yay we can read the data - const payload = array_buffer.slice(); - this.onInternalReceiveData(payload); - } else { - // node.js errors in this case with the same error, lets keep it consistent - const error_value = globalObject.ERR(.STREAM_WRAP, "Stream has StringDecoder set or is in objectMode", .{}).toJS(); - error_value.ensureStillAlive(); - this.handlers.onError(this.handlers.ctx, error_value); - } - } - } - } - return JSC.JSValue.jsUndefined(); - } - - fn onEnd( - globalObject: *JSC.JSGlobalObject, - callframe: *JSC.CallFrame, - ) void { - log("onEnd", .{}); - _ = globalObject; - const function = callframe.callee(); - - if (JSC.host_fn.getFunctionData(function)) |self| { - const this = @as(*UpgradedDuplex, @ptrCast(@alignCast(self))); - - if (this.wrapper != null) { - this.handlers.onEnd(this.handlers.ctx); - } - } - } - - fn onWritable( - globalObject: *JSC.JSGlobalObject, - callframe: *JSC.CallFrame, - ) bun.JSError!JSC.JSValue { - log("onWritable", .{}); - - _ = globalObject; - const function = callframe.callee(); - - if (JSC.host_fn.getFunctionData(function)) |self| { - const this = @as(*UpgradedDuplex, @ptrCast(@alignCast(self))); - // flush pending data - if (this.wrapper) |*wrapper| { - _ = wrapper.flush(); - } - // call onWritable (will flush on demand) - this.handlers.onWritable(this.handlers.ctx); - } - - return JSC.JSValue.jsUndefined(); - } - - fn onCloseJS( - globalObject: *JSC.JSGlobalObject, - callframe: *JSC.CallFrame, - ) bun.JSError!JSC.JSValue { - log("onCloseJS", .{}); - - _ = globalObject; - const function = callframe.callee(); - - if (JSC.host_fn.getFunctionData(function)) |self| { - const this = @as(*UpgradedDuplex, @ptrCast(@alignCast(self))); - // flush pending data - if (this.wrapper) |*wrapper| { - _ = wrapper.shutdown(true); - } - } - - return JSC.JSValue.jsUndefined(); - } - - pub fn onTimeout(this: *UpgradedDuplex) EventLoopTimer.Arm { - log("onTimeout", .{}); - - const has_been_cleared = this.event_loop_timer.state == .CANCELLED or this.vm.scriptExecutionStatus() != .running; - - this.event_loop_timer.state = .FIRED; - this.event_loop_timer.heap = .{}; - - if (has_been_cleared) { - return .disarm; - } - - this.handlers.onTimeout(this.handlers.ctx); - - return .disarm; - } - - pub fn from( - globalThis: *JSC.JSGlobalObject, - origin: JSC.JSValue, - handlers: UpgradedDuplex.Handlers, - ) UpgradedDuplex { - return UpgradedDuplex{ - .vm = globalThis.bunVM(), - .origin = .create(origin, globalThis), - .global = globalThis, - .wrapper = null, - .handlers = handlers, - }; - } - - pub fn getJSHandlers(this: *UpgradedDuplex, globalThis: *JSC.JSGlobalObject) bun.JSError!JSC.JSValue { - const array = try JSC.JSValue.createEmptyArray(globalThis, 4); - array.ensureStillAlive(); - - { - const callback = this.onDataCallback.get() orelse brk: { - const dataCallback = JSC.host_fn.NewFunctionWithData( - globalThis, - null, - 0, - onReceivedData, - false, - this, - ); - dataCallback.ensureStillAlive(); - - JSC.host_fn.setFunctionData(dataCallback, this); - - this.onDataCallback = .create(dataCallback, globalThis); - break :brk dataCallback; - }; - array.putIndex(globalThis, 0, callback); - } - - { - const callback = this.onEndCallback.get() orelse brk: { - const endCallback = JSC.host_fn.NewFunctionWithData( - globalThis, - null, - 0, - onReceivedData, - false, - this, - ); - endCallback.ensureStillAlive(); - - JSC.host_fn.setFunctionData(endCallback, this); - - this.onEndCallback = .create(endCallback, globalThis); - break :brk endCallback; - }; - array.putIndex(globalThis, 1, callback); - } - - { - const callback = this.onWritableCallback.get() orelse brk: { - const writableCallback = JSC.host_fn.NewFunctionWithData( - globalThis, - null, - 0, - onWritable, - false, - this, - ); - writableCallback.ensureStillAlive(); - - JSC.host_fn.setFunctionData(writableCallback, this); - this.onWritableCallback = .create(writableCallback, globalThis); - break :brk writableCallback; - }; - array.putIndex(globalThis, 2, callback); - } - - { - const callback = this.onCloseCallback.get() orelse brk: { - const closeCallback = JSC.host_fn.NewFunctionWithData( - globalThis, - null, - 0, - onCloseJS, - false, - this, - ); - closeCallback.ensureStillAlive(); - - JSC.host_fn.setFunctionData(closeCallback, this); - this.onCloseCallback = .create(closeCallback, globalThis); - break :brk closeCallback; - }; - array.putIndex(globalThis, 3, callback); - } - - return array; - } - - pub fn startTLS(this: *UpgradedDuplex, ssl_options: JSC.API.ServerConfig.SSLConfig, is_client: bool) !void { - this.wrapper = try WrapperType.init(ssl_options, is_client, .{ - .ctx = this, - .onOpen = UpgradedDuplex.onOpen, - .onHandshake = UpgradedDuplex.onHandshake, - .onData = UpgradedDuplex.onData, - .onClose = UpgradedDuplex.onClose, - .write = UpgradedDuplex.internalWrite, - }); - - this.wrapper.?.start(); - } - - pub fn encodeAndWrite(this: *UpgradedDuplex, data: []const u8, is_end: bool) i32 { - log("encodeAndWrite (len: {} - is_end: {})", .{ data.len, is_end }); - if (this.wrapper) |*wrapper| { - return @as(i32, @intCast(wrapper.writeData(data) catch 0)); - } - return 0; - } - - pub fn rawWrite(this: *UpgradedDuplex, encoded_data: []const u8, _: bool) i32 { - this.internalWrite(encoded_data); - return @intCast(encoded_data.len); - } - - pub fn close(this: *UpgradedDuplex) void { - if (this.wrapper) |*wrapper| { - _ = wrapper.shutdown(true); - } - } - - pub fn shutdown(this: *UpgradedDuplex) void { - if (this.wrapper) |*wrapper| { - _ = wrapper.shutdown(false); - } - } - - pub fn shutdownRead(this: *UpgradedDuplex) void { - if (this.wrapper) |*wrapper| { - _ = wrapper.shutdownRead(); - } - } - - pub fn isShutdown(this: *UpgradedDuplex) bool { - if (this.wrapper) |wrapper| { - return wrapper.isShutdown(); - } - return true; - } - - pub fn isClosed(this: *UpgradedDuplex) bool { - if (this.wrapper) |wrapper| { - return wrapper.isClosed(); - } - return true; - } - - pub fn isEstablished(this: *UpgradedDuplex) bool { - return !this.isClosed(); - } - - pub fn ssl(this: *UpgradedDuplex) ?*BoringSSL.SSL { - if (this.wrapper) |wrapper| { - return wrapper.ssl; - } - return null; - } - - pub fn sslError(this: *UpgradedDuplex) us_bun_verify_error_t { - return .{ - .error_no = this.ssl_error.error_no, - .code = @ptrCast(this.ssl_error.code.ptr), - .reason = @ptrCast(this.ssl_error.reason.ptr), - }; - } - - pub fn resetTimeout(this: *UpgradedDuplex) void { - this.setTimeoutInMilliseconds(this.current_timeout); - } - pub fn setTimeoutInMilliseconds(this: *UpgradedDuplex, ms: c_uint) void { - if (this.event_loop_timer.state == .ACTIVE) { - this.vm.timer.remove(&this.event_loop_timer); - } - this.current_timeout = ms; - - // if the interval is 0 means that we stop the timer - if (ms == 0) { - return; - } - - // reschedule the timer - this.event_loop_timer.next = bun.timespec.msFromNow(ms); - this.vm.timer.insert(&this.event_loop_timer); - } - pub fn setTimeout(this: *UpgradedDuplex, seconds: c_uint) void { - log("setTimeout({d})", .{seconds}); - this.setTimeoutInMilliseconds(seconds * 1000); - } - - pub fn deinit(this: *UpgradedDuplex) void { - log("deinit", .{}); - // clear the timer - this.setTimeout(0); - - if (this.wrapper) |*wrapper| { - wrapper.deinit(); - this.wrapper = null; - } - - this.origin.deinit(); - if (this.onDataCallback.get()) |callback| { - JSC.host_fn.setFunctionData(callback, null); - this.onDataCallback.deinit(); - } - if (this.onEndCallback.get()) |callback| { - JSC.host_fn.setFunctionData(callback, null); - this.onEndCallback.deinit(); - } - if (this.onWritableCallback.get()) |callback| { - JSC.host_fn.setFunctionData(callback, null); - this.onWritableCallback.deinit(); - } - if (this.onCloseCallback.get()) |callback| { - JSC.host_fn.setFunctionData(callback, null); - this.onCloseCallback.deinit(); - } - var ssl_error = this.ssl_error; - ssl_error.deinit(); - this.ssl_error = .{}; - } -}; - -pub const WindowsNamedPipe = if (Environment.isWindows) struct { - pub const CertError = UpgradedDuplex.CertError; - - const WrapperType = SSLWrapper(*WindowsNamedPipe); - const uv = bun.windows.libuv; - wrapper: ?WrapperType, - pipe: if (Environment.isWindows) ?*uv.Pipe else void, // any duplex - vm: *bun.JSC.VirtualMachine, //TODO: create a timeout version that dont need the JSC VM - - writer: bun.io.StreamingWriter(@This(), .{ - .onClose = onClose, - .onWritable = onWritable, - .onError = onError, - .onWrite = onWrite, - }) = .{}, - - incoming: bun.ByteList = .{}, // Maybe we should use IPCBuffer here as well - ssl_error: CertError = .{}, - handlers: Handlers, - connect_req: uv.uv_connect_t = std.mem.zeroes(uv.uv_connect_t), - - event_loop_timer: EventLoopTimer = .{ - .next = .{}, - .tag = .WindowsNamedPipe, - }, - current_timeout: u32 = 0, - flags: Flags = .{}, - - pub const Flags = packed struct(u8) { - disconnected: bool = true, - is_closed: bool = false, - is_client: bool = false, - is_ssl: bool = false, - _: u4 = 0, - }; - pub const Handlers = struct { - ctx: *anyopaque, - onOpen: *const fn (*anyopaque) void, - onHandshake: *const fn (*anyopaque, bool, uws.us_bun_verify_error_t) void, - onData: *const fn (*anyopaque, []const u8) void, - onClose: *const fn (*anyopaque) void, - onEnd: *const fn (*anyopaque) void, - onWritable: *const fn (*anyopaque) void, - onError: *const fn (*anyopaque, bun.sys.Error) void, - onTimeout: *const fn (*anyopaque) void, - }; - - const log = bun.Output.scoped(.WindowsNamedPipe, false); - - fn onWritable( - this: *WindowsNamedPipe, - ) void { - log("onWritable", .{}); - // flush pending data - this.flush(); - // call onWritable (will flush on demand) - this.handlers.onWritable(this.handlers.ctx); - } - - fn onPipeClose(this: *WindowsNamedPipe) void { - log("onPipeClose", .{}); - this.flags.disconnected = true; - this.pipe = null; - this.onClose(); - } - - fn onReadAlloc(this: *WindowsNamedPipe, suggested_size: usize) []u8 { - var available = this.incoming.available(); - if (available.len < suggested_size) { - this.incoming.ensureUnusedCapacity(bun.default_allocator, suggested_size) catch bun.outOfMemory(); - available = this.incoming.available(); - } - return available.ptr[0..suggested_size]; - } - - fn onRead(this: *WindowsNamedPipe, buffer: []const u8) void { - log("onRead ({})", .{buffer.len}); - this.incoming.len += @as(u32, @truncate(buffer.len)); - bun.assert(this.incoming.len <= this.incoming.cap); - bun.assert(bun.isSliceInBuffer(buffer, this.incoming.allocatedSlice())); - - const data = this.incoming.slice(); - - this.resetTimeout(); - - if (this.wrapper) |*wrapper| { - wrapper.receiveData(data); - } else { - this.handlers.onData(this.handlers.ctx, data); - } - this.incoming.len = 0; - } - - fn onWrite(this: *WindowsNamedPipe, amount: usize, status: bun.io.WriteStatus) void { - log("onWrite {d} {}", .{ amount, status }); - - switch (status) { - .pending => {}, - .drained => { - // unref after sending all data - if (this.writer.source) |source| { - source.pipe.unref(); - } - }, - .end_of_file => { - // we send FIN so we close after this - this.writer.close(); - }, - } - } - - fn onReadError(this: *WindowsNamedPipe, err: bun.sys.E) void { - log("onReadError", .{}); - if (err == .EOF) { - // we received FIN but we dont allow half-closed connections right now - this.handlers.onEnd(this.handlers.ctx); - } else { - this.onError(bun.sys.Error.fromCode(err, .read)); - } - this.writer.close(); - } - - fn onError(this: *WindowsNamedPipe, err: bun.sys.Error) void { - log("onError", .{}); - this.handlers.onError(this.handlers.ctx, err); - this.close(); - } - - fn onOpen(this: *WindowsNamedPipe) void { - log("onOpen", .{}); - this.handlers.onOpen(this.handlers.ctx); - } - - fn onData(this: *WindowsNamedPipe, decoded_data: []const u8) void { - log("onData ({})", .{decoded_data.len}); - this.handlers.onData(this.handlers.ctx, decoded_data); - } - - fn onHandshake(this: *WindowsNamedPipe, handshake_success: bool, ssl_error: uws.us_bun_verify_error_t) void { - log("onHandshake", .{}); - - this.ssl_error = .{ - .error_no = ssl_error.error_no, - .code = if (ssl_error.code == null or ssl_error.error_no == 0) "" else bun.default_allocator.dupeZ(u8, ssl_error.code[0..bun.len(ssl_error.code) :0]) catch bun.outOfMemory(), - .reason = if (ssl_error.reason == null or ssl_error.error_no == 0) "" else bun.default_allocator.dupeZ(u8, ssl_error.reason[0..bun.len(ssl_error.reason) :0]) catch bun.outOfMemory(), - }; - this.handlers.onHandshake(this.handlers.ctx, handshake_success, ssl_error); - } - - fn onClose(this: *WindowsNamedPipe) void { - log("onClose", .{}); - if (!this.flags.is_closed) { - this.flags.is_closed = true; // only call onClose once - this.handlers.onClose(this.handlers.ctx); - this.deinit(); - } - } - - fn callWriteOrEnd(this: *WindowsNamedPipe, data: ?[]const u8, msg_more: bool) void { - if (data) |bytes| { - if (bytes.len > 0) { - // ref because we have pending data - if (this.writer.source) |source| { - source.pipe.ref(); - } - if (this.flags.disconnected) { - // enqueue to be sent after connecting - this.writer.outgoing.write(bytes) catch bun.outOfMemory(); - } else { - // write will enqueue the data if it cannot be sent - _ = this.writer.write(bytes); - } - } - } - - if (!msg_more) { - if (this.wrapper) |*wrapper| { - _ = wrapper.shutdown(false); - } - this.writer.end(); - } - } - - fn internalWrite(this: *WindowsNamedPipe, encoded_data: []const u8) void { - this.resetTimeout(); - - // Possible scenarios: - // Scenario 1: will not write if is not connected yet but will enqueue the data - // Scenario 2: will not write if a exception is thrown (will be handled by onError) - // Scenario 3: will be queued in memory and will be flushed later - // Scenario 4: no write/end function exists (will be handled by onError) - this.callWriteOrEnd(encoded_data, true); - } - - pub fn resumeStream(this: *WindowsNamedPipe) bool { - const stream = this.writer.getStream() orelse { - return false; - }; - const readStartResult = stream.readStart(this, onReadAlloc, onReadError, onRead); - if (readStartResult == .err) { - return false; - } - return true; - } - - pub fn pauseStream(this: *WindowsNamedPipe) bool { - const pipe = this.pipe orelse { - return false; - }; - pipe.readStop(); - return true; - } - - pub fn flush(this: *WindowsNamedPipe) void { - if (this.wrapper) |*wrapper| { - _ = wrapper.flush(); - } - if (!this.flags.disconnected) { - _ = this.writer.flush(); - } - } - - fn onInternalReceiveData(this: *WindowsNamedPipe, data: []const u8) void { - if (this.wrapper) |*wrapper| { - this.resetTimeout(); - wrapper.receiveData(data); - } - } - - pub fn onTimeout(this: *WindowsNamedPipe) EventLoopTimer.Arm { - log("onTimeout", .{}); - - const has_been_cleared = this.event_loop_timer.state == .CANCELLED or this.vm.scriptExecutionStatus() != .running; - - this.event_loop_timer.state = .FIRED; - this.event_loop_timer.heap = .{}; - - if (has_been_cleared) { - return .disarm; - } - - this.handlers.onTimeout(this.handlers.ctx); - - return .disarm; - } - - pub fn from( - pipe: *uv.Pipe, - handlers: WindowsNamedPipe.Handlers, - vm: *JSC.VirtualMachine, - ) WindowsNamedPipe { - if (Environment.isPosix) { - @compileError("WindowsNamedPipe is not supported on POSIX systems"); - } - return WindowsNamedPipe{ - .vm = vm, - .pipe = pipe, - .wrapper = null, - .handlers = handlers, - }; - } - fn onConnect(this: *WindowsNamedPipe, status: uv.ReturnCode) void { - if (this.pipe) |pipe| { - _ = pipe.unref(); - } - - if (status.toError(.connect)) |err| { - this.onError(err); - return; - } - - this.flags.disconnected = false; - if (this.start(true)) { - if (this.isTLS()) { - if (this.wrapper) |*wrapper| { - // trigger onOpen and start the handshake - wrapper.start(); - } - } else { - // trigger onOpen - this.onOpen(); - } - } - this.flush(); - } - - pub fn getAcceptedBy(this: *WindowsNamedPipe, server: *uv.Pipe, ssl_ctx: ?*BoringSSL.SSL_CTX) JSC.Maybe(void) { - bun.assert(this.pipe != null); - this.flags.disconnected = true; - - if (ssl_ctx) |tls| { - this.flags.is_ssl = true; - this.wrapper = WrapperType.initWithCTX(tls, false, .{ - .ctx = this, - .onOpen = WindowsNamedPipe.onOpen, - .onHandshake = WindowsNamedPipe.onHandshake, - .onData = WindowsNamedPipe.onData, - .onClose = WindowsNamedPipe.onClose, - .write = WindowsNamedPipe.internalWrite, - }) catch { - return .{ - .err = .{ - .errno = @intFromEnum(bun.sys.E.PIPE), - .syscall = .connect, - }, - }; - }; - // ref because we are accepting will unref when wrapper deinit - _ = BoringSSL.SSL_CTX_up_ref(tls); - } - const initResult = this.pipe.?.init(this.vm.uvLoop(), false); - if (initResult == .err) { - return initResult; - } - - const openResult = server.accept(this.pipe.?); - if (openResult == .err) { - return openResult; - } - - this.flags.disconnected = false; - if (this.start(false)) { - if (this.isTLS()) { - if (this.wrapper) |*wrapper| { - // trigger onOpen and start the handshake - wrapper.start(); - } - } else { - // trigger onOpen - this.onOpen(); - } - } - return .{ .result = {} }; - } - pub fn open(this: *WindowsNamedPipe, fd: bun.FileDescriptor, ssl_options: ?JSC.API.ServerConfig.SSLConfig) JSC.Maybe(void) { - bun.assert(this.pipe != null); - this.flags.disconnected = true; - - if (ssl_options) |tls| { - this.flags.is_ssl = true; - this.wrapper = WrapperType.init(tls, true, .{ - .ctx = this, - .onOpen = WindowsNamedPipe.onOpen, - .onHandshake = WindowsNamedPipe.onHandshake, - .onData = WindowsNamedPipe.onData, - .onClose = WindowsNamedPipe.onClose, - .write = WindowsNamedPipe.internalWrite, - }) catch { - return .{ - .err = .{ - .errno = @intFromEnum(bun.sys.E.PIPE), - .syscall = .connect, - }, - }; - }; - } - const initResult = this.pipe.?.init(this.vm.uvLoop(), false); - if (initResult == .err) { - return initResult; - } - - const openResult = this.pipe.?.open(fd); - if (openResult == .err) { - return openResult; - } - - onConnect(this, uv.ReturnCode.zero); - return .{ .result = {} }; - } - - pub fn connect(this: *WindowsNamedPipe, path: []const u8, ssl_options: ?JSC.API.ServerConfig.SSLConfig) JSC.Maybe(void) { - bun.assert(this.pipe != null); - this.flags.disconnected = true; - // ref because we are connecting - _ = this.pipe.?.ref(); - - if (ssl_options) |tls| { - this.flags.is_ssl = true; - this.wrapper = WrapperType.init(tls, true, .{ - .ctx = this, - .onOpen = WindowsNamedPipe.onOpen, - .onHandshake = WindowsNamedPipe.onHandshake, - .onData = WindowsNamedPipe.onData, - .onClose = WindowsNamedPipe.onClose, - .write = WindowsNamedPipe.internalWrite, - }) catch { - return .{ - .err = .{ - .errno = @intFromEnum(bun.sys.E.PIPE), - .syscall = .connect, - }, - }; - }; - } - const initResult = this.pipe.?.init(this.vm.uvLoop(), false); - if (initResult == .err) { - return initResult; - } - - this.connect_req.data = this; - return this.pipe.?.connect(&this.connect_req, path, this, onConnect); - } - pub fn startTLS(this: *WindowsNamedPipe, ssl_options: JSC.API.ServerConfig.SSLConfig, is_client: bool) !void { - this.flags.is_ssl = true; - if (this.start(is_client)) { - this.wrapper = try WrapperType.init(ssl_options, is_client, .{ - .ctx = this, - .onOpen = WindowsNamedPipe.onOpen, - .onHandshake = WindowsNamedPipe.onHandshake, - .onData = WindowsNamedPipe.onData, - .onClose = WindowsNamedPipe.onClose, - .write = WindowsNamedPipe.internalWrite, - }); - - this.wrapper.?.start(); - } - } - - pub fn start(this: *WindowsNamedPipe, is_client: bool) bool { - this.flags.is_client = is_client; - if (this.pipe == null) { - return false; - } - _ = this.pipe.?.unref(); - this.writer.setParent(this); - const startPipeResult = this.writer.startWithPipe(this.pipe.?); - if (startPipeResult == .err) { - this.onError(startPipeResult.err); - return false; - } - const stream = this.writer.getStream() orelse { - this.onError(bun.sys.Error.fromCode(bun.sys.E.PIPE, .read)); - return false; - }; - - const readStartResult = stream.readStart(this, onReadAlloc, onReadError, onRead); - if (readStartResult == .err) { - this.onError(readStartResult.err); - return false; - } - return true; - } - - pub fn isTLS(this: *WindowsNamedPipe) bool { - return this.flags.is_ssl; - } - - pub fn encodeAndWrite(this: *WindowsNamedPipe, data: []const u8, is_end: bool) i32 { - log("encodeAndWrite (len: {} - is_end: {})", .{ data.len, is_end }); - if (this.wrapper) |*wrapper| { - return @as(i32, @intCast(wrapper.writeData(data) catch 0)); - } else { - this.internalWrite(data); - } - return @intCast(data.len); - } - - pub fn rawWrite(this: *WindowsNamedPipe, encoded_data: []const u8, _: bool) i32 { - this.internalWrite(encoded_data); - return @intCast(encoded_data.len); - } - - pub fn close(this: *WindowsNamedPipe) void { - if (this.wrapper) |*wrapper| { - _ = wrapper.shutdown(false); - } - this.writer.end(); - } - - pub fn shutdown(this: *WindowsNamedPipe) void { - if (this.wrapper) |*wrapper| { - _ = wrapper.shutdown(false); - } - } - - pub fn shutdownRead(this: *WindowsNamedPipe) void { - if (this.wrapper) |*wrapper| { - _ = wrapper.shutdownRead(); - } else { - if (this.writer.getStream()) |stream| { - _ = stream.readStop(); - } - } - } - - pub fn isShutdown(this: *WindowsNamedPipe) bool { - if (this.wrapper) |wrapper| { - return wrapper.isShutdown(); - } - - return this.flags.disconnected or this.writer.is_done; - } - - pub fn isClosed(this: *WindowsNamedPipe) bool { - if (this.wrapper) |wrapper| { - return wrapper.isClosed(); - } - return this.flags.disconnected; - } - - pub fn isEstablished(this: *WindowsNamedPipe) bool { - return !this.isClosed(); - } - - pub fn ssl(this: *WindowsNamedPipe) ?*BoringSSL.SSL { - if (this.wrapper) |wrapper| { - return wrapper.ssl; - } - return null; - } - - pub fn sslError(this: *WindowsNamedPipe) us_bun_verify_error_t { - return .{ - .error_no = this.ssl_error.error_no, - .code = @ptrCast(this.ssl_error.code.ptr), - .reason = @ptrCast(this.ssl_error.reason.ptr), - }; - } - - pub fn resetTimeout(this: *WindowsNamedPipe) void { - this.setTimeoutInMilliseconds(this.current_timeout); - } - pub fn setTimeoutInMilliseconds(this: *WindowsNamedPipe, ms: c_uint) void { - if (this.event_loop_timer.state == .ACTIVE) { - this.vm.timer.remove(&this.event_loop_timer); - } - this.current_timeout = ms; - - // if the interval is 0 means that we stop the timer - if (ms == 0) { - return; - } - - // reschedule the timer - this.event_loop_timer.next = bun.timespec.msFromNow(ms); - this.vm.timer.insert(&this.event_loop_timer); - } - pub fn setTimeout(this: *WindowsNamedPipe, seconds: c_uint) void { - log("setTimeout({d})", .{seconds}); - this.setTimeoutInMilliseconds(seconds * 1000); - } - /// Free internal resources, it can be called multiple times - pub fn deinit(this: *WindowsNamedPipe) void { - log("deinit", .{}); - // clear the timer - this.setTimeout(0); - if (this.writer.getStream()) |stream| { - _ = stream.readStop(); - } - this.writer.deinit(); - if (this.wrapper) |*wrapper| { - wrapper.deinit(); - this.wrapper = null; - } - var ssl_error = this.ssl_error; - ssl_error.deinit(); - this.ssl_error = .{}; - } -} else void; - -pub const InternalSocket = union(enum) { - connected: *Socket, - connecting: *ConnectingSocket, - detached: void, - upgradedDuplex: *UpgradedDuplex, - pipe: *WindowsNamedPipe, - - pub fn pauseResume(this: InternalSocket, comptime ssl: bool, comptime pause: bool) bool { - switch (this) { - .detached => return true, - .connected => |socket| { - if (pause) socket.pause(ssl) else socket.@"resume"(ssl); - return true; - }, - .connecting => |_| { - // always return false for connecting sockets - return false; - }, - .upgradedDuplex => |_| { - // TODO: pause and resume upgraded duplex - return false; - }, - .pipe => |pipe| { - if (Environment.isWindows) { - if (pause) { - return pipe.pauseStream(); - } - return pipe.resumeStream(); - } - return false; - }, - } - } - pub fn isDetached(this: InternalSocket) bool { - return this == .detached; - } - pub fn isNamedPipe(this: InternalSocket) bool { - return this == .pipe; - } - pub fn detach(this: *InternalSocket) void { - this.* = .detached; - } - pub fn setNoDelay(this: InternalSocket, enabled: bool) bool { - switch (this) { - .pipe, .upgradedDuplex, .connecting, .detached => return false, - .connected => |socket| { - // only supported by connected sockets - socket.setNodelay(enabled); - return true; - }, - } - } - pub fn setKeepAlive(this: InternalSocket, enabled: bool, delay: u32) bool { - switch (this) { - .pipe, .upgradedDuplex, .connecting, .detached => return false, - .connected => |socket| { - // only supported by connected sockets and can fail - return socket.setKeepalive(enabled, delay) == 0; - }, - } - } - pub fn close(this: InternalSocket, comptime is_ssl: bool, code: Socket.CloseCode) void { - switch (this) { - .detached => {}, - .connected => |socket| { - socket.close(is_ssl, code); - }, - .connecting => |socket| { - debug("us_connecting_socket_close({d})", .{@intFromPtr(socket)}); - _ = us_connecting_socket_close( - comptime @intFromBool(is_ssl), - socket, - ); - }, - .upgradedDuplex => |socket| { - socket.close(); - }, - .pipe => |pipe| { - if (Environment.isWindows) pipe.close(); - }, - } - } - - pub fn isClosed(this: InternalSocket, comptime is_ssl: bool) bool { - return switch (this) { - .connected => |socket| socket.isClosed(is_ssl), - .connecting => |socket| us_connecting_socket_is_closed(@intFromBool(is_ssl), socket) > 0, - .detached => true, - .upgradedDuplex => |socket| socket.isClosed(), - .pipe => |pipe| if (Environment.isWindows) pipe.isClosed() else true, - }; - } - - pub fn get(this: @This()) ?*Socket { - return switch (this) { - .connected => this.connected, - .connecting => null, - .detached => null, - .upgradedDuplex => null, - .pipe => null, - }; - } - - pub fn eq(this: @This(), other: @This()) bool { - return switch (this) { - .connected => switch (other) { - .connected => this.connected == other.connected, - .upgradedDuplex, .connecting, .detached, .pipe => false, - }, - .connecting => switch (other) { - .upgradedDuplex, .connected, .detached, .pipe => false, - .connecting => this.connecting == other.connecting, - }, - .detached => switch (other) { - .detached => true, - .upgradedDuplex, .connected, .connecting, .pipe => false, - }, - .upgradedDuplex => switch (other) { - .upgradedDuplex => this.upgradedDuplex == other.upgradedDuplex, - .connected, .connecting, .detached, .pipe => false, - }, - .pipe => switch (other) { - .pipe => if (Environment.isWindows) other.pipe == other.pipe else false, - .connected, .connecting, .detached, .upgradedDuplex => false, - }, - }; - } -}; - -pub fn NewSocketHandler(comptime is_ssl: bool) type { - return struct { - const ssl_int: i32 = @intFromBool(is_ssl); - - socket: InternalSocket, - - const ThisSocket = @This(); - - pub const detached: NewSocketHandler(is_ssl) = NewSocketHandler(is_ssl){ .socket = .{ .detached = {} } }; - - pub fn setNoDelay(this: ThisSocket, enabled: bool) bool { - return this.socket.setNoDelay(enabled); - } - - pub fn setKeepAlive(this: ThisSocket, enabled: bool, delay: u32) bool { - return this.socket.setKeepAlive(enabled, delay); - } - - pub fn pauseStream(this: ThisSocket) bool { - return this.socket.pauseResume(is_ssl, true); - } - - pub fn resumeStream(this: ThisSocket) bool { - return this.socket.pauseResume(is_ssl, false); - } - - pub fn detach(this: *ThisSocket) void { - this.socket.detach(); - } - - pub fn isDetached(this: ThisSocket) bool { - return this.socket.isDetached(); - } - - pub fn isNamedPipe(this: ThisSocket) bool { - return this.socket.isNamedPipe(); - } - - pub fn verifyError(this: ThisSocket) us_bun_verify_error_t { - switch (this.socket) { - .connected => |socket| return uws.us_socket_verify_error(comptime ssl_int, socket), - .upgradedDuplex => |socket| return socket.sslError(), - .pipe => |pipe| if (Environment.isWindows) return pipe.sslError() else return std.mem.zeroes(us_bun_verify_error_t), - .connecting, .detached => return std.mem.zeroes(us_bun_verify_error_t), - } - } - - pub fn isEstablished(this: ThisSocket) bool { - switch (this.socket) { - .connected => |socket| return us_socket_is_established(comptime ssl_int, socket) > 0, - .upgradedDuplex => |socket| return socket.isEstablished(), - .pipe => |pipe| if (Environment.isWindows) return pipe.isEstablished() else return false, - .connecting, .detached => return false, - } - } - - pub fn timeout(this: ThisSocket, seconds: c_uint) void { - switch (this.socket) { - .upgradedDuplex => |socket| socket.setTimeout(seconds), - .pipe => |pipe| if (Environment.isWindows) pipe.setTimeout(seconds), - .connected => |socket| socket.setTimeout(is_ssl, seconds), - .connecting => |socket| us_connecting_socket_timeout(comptime ssl_int, socket, seconds), - .detached => {}, - } - } - - pub fn setTimeout(this: ThisSocket, seconds: c_uint) void { - switch (this.socket) { - .connected => |socket| { - if (seconds > 240) { - socket.setTimeout(is_ssl, 0); - socket.setLongTimeout(is_ssl, seconds / 60); - } else { - socket.setTimeout(is_ssl, seconds); - socket.setLongTimeout(is_ssl, 0); - } - }, - .connecting => |socket| { - if (seconds > 240) { - us_connecting_socket_timeout(comptime ssl_int, socket, 0); - us_connecting_socket_long_timeout(comptime ssl_int, socket, seconds / 60); - } else { - us_connecting_socket_timeout(comptime ssl_int, socket, seconds); - us_connecting_socket_long_timeout(comptime ssl_int, socket, 0); - } - }, - .detached => {}, - .upgradedDuplex => |socket| socket.setTimeout(seconds), - .pipe => |pipe| if (Environment.isWindows) pipe.setTimeout(seconds), - } - } - - pub fn setTimeoutMinutes(this: ThisSocket, minutes: c_uint) void { - switch (this.socket) { - .connected => |socket| { - socket.setTimeout(is_ssl, 0); - socket.setLongTimeout(is_ssl, minutes); - }, - .connecting => |socket| { - us_connecting_socket_timeout(comptime ssl_int, socket, 0); - us_connecting_socket_long_timeout(comptime ssl_int, socket, minutes); - }, - .detached => {}, - .upgradedDuplex => |socket| socket.setTimeout(minutes * 60), - .pipe => |pipe| if (Environment.isWindows) pipe.setTimeout(minutes * 60), - } - } - - pub fn startTLS(this: ThisSocket, is_client: bool) void { - if (this.socket.get()) |socket| socket.open(is_ssl, is_client, null); - } - - pub fn ssl(this: ThisSocket) ?*BoringSSL.SSL { - if (comptime is_ssl) { - if (this.getNativeHandle()) |handle| { - return @as(*BoringSSL.SSL, @ptrCast(handle)); - } - return null; - } - return null; - } - - // Note: this assumes that the socket is non-TLS and will be adopted and wrapped with a new TLS context - // context ext will not be copied to the new context, new context will contain us_wrapped_socket_context_t on ext - pub fn wrapTLS( - this: ThisSocket, - options: us_bun_socket_context_options_t, - socket_ext_size: i32, - comptime deref: bool, - comptime ContextType: type, - comptime Fields: anytype, - ) ?NewSocketHandler(true) { - const TLSSocket = NewSocketHandler(true); - const SocketHandler = struct { - const alignment = if (ContextType == anyopaque) - @sizeOf(usize) - else - std.meta.alignment(ContextType); - const deref_ = deref; - const ValueType = if (deref) ContextType else *ContextType; - fn getValue(socket: *Socket) ValueType { - if (comptime ContextType == anyopaque) { - return socket.ext(true); - } - - if (comptime deref_) { - return (TLSSocket.from(socket)).ext(ContextType).?.*; - } - - return (TLSSocket.from(socket)).ext(ContextType); - } - - pub fn on_open(socket: *Socket, is_client: i32, _: [*c]u8, _: i32) callconv(.C) ?*Socket { - if (comptime @hasDecl(Fields, "onCreate")) { - if (is_client == 0) { - Fields.onCreate( - TLSSocket.from(socket), - ); - } - } - Fields.onOpen( - getValue(socket), - TLSSocket.from(socket), - ); - return socket; - } - pub fn on_close(socket: *Socket, code: i32, reason: ?*anyopaque) callconv(.C) ?*Socket { - Fields.onClose( - getValue(socket), - TLSSocket.from(socket), - code, - reason, - ); - return socket; - } - pub fn on_data(socket: *Socket, buf: ?[*]u8, len: i32) callconv(.C) ?*Socket { - Fields.onData( - getValue(socket), - TLSSocket.from(socket), - buf.?[0..@as(usize, @intCast(len))], - ); - return socket; - } - pub fn on_writable(socket: *Socket) callconv(.C) ?*Socket { - Fields.onWritable( - getValue(socket), - TLSSocket.from(socket), - ); - return socket; - } - pub fn on_timeout(socket: *Socket) callconv(.C) ?*Socket { - Fields.onTimeout( - getValue(socket), - TLSSocket.from(socket), - ); - return socket; - } - pub fn on_long_timeout(socket: *Socket) callconv(.C) ?*Socket { - Fields.onLongTimeout( - getValue(socket), - TLSSocket.from(socket), - ); - return socket; - } - pub fn on_connect_error(socket: *Socket, code: i32) callconv(.C) ?*Socket { - Fields.onConnectError( - TLSSocket.from(socket).ext(ContextType).?.*, - TLSSocket.from(socket), - code, - ); - return socket; - } - pub fn on_connect_error_connecting_socket(socket: *ConnectingSocket, code: i32) callconv(.C) ?*ConnectingSocket { - Fields.onConnectError( - @as(*align(alignment) ContextType, @ptrCast(@alignCast(us_connecting_socket_ext(1, socket)))).*, - TLSSocket.fromConnecting(socket), - code, - ); - return socket; - } - pub fn on_end(socket: *Socket) callconv(.C) ?*Socket { - Fields.onEnd( - getValue(socket), - TLSSocket.from(socket), - ); - return socket; - } - pub fn on_handshake(socket: *Socket, success: i32, verify_error: us_bun_verify_error_t, _: ?*anyopaque) callconv(.C) void { - Fields.onHandshake(getValue(socket), TLSSocket.from(socket), success, verify_error); - } - }; - - const events: us_socket_events_t = .{ - .on_open = SocketHandler.on_open, - .on_close = SocketHandler.on_close, - .on_data = SocketHandler.on_data, - .on_writable = SocketHandler.on_writable, - .on_timeout = SocketHandler.on_timeout, - .on_connect_error = SocketHandler.on_connect_error, - .on_connect_error_connecting_socket = SocketHandler.on_connect_error_connecting_socket, - .on_end = SocketHandler.on_end, - .on_handshake = SocketHandler.on_handshake, - .on_long_timeout = SocketHandler.on_long_timeout, - }; - - const this_socket = this.socket.get() orelse return null; - - const socket = us_socket_wrap_with_tls(ssl_int, this_socket, options, events, socket_ext_size) orelse return null; - return NewSocketHandler(true).from(socket); - } - - pub fn getNativeHandle(this: ThisSocket) ?*NativeSocketHandleType(is_ssl) { - return @ptrCast(switch (this.socket) { - .connected => |socket| socket.getNativeHandle(is_ssl), - .connecting => |socket| us_connecting_socket_get_native_handle(comptime ssl_int, socket), - .detached => null, - .upgradedDuplex => |socket| if (is_ssl) @as(*anyopaque, @ptrCast(socket.ssl() orelse return null)) else null, - .pipe => |socket| if (is_ssl and Environment.isWindows) @as(*anyopaque, @ptrCast(socket.ssl() orelse return null)) else null, - } orelse return null); - } - - pub inline fn fd(this: ThisSocket) bun.FileDescriptor { - if (comptime is_ssl) { - @compileError("SSL sockets do not have a file descriptor accessible this way"); - } - const socket = this.socket.get() orelse return bun.invalid_fd; - - // on windows uSockets exposes SOCKET - return if (comptime Environment.isWindows) - .fromNative(@ptrCast(socket.getNativeHandle(is_ssl).?)) - else - .fromNative(@intCast(@intFromPtr(socket.getNativeHandle(is_ssl)))); - } - - pub fn markNeedsMoreForSendfile(this: ThisSocket) void { - if (comptime is_ssl) { - @compileError("SSL sockets do not support sendfile yet"); - } - const socket = this.socket.get() orelse return; - socket.sendFileNeedsMore(); - } - - pub fn ext(this: ThisSocket, comptime ContextType: type) ?*ContextType { - const alignment = if (ContextType == *anyopaque) - @sizeOf(usize) - else - std.meta.alignment(ContextType); - - const ptr = switch (this.socket) { - .connected => |sock| sock.ext(is_ssl), - .connecting => |sock| us_connecting_socket_ext(comptime ssl_int, sock), - .detached => return null, - .upgradedDuplex => return null, - .pipe => return null, - }; - - return @as(*align(alignment) ContextType, @ptrCast(@alignCast(ptr))); - } - - /// This can be null if the socket was closed. - pub fn context(this: ThisSocket) ?*SocketContext { - switch (this.socket) { - .connected => |socket| return socket.context(is_ssl), - .connecting => |socket| return us_connecting_socket_context(comptime ssl_int, socket), - .detached => return null, - .upgradedDuplex => return null, - .pipe => return null, - } - } - - pub fn flush(this: ThisSocket) void { - switch (this.socket) { - .upgradedDuplex => |socket| socket.flush(), - .pipe => |pipe| if (comptime Environment.isWindows) pipe.flush(), - .connected => |socket| socket.flush(is_ssl), - .connecting, .detached => return, - } - } - - pub fn write(this: ThisSocket, data: []const u8, msg_more: bool) i32 { - return switch (this.socket) { - .upgradedDuplex => |socket| socket.encodeAndWrite(data, msg_more), - .pipe => |pipe| if (comptime Environment.isWindows) pipe.encodeAndWrite(data, msg_more) else 0, - .connected => |socket| socket.write(is_ssl, data, msg_more), - .connecting, .detached => 0, - }; - } - - pub fn writeFd(this: ThisSocket, data: []const u8, file_descriptor: bun.FileDescriptor) i32 { - return switch (this.socket) { - .upgradedDuplex, .pipe => this.write(data, false), - .connected => |socket| socket.writeFd(data, file_descriptor), - .connecting, .detached => 0, - }; - } - - pub fn rawWrite(this: ThisSocket, data: []const u8, msg_more: bool) i32 { - return switch (this.socket) { - .connected => |socket| socket.rawWrite(is_ssl, data, msg_more), - .connecting, .detached => 0, - .upgradedDuplex => |socket| socket.rawWrite(data, msg_more), - .pipe => |pipe| if (comptime Environment.isWindows) pipe.rawWrite(data, msg_more) else 0, - }; - } - - pub fn shutdown(this: ThisSocket) void { - switch (this.socket) { - .connected => |socket| socket.shutdown(is_ssl), - .connecting => |socket| { - debug("us_connecting_socket_shutdown({d})", .{@intFromPtr(socket)}); - return us_connecting_socket_shutdown( - comptime ssl_int, - socket, - ); - }, - .detached => {}, - .upgradedDuplex => |socket| socket.shutdown(), - .pipe => |pipe| if (comptime Environment.isWindows) pipe.shutdown(), - } - } - - pub fn shutdownRead(this: ThisSocket) void { - switch (this.socket) { - .connected => |socket| socket.shutdownRead(is_ssl), - .connecting => |socket| { - debug("us_connecting_socket_shutdown_read({d})", .{@intFromPtr(socket)}); - return us_connecting_socket_shutdown_read( - comptime ssl_int, - socket, - ); - }, - .upgradedDuplex => |socket| socket.shutdownRead(), - .pipe => |pipe| if (comptime Environment.isWindows) pipe.shutdownRead(), - .detached => {}, - } - } - - pub fn isShutdown(this: ThisSocket) bool { - return switch (this.socket) { - .connected => |socket| socket.isShutDown(is_ssl), - .connecting => |socket| blk: { - debug("us_connecting_socket_is_shut_down({d})", .{@intFromPtr(socket)}); - break :blk us_connecting_socket_is_shut_down( - comptime ssl_int, - socket, - ) > 0; - }, - .upgradedDuplex => |socket| socket.isShutdown(), - .pipe => |pipe| return if (Environment.isWindows) pipe.isShutdown() else false, - .detached => true, - }; - } - - pub fn isClosedOrHasError(this: ThisSocket) bool { - if (this.isClosed() or this.isShutdown()) { - return true; - } - - return this.getError() != 0; - } - - pub fn getError(this: ThisSocket) i32 { - switch (this.socket) { - .connected => |socket| { - debug("us_socket_get_error({d})", .{@intFromPtr(socket)}); - return us_socket_get_error( - comptime ssl_int, - socket, - ); - }, - .connecting => |socket| { - debug("us_connecting_socket_get_error({d})", .{@intFromPtr(socket)}); - return us_connecting_socket_get_error( - comptime ssl_int, - socket, - ); - }, - .detached => return 0, - .upgradedDuplex => |socket| { - return socket.sslError().error_no; - }, - .pipe => |pipe| { - return if (Environment.isWindows) pipe.sslError().error_no else 0; - }, - } - } - - pub fn isClosed(this: ThisSocket) bool { - return this.socket.isClosed(comptime is_ssl); - } - - pub fn close(this: ThisSocket, code: Socket.CloseCode) void { - return this.socket.close(comptime is_ssl, code); - } - - pub fn localPort(this: ThisSocket) i32 { - return switch (this.socket) { - .connected => |socket| socket.localPort(is_ssl), - .pipe, .upgradedDuplex, .connecting, .detached => 0, - }; - } - - pub fn remotePort(this: ThisSocket) i32 { - return switch (this.socket) { - .connected => |socket| socket.remotePort(is_ssl), - .pipe, .upgradedDuplex, .connecting, .detached => 0, - }; - } - - /// `buf` cannot be longer than 2^31 bytes long. - pub fn remoteAddress(this: ThisSocket, buf: []u8) ?[]const u8 { - return switch (this.socket) { - .connected => |sock| sock.remoteAddress(is_ssl, buf) catch |e| { - bun.Output.panic("Failed to get socket's remote address: {s}", .{@errorName(e)}); - }, - .pipe, .upgradedDuplex, .connecting, .detached => null, - }; - } - - /// Get the local address of a socket in binary format. - /// - /// # Arguments - /// - `buf`: A buffer to store the binary address data. - /// - /// # Returns - /// This function returns a slice of the buffer on success, or null on failure. - pub fn localAddress(this: ThisSocket, buf: []u8) ?[]const u8 { - return switch (this.socket) { - .connected => |sock| sock.localAddress(is_ssl, buf) catch |e| { - bun.Output.panic("Failed to get socket's local address: {s}", .{@errorName(e)}); - }, - .pipe, .upgradedDuplex, .connecting, .detached => null, - }; - } - - pub fn connect( - host: []const u8, - port: i32, - socket_ctx: *SocketContext, - comptime Context: type, - ctx: Context, - comptime socket_field_name: []const u8, - allowHalfOpen: bool, - ) ?*Context { - debug("connect({s}, {d})", .{ host, port }); - - var stack_fallback = std.heap.stackFallback(1024, bun.default_allocator); - var allocator = stack_fallback.get(); - - // remove brackets from IPv6 addresses, as getaddrinfo doesn't understand them - const clean_host = if (host.len > 1 and host[0] == '[' and host[host.len - 1] == ']') - host[1 .. host.len - 1] - else - host; - - const host_ = allocator.dupeZ(u8, clean_host) catch bun.outOfMemory(); - defer allocator.free(host); - - var did_dns_resolve: i32 = 0; - const socket = us_socket_context_connect(comptime ssl_int, socket_ctx, host_, port, if (allowHalfOpen) LIBUS_SOCKET_ALLOW_HALF_OPEN else 0, @sizeOf(Context), &did_dns_resolve) orelse return null; - const socket_ = if (did_dns_resolve == 1) - ThisSocket{ - .socket = .{ .connected = @ptrCast(socket) }, - } - else - ThisSocket{ - .socket = .{ .connecting = @ptrCast(socket) }, - }; - - var holder = socket_.ext(Context); - holder.* = ctx; - @field(holder, socket_field_name) = socket_; - return holder; - } - - pub fn connectPtr( - host: []const u8, - port: i32, - socket_ctx: *SocketContext, - comptime Context: type, - ctx: *Context, - comptime socket_field_name: []const u8, - allowHalfOpen: bool, - ) !*Context { - const this_socket = try connectAnon(host, port, socket_ctx, ctx, allowHalfOpen); - @field(ctx, socket_field_name) = this_socket; - return ctx; - } - - pub fn fromDuplex( - duplex: *UpgradedDuplex, - ) ThisSocket { - return ThisSocket{ .socket = .{ .upgradedDuplex = duplex } }; - } - - pub fn fromNamedPipe( - pipe: *WindowsNamedPipe, - ) ThisSocket { - if (Environment.isWindows) { - return ThisSocket{ .socket = .{ .pipe = pipe } }; - } - @compileError("WindowsNamedPipe is only available on Windows"); - } - - pub fn fromFd( - ctx: *SocketContext, - handle: bun.FileDescriptor, - comptime This: type, - this: *This, - comptime socket_field_name: ?[]const u8, - is_ipc: bool, - ) ?ThisSocket { - const socket_ = ThisSocket{ .socket = .{ .connected = us_socket_from_fd(ctx, @sizeOf(*anyopaque), handle.asSocketFd(), @intFromBool(is_ipc)) orelse return null } }; - - if (socket_.ext(*anyopaque)) |holder| { - holder.* = this; - } - - if (comptime socket_field_name) |field| { - @field(this, field) = socket_; - } - - return socket_; - } - - pub fn connectUnixPtr( - path: []const u8, - socket_ctx: *SocketContext, - comptime Context: type, - ctx: *Context, - comptime socket_field_name: []const u8, - ) !*Context { - const this_socket = try connectUnixAnon(path, socket_ctx, ctx); - @field(ctx, socket_field_name) = this_socket; - return ctx; - } - - pub fn connectUnixAnon( - path: []const u8, - socket_ctx: *SocketContext, - ctx: *anyopaque, - allowHalfOpen: bool, - ) !ThisSocket { - debug("connect(unix:{s})", .{path}); - var stack_fallback = std.heap.stackFallback(1024, bun.default_allocator); - var allocator = stack_fallback.get(); - const path_ = allocator.dupeZ(u8, path) catch bun.outOfMemory(); - defer allocator.free(path_); - - const socket = us_socket_context_connect_unix(comptime ssl_int, socket_ctx, path_, path_.len, if (allowHalfOpen) LIBUS_SOCKET_ALLOW_HALF_OPEN else 0, 8) orelse - return error.FailedToOpenSocket; - - const socket_ = ThisSocket{ .socket = .{ .connected = socket } }; - if (socket_.ext(*anyopaque)) |holder| { - holder.* = ctx; - } - return socket_; - } - - pub fn connectAnon( - raw_host: []const u8, - port: i32, - socket_ctx: *SocketContext, - ptr: *anyopaque, - allowHalfOpen: bool, - ) !ThisSocket { - debug("connect({s}, {d})", .{ raw_host, port }); - var stack_fallback = std.heap.stackFallback(1024, bun.default_allocator); - var allocator = stack_fallback.get(); - - // remove brackets from IPv6 addresses, as getaddrinfo doesn't understand them - const clean_host = if (raw_host.len > 1 and raw_host[0] == '[' and raw_host[raw_host.len - 1] == ']') - raw_host[1 .. raw_host.len - 1] - else - raw_host; - - const host = allocator.dupeZ(u8, clean_host) catch bun.outOfMemory(); - defer allocator.free(host); - - var did_dns_resolve: i32 = 0; - const socket_ptr = us_socket_context_connect( - comptime ssl_int, - socket_ctx, - host.ptr, - port, - if (allowHalfOpen) LIBUS_SOCKET_ALLOW_HALF_OPEN else 0, - @sizeOf(*anyopaque), - &did_dns_resolve, - ) orelse return error.FailedToOpenSocket; - const socket = if (did_dns_resolve == 1) - ThisSocket{ - .socket = .{ .connected = @ptrCast(socket_ptr) }, - } - else - ThisSocket{ - .socket = .{ .connecting = @ptrCast(socket_ptr) }, - }; - if (socket.ext(*anyopaque)) |holder| { - holder.* = ptr; - } - return socket; - } - - pub fn unsafeConfigure( - ctx: *SocketContext, - comptime ssl_type: bool, - comptime deref: bool, - comptime ContextType: type, - comptime Fields: anytype, - ) void { - const SocketHandlerType = NewSocketHandler(ssl_type); - const Type = comptime if (@TypeOf(Fields) != type) @TypeOf(Fields) else Fields; - - const SocketHandler = struct { - const alignment = if (ContextType == anyopaque) - @sizeOf(usize) - else - std.meta.alignment(ContextType); - const deref_ = deref; - const ValueType = if (deref) ContextType else *ContextType; - fn getValue(socket: *Socket) ValueType { - if (comptime ContextType == anyopaque) { - return socket.ext(is_ssl); - } - - if (comptime deref_) { - return (SocketHandlerType.from(socket)).ext(ContextType).?.*; - } - - return (SocketHandlerType.from(socket)).ext(ContextType); - } - - pub fn on_open(socket: *Socket, is_client: i32, _: [*c]u8, _: i32) callconv(.C) ?*Socket { - if (comptime @hasDecl(Fields, "onCreate")) { - if (is_client == 0) { - Fields.onCreate( - SocketHandlerType.from(socket), - ); - } - } - Fields.onOpen( - getValue(socket), - SocketHandlerType.from(socket), - ); - return socket; - } - pub fn on_close(socket: *Socket, code: i32, reason: ?*anyopaque) callconv(.C) ?*Socket { - Fields.onClose( - getValue(socket), - SocketHandlerType.from(socket), - code, - reason, - ); - return socket; - } - pub fn on_data(socket: *Socket, buf: ?[*]u8, len: i32) callconv(.C) ?*Socket { - Fields.onData( - getValue(socket), - SocketHandlerType.from(socket), - buf.?[0..@as(usize, @intCast(len))], - ); - return socket; - } - pub fn on_writable(socket: *Socket) callconv(.C) ?*Socket { - Fields.onWritable( - getValue(socket), - SocketHandlerType.from(socket), - ); - return socket; - } - pub fn on_timeout(socket: *Socket) callconv(.C) ?*Socket { - Fields.onTimeout( - getValue(socket), - SocketHandlerType.from(socket), - ); - return socket; - } - pub fn on_connect_error_connecting_socket(socket: *ConnectingSocket, code: i32) callconv(.C) ?*ConnectingSocket { - const val = if (comptime ContextType == anyopaque) - us_connecting_socket_ext(comptime ssl_int, socket) - else if (comptime deref_) - SocketHandlerType.fromConnecting(socket).ext(ContextType).?.* - else - SocketHandlerType.fromConnecting(socket).ext(ContextType); - Fields.onConnectError( - val, - SocketHandlerType.fromConnecting(socket), - code, - ); - return socket; - } - pub fn on_connect_error(socket: *Socket, code: i32) callconv(.C) ?*Socket { - const val = if (comptime ContextType == anyopaque) - socket.ext(is_ssl) - else if (comptime deref_) - SocketHandlerType.from(socket).ext(ContextType).?.* - else - SocketHandlerType.from(socket).ext(ContextType); - Fields.onConnectError( - val, - SocketHandlerType.from(socket), - code, - ); - return socket; - } - pub fn on_end(socket: *Socket) callconv(.C) ?*Socket { - Fields.onEnd( - getValue(socket), - SocketHandlerType.from(socket), - ); - return socket; - } - pub fn on_handshake(socket: *Socket, success: i32, verify_error: us_bun_verify_error_t, _: ?*anyopaque) callconv(.C) void { - Fields.onHandshake(getValue(socket), SocketHandlerType.from(socket), success, verify_error); - } - }; - - if (@typeInfo(@TypeOf(Type.onOpen)) != .null) - us_socket_context_on_open(ssl_int, ctx, SocketHandler.on_open); - if (@typeInfo(@TypeOf(Type.onClose)) != .null) - us_socket_context_on_close(ssl_int, ctx, SocketHandler.on_close); - if (@typeInfo(@TypeOf(Type.onData)) != .null) - us_socket_context_on_data(ssl_int, ctx, SocketHandler.on_data); - if (@typeInfo(@TypeOf(Type.onFd)) != .null) - us_socket_context_on_fd(ssl_int, ctx, SocketHandler.on_fd); - if (@typeInfo(@TypeOf(Type.onWritable)) != .null) - us_socket_context_on_writable(ssl_int, ctx, SocketHandler.on_writable); - if (@typeInfo(@TypeOf(Type.onTimeout)) != .null) - us_socket_context_on_timeout(ssl_int, ctx, SocketHandler.on_timeout); - if (@typeInfo(@TypeOf(Type.onConnectError)) != .null) { - us_socket_context_on_socket_connect_error(ssl_int, ctx, SocketHandler.on_connect_error); - us_socket_context_on_connect_error(ssl_int, ctx, SocketHandler.on_connect_error_connecting_socket); - } - if (@typeInfo(@TypeOf(Type.onEnd)) != .null) - us_socket_context_on_end(ssl_int, ctx, SocketHandler.on_end); - if (@typeInfo(@TypeOf(Type.onHandshake)) != .null) - us_socket_context_on_handshake(ssl_int, ctx, SocketHandler.on_handshake, null); - } - - pub fn configure( - ctx: *SocketContext, - comptime deref: bool, - comptime ContextType: type, - comptime Fields: anytype, - ) void { - const Type = comptime if (@TypeOf(Fields) != type) @TypeOf(Fields) else Fields; - - const SocketHandler = struct { - const alignment = if (ContextType == anyopaque) - @sizeOf(usize) - else - std.meta.alignment(ContextType); - const deref_ = deref; - const ValueType = if (deref) ContextType else *ContextType; - fn getValue(socket: *Socket) ValueType { - if (comptime ContextType == anyopaque) { - return socket.ext(is_ssl); - } - - if (comptime deref_) { - return (ThisSocket.from(socket)).ext(ContextType).?.*; - } - - return (ThisSocket.from(socket)).ext(ContextType); - } - - pub fn on_open(socket: *Socket, is_client: i32, _: [*c]u8, _: i32) callconv(.C) ?*Socket { - if (comptime @hasDecl(Fields, "onCreate")) { - if (is_client == 0) { - Fields.onCreate( - ThisSocket.from(socket), - ); - } - } - Fields.onOpen( - getValue(socket), - ThisSocket.from(socket), - ); - return socket; - } - pub fn on_close(socket: *Socket, code: i32, reason: ?*anyopaque) callconv(.C) ?*Socket { - Fields.onClose( - getValue(socket), - ThisSocket.from(socket), - code, - reason, - ); - return socket; - } - pub fn on_data(socket: *Socket, buf: ?[*]u8, len: i32) callconv(.C) ?*Socket { - Fields.onData( - getValue(socket), - ThisSocket.from(socket), - buf.?[0..@as(usize, @intCast(len))], - ); - return socket; - } - pub fn on_fd(socket: *Socket, file_descriptor: c_int) callconv(.C) ?*Socket { - Fields.onFd( - getValue(socket), - ThisSocket.from(socket), - file_descriptor, - ); - return socket; - } - pub fn on_writable(socket: *Socket) callconv(.C) ?*Socket { - Fields.onWritable( - getValue(socket), - ThisSocket.from(socket), - ); - return socket; - } - pub fn on_timeout(socket: *Socket) callconv(.C) ?*Socket { - Fields.onTimeout( - getValue(socket), - ThisSocket.from(socket), - ); - return socket; - } - pub fn on_long_timeout(socket: *Socket) callconv(.C) ?*Socket { - Fields.onLongTimeout( - getValue(socket), - ThisSocket.from(socket), - ); - return socket; - } - pub fn on_connect_error_connecting_socket(socket: *ConnectingSocket, code: i32) callconv(.C) ?*ConnectingSocket { - const val = if (comptime ContextType == anyopaque) - us_connecting_socket_ext(comptime ssl_int, socket) - else if (comptime deref_) - ThisSocket.fromConnecting(socket).ext(ContextType).?.* - else - ThisSocket.fromConnecting(socket).ext(ContextType); - Fields.onConnectError( - val, - ThisSocket.fromConnecting(socket), - code, - ); - return socket; - } - pub fn on_connect_error(socket: *Socket, code: i32) callconv(.C) ?*Socket { - const val = if (comptime ContextType == anyopaque) - socket.ext(is_ssl) - else if (comptime deref_) - ThisSocket.from(socket).ext(ContextType).?.* - else - ThisSocket.from(socket).ext(ContextType); - - // We close immediately in this case - // uSockets doesn't know if this is a TLS socket or not. - // So we need to close it like a TCP socket. - NewSocketHandler(false).from(socket).close(.failure); - - Fields.onConnectError( - val, - ThisSocket.from(socket), - code, - ); - return socket; - } - pub fn on_end(socket: *Socket) callconv(.C) ?*Socket { - Fields.onEnd( - getValue(socket), - ThisSocket.from(socket), - ); - return socket; - } - pub fn on_handshake(socket: *Socket, success: i32, verify_error: us_bun_verify_error_t, _: ?*anyopaque) callconv(.C) void { - Fields.onHandshake(getValue(socket), ThisSocket.from(socket), success, verify_error); - } - }; - - if (comptime @hasDecl(Type, "onOpen") and @typeInfo(@TypeOf(Type.onOpen)) != .null) - us_socket_context_on_open(ssl_int, ctx, SocketHandler.on_open); - if (comptime @hasDecl(Type, "onClose") and @typeInfo(@TypeOf(Type.onClose)) != .null) - us_socket_context_on_close(ssl_int, ctx, SocketHandler.on_close); - if (comptime @hasDecl(Type, "onData") and @typeInfo(@TypeOf(Type.onData)) != .null) - us_socket_context_on_data(ssl_int, ctx, SocketHandler.on_data); - if (comptime @hasDecl(Type, "onFd") and @typeInfo(@TypeOf(Type.onFd)) != .null) - us_socket_context_on_fd(ssl_int, ctx, SocketHandler.on_fd); - if (comptime @hasDecl(Type, "onWritable") and @typeInfo(@TypeOf(Type.onWritable)) != .null) - us_socket_context_on_writable(ssl_int, ctx, SocketHandler.on_writable); - if (comptime @hasDecl(Type, "onTimeout") and @typeInfo(@TypeOf(Type.onTimeout)) != .null) - us_socket_context_on_timeout(ssl_int, ctx, SocketHandler.on_timeout); - if (comptime @hasDecl(Type, "onConnectError") and @typeInfo(@TypeOf(Type.onConnectError)) != .null) { - us_socket_context_on_socket_connect_error(ssl_int, ctx, SocketHandler.on_connect_error); - us_socket_context_on_connect_error(ssl_int, ctx, SocketHandler.on_connect_error_connecting_socket); - } - if (comptime @hasDecl(Type, "onEnd") and @typeInfo(@TypeOf(Type.onEnd)) != .null) - us_socket_context_on_end(ssl_int, ctx, SocketHandler.on_end); - if (comptime @hasDecl(Type, "onHandshake") and @typeInfo(@TypeOf(Type.onHandshake)) != .null) - us_socket_context_on_handshake(ssl_int, ctx, SocketHandler.on_handshake, null); - if (comptime @hasDecl(Type, "onLongTimeout") and @typeInfo(@TypeOf(Type.onLongTimeout)) != .null) - us_socket_context_on_long_timeout(ssl_int, ctx, SocketHandler.on_long_timeout); - } - - pub fn from(socket: *Socket) ThisSocket { - return ThisSocket{ .socket = .{ .connected = socket } }; - } - - pub fn fromConnecting(connecting: *ConnectingSocket) ThisSocket { - return ThisSocket{ .socket = .{ .connecting = connecting } }; - } - - pub fn fromAny(socket: InternalSocket) ThisSocket { - return ThisSocket{ .socket = socket }; - } - - pub fn adoptPtr( - socket: *Socket, - socket_ctx: *SocketContext, - comptime Context: type, - comptime socket_field_name: []const u8, - ctx: *Context, - ) bool { - // ext_size of -1 means we want to keep the current ext size - // in particular, we don't want to allocate a new socket - const new_socket = us_socket_context_adopt_socket(comptime ssl_int, socket_ctx, socket, -1) orelse return false; - bun.assert(new_socket == socket); - var adopted = ThisSocket.from(new_socket); - if (adopted.ext(*anyopaque)) |holder| { - holder.* = ctx; - } - @field(ctx, socket_field_name) = adopted; - return true; - } - }; -} -pub const SocketTCP = NewSocketHandler(false); -pub const SocketTLS = NewSocketHandler(true); - -pub const Timer = opaque { - pub fn create(loop: *Loop, ptr: anytype) *Timer { - const Type = @TypeOf(ptr); - - // never fallthrough poll - // the problem is uSockets hardcodes it on the other end - // so we can never free non-fallthrough polls - return us_create_timer(loop, 0, @sizeOf(Type)) orelse std.debug.panic("us_create_timer: returned null: {d}", .{std.c._errno().*}); - } - - pub fn createFallthrough(loop: *Loop, ptr: anytype) *Timer { - const Type = @TypeOf(ptr); - - // never fallthrough poll - // the problem is uSockets hardcodes it on the other end - // so we can never free non-fallthrough polls - return us_create_timer(loop, 1, @sizeOf(Type)) orelse std.debug.panic("us_create_timer: returned null: {d}", .{std.c._errno().*}); - } - - pub fn set(this: *Timer, ptr: anytype, cb: ?*const fn (*Timer) callconv(.C) void, ms: i32, repeat_ms: i32) void { - us_timer_set(this, cb, ms, repeat_ms); - const value_ptr = us_timer_ext(this); - @setRuntimeSafety(false); - @as(*@TypeOf(ptr), @ptrCast(@alignCast(value_ptr))).* = ptr; - } - - pub fn deinit(this: *Timer, comptime fallthrough: bool) void { - debug("Timer.deinit()", .{}); - us_timer_close(this, @intFromBool(fallthrough)); - } - - pub fn ext(this: *Timer, comptime Type: type) ?*Type { - return @as(*Type, @ptrCast(@alignCast(us_timer_ext(this).*.?))); - } - - pub fn as(this: *Timer, comptime Type: type) Type { - @setRuntimeSafety(false); - return @as(*?Type, @ptrCast(@alignCast(us_timer_ext(this)))).*.?; - } -}; - -pub const SocketContext = opaque { - pub fn getNativeHandle(this: *SocketContext, comptime ssl: bool) *anyopaque { - return us_socket_context_get_native_handle(@intFromBool(ssl), this).?; - } - - fn _deinit_ssl(this: *SocketContext) void { - us_socket_context_free(@as(i32, 1), this); - } - - fn _deinit(this: *SocketContext) void { - us_socket_context_free(@as(i32, 0), this); - } - - pub fn ref(this: *SocketContext, comptime ssl: bool) *SocketContext { - us_socket_context_ref(@intFromBool(ssl), this); - return this; - } - - pub fn unref(this: *SocketContext, comptime ssl: bool) *SocketContext { - us_socket_context_unref(@intFromBool(ssl), this); - return this; - } - - pub fn cleanCallbacks(ctx: *SocketContext, is_ssl: bool) void { - const ssl_int: i32 = @intFromBool(is_ssl); - // replace callbacks with dummy ones - const DummyCallbacks = struct { - fn open(socket: *Socket, _: i32, _: [*c]u8, _: i32) callconv(.C) ?*Socket { - return socket; - } - fn close(socket: *Socket, _: i32, _: ?*anyopaque) callconv(.C) ?*Socket { - return socket; - } - fn data(socket: *Socket, _: [*c]u8, _: i32) callconv(.C) ?*Socket { - return socket; - } - fn fd(socket: *Socket, _: c_int) callconv(.C) ?*Socket { - return socket; - } - fn writable(socket: *Socket) callconv(.C) ?*Socket { - return socket; - } - fn timeout(socket: *Socket) callconv(.C) ?*Socket { - return socket; - } - fn connect_error(socket: *ConnectingSocket, _: i32) callconv(.C) ?*ConnectingSocket { - return socket; - } - fn socket_connect_error(socket: *Socket, _: i32) callconv(.C) ?*Socket { - return socket; - } - fn end(socket: *Socket) callconv(.C) ?*Socket { - return socket; - } - fn handshake(_: *Socket, _: i32, _: us_bun_verify_error_t, _: ?*anyopaque) callconv(.C) void {} - fn long_timeout(socket: *Socket) callconv(.C) ?*Socket { - return socket; - } - }; - us_socket_context_on_open(ssl_int, ctx, DummyCallbacks.open); - us_socket_context_on_close(ssl_int, ctx, DummyCallbacks.close); - us_socket_context_on_data(ssl_int, ctx, DummyCallbacks.data); - us_socket_context_on_fd(ssl_int, ctx, DummyCallbacks.fd); - us_socket_context_on_writable(ssl_int, ctx, DummyCallbacks.writable); - us_socket_context_on_timeout(ssl_int, ctx, DummyCallbacks.timeout); - us_socket_context_on_connect_error(ssl_int, ctx, DummyCallbacks.connect_error); - us_socket_context_on_socket_connect_error(ssl_int, ctx, DummyCallbacks.socket_connect_error); - us_socket_context_on_end(ssl_int, ctx, DummyCallbacks.end); - us_socket_context_on_handshake(ssl_int, ctx, DummyCallbacks.handshake, null); - us_socket_context_on_long_timeout(ssl_int, ctx, DummyCallbacks.long_timeout); - } - - fn getLoop(this: *SocketContext, ssl: bool) ?*Loop { - return us_socket_context_loop(@intFromBool(ssl), this); - } - - /// closes and deinit the SocketContexts - pub fn deinit(this: *SocketContext, ssl: bool) void { - // we clean the callbacks to avoid UAF because we are deiniting - this.cleanCallbacks(ssl); - this.close(ssl); - //always deinit in next iteration - if (ssl) { - Loop.get().nextTick(*SocketContext, this, SocketContext._deinit_ssl); - } else { - Loop.get().nextTick(*SocketContext, this, SocketContext._deinit); - } - } - - pub fn close(this: *SocketContext, ssl: bool) void { - debug("us_socket_context_close({d})", .{@intFromPtr(this)}); - us_socket_context_close(@intFromBool(ssl), this); - } - - pub fn ext(this: *SocketContext, ssl: bool, comptime ContextType: type) ?*ContextType { - const alignment = if (ContextType == *anyopaque) - @sizeOf(usize) - else - std.meta.alignment(ContextType); - - const ptr = us_socket_context_ext( - @intFromBool(ssl), - this, - ) orelse return null; - - return @as(*align(alignment) ContextType, @ptrCast(@alignCast(ptr))); - } -}; -pub const PosixLoop = extern struct { - internal_loop_data: InternalLoopData align(16), - - /// Number of non-fallthrough polls in the loop - num_polls: i32, - - /// Number of ready polls this iteration - num_ready_polls: i32, - - /// Current index in list of ready polls - current_ready_poll: i32, - - /// Loop's own file descriptor - fd: i32, - - /// Number of polls owned by Bun - active: u32 = 0, - - /// The list of ready polls - ready_polls: [1024]EventType align(16), - - const EventType = switch (Environment.os) { - .linux => std.os.linux.epoll_event, - .mac => std.posix.system.kevent64_s, - // TODO: - .windows => *anyopaque, - else => @compileError("Unsupported OS"), - }; - - const log = bun.Output.scoped(.Loop, false); - - pub fn uncork(this: *PosixLoop) void { - uws_res_clear_corked_socket(this); - } - - pub fn iterationNumber(this: *const PosixLoop) u64 { - return this.internal_loop_data.iteration_nr; - } - - pub fn inc(this: *PosixLoop) void { - log("inc {d} + 1 = {d}", .{ this.num_polls, this.num_polls + 1 }); - this.num_polls += 1; - } - - pub fn dec(this: *PosixLoop) void { - log("dec {d} - 1 = {d}", .{ this.num_polls, this.num_polls - 1 }); - this.num_polls -= 1; - } - - pub fn ref(this: *PosixLoop) void { - log("ref {d} + 1 = {d} | {d} + 1 = {d}", .{ this.num_polls, this.num_polls + 1, this.active, this.active + 1 }); - this.num_polls += 1; - this.active += 1; - } - - pub fn unref(this: *PosixLoop) void { - log("unref {d} - 1 = {d} | {d} - 1 = {d}", .{ this.num_polls, this.num_polls - 1, this.active, this.active -| 1 }); - this.num_polls -= 1; - this.active -|= 1; - } - - pub fn isActive(this: *const Loop) bool { - return this.active > 0; - } - - // This exists as a method so that we can stick a debugger in here - pub fn addActive(this: *PosixLoop, value: u32) void { - log("add {d} + {d} = {d}", .{ this.active, value, this.active +| value }); - this.active +|= value; - } - - // This exists as a method so that we can stick a debugger in here - pub fn subActive(this: *PosixLoop, value: u32) void { - log("sub {d} - {d} = {d}", .{ this.active, value, this.active -| value }); - this.active -|= value; - } - - pub fn unrefCount(this: *PosixLoop, count: i32) void { - log("unref x {d}", .{count}); - this.num_polls -= count; - this.active -|= @as(u32, @intCast(count)); - } - - pub fn get() *Loop { - return uws_get_loop(); - } - - pub fn create(comptime Handler: anytype) *Loop { - return us_create_loop( - null, - Handler.wakeup, - if (@hasDecl(Handler, "pre")) Handler.pre else null, - if (@hasDecl(Handler, "post")) Handler.post else null, - 0, - ).?; - } - - pub fn wakeup(this: *PosixLoop) void { - return us_wakeup_loop(this); - } - - pub const wake = wakeup; - - pub fn tick(this: *PosixLoop) void { - us_loop_run_bun_tick(this, null); - } - - pub fn tickWithoutIdle(this: *PosixLoop) void { - const timespec = bun.timespec{ .sec = 0, .nsec = 0 }; - us_loop_run_bun_tick(this, ×pec); - } - - pub fn tickWithTimeout(this: *PosixLoop, timespec: ?*const bun.timespec) void { - us_loop_run_bun_tick(this, timespec); - } - - extern fn us_loop_run_bun_tick(loop: ?*Loop, timouetMs: ?*const bun.timespec) void; - - pub fn nextTick(this: *PosixLoop, comptime UserType: type, user_data: UserType, comptime deferCallback: fn (ctx: UserType) void) void { - const Handler = struct { - pub fn callback(data: *anyopaque) callconv(.C) void { - deferCallback(@as(UserType, @ptrCast(@alignCast(data)))); - } - }; - uws_loop_defer(this, user_data, Handler.callback); - } - - fn NewHandler(comptime UserType: type, comptime callback_fn: fn (UserType) void) type { - return struct { - loop: *Loop, - pub fn removePost(handler: @This()) void { - return uws_loop_removePostHandler(handler.loop, callback); - } - pub fn removePre(handler: @This()) void { - return uws_loop_removePostHandler(handler.loop, callback); - } - pub fn callback(data: *anyopaque, _: *Loop) callconv(.C) void { - callback_fn(@as(UserType, @ptrCast(@alignCast(data)))); - } - }; - } - - pub fn addPostHandler(this: *PosixLoop, comptime UserType: type, ctx: UserType, comptime callback: fn (UserType) void) NewHandler(UserType, callback) { - const Handler = NewHandler(UserType, callback); - - uws_loop_addPostHandler(this, ctx, Handler.callback); - return Handler{ - .loop = this, - }; - } - - pub fn addPreHandler(this: *PosixLoop, comptime UserType: type, ctx: UserType, comptime callback: fn (UserType) void) NewHandler(UserType, callback) { - const Handler = NewHandler(UserType, callback); - - uws_loop_addPreHandler(this, ctx, Handler.callback); - return Handler{ - .loop = this, - }; - } - - pub fn run(this: *PosixLoop) void { - us_loop_run(this); - } -}; - -extern fn uws_loop_defer(loop: *Loop, ctx: *anyopaque, cb: *const (fn (ctx: *anyopaque) callconv(.C) void)) void; - -extern fn us_create_timer(loop: ?*Loop, fallthrough: i32, ext_size: c_uint) ?*Timer; -extern fn us_timer_ext(timer: ?*Timer) *?*anyopaque; -extern fn us_timer_close(timer: ?*Timer, fallthrough: i32) void; -extern fn us_timer_set(timer: ?*Timer, cb: ?*const fn (*Timer) callconv(.C) void, ms: i32, repeat_ms: i32) void; -extern fn us_timer_loop(t: ?*Timer) ?*Loop; -pub const us_socket_context_options_t = extern struct { - key_file_name: [*c]const u8 = null, - cert_file_name: [*c]const u8 = null, - passphrase: [*c]const u8 = null, - dh_params_file_name: [*c]const u8 = null, - ca_file_name: [*c]const u8 = null, - ssl_ciphers: [*c]const u8 = null, - ssl_prefer_low_memory_usage: i32 = 0, -}; - -pub const us_bun_socket_context_options_t = extern struct { - key_file_name: [*c]const u8 = null, - cert_file_name: [*c]const u8 = null, - passphrase: [*c]const u8 = null, - dh_params_file_name: [*c]const u8 = null, - ca_file_name: [*c]const u8 = null, - ssl_ciphers: [*c]const u8 = null, - ssl_prefer_low_memory_usage: i32 = 0, - key: ?[*]?[*:0]const u8 = null, - key_count: u32 = 0, - cert: ?[*]?[*:0]const u8 = null, - cert_count: u32 = 0, - ca: ?[*]?[*:0]const u8 = null, - ca_count: u32 = 0, - secure_options: u32 = 0, - reject_unauthorized: i32 = 0, - request_cert: i32 = 0, - client_renegotiation_limit: u32 = 3, - client_renegotiation_window: u32 = 600, -}; - -pub const create_bun_socket_error_t = enum(c_int) { - none = 0, - load_ca_file, - invalid_ca_file, - invalid_ca, - - pub fn toJS(this: create_bun_socket_error_t, globalObject: *JSC.JSGlobalObject) JSC.JSValue { - return switch (this) { - .none => brk: { - bun.debugAssert(false); - break :brk .null; - }, - .load_ca_file => globalObject.ERR(.BORINGSSL, "Failed to load CA file", .{}).toJS(), - .invalid_ca_file => globalObject.ERR(.BORINGSSL, "Invalid CA file", .{}).toJS(), - .invalid_ca => globalObject.ERR(.BORINGSSL, "Invalid CA", .{}).toJS(), - }; - } -}; - -pub extern fn create_ssl_context_from_bun_options(options: us_bun_socket_context_options_t, err: *create_bun_socket_error_t) ?*BoringSSL.SSL_CTX; - -pub const us_bun_verify_error_t = extern struct { - error_no: i32 = 0, - code: [*c]const u8 = null, - reason: [*c]const u8 = null, - - pub fn toJS(this: *const us_bun_verify_error_t, globalObject: *JSC.JSGlobalObject) JSC.JSValue { - const code = if (this.code == null) "" else this.code[0..bun.len(this.code)]; - const reason = if (this.reason == null) "" else this.reason[0..bun.len(this.reason)]; - - const fallback = JSC.SystemError{ - .code = bun.String.createUTF8(code), - .message = bun.String.createUTF8(reason), - }; - - return fallback.toErrorInstance(globalObject); - } -}; -pub extern fn us_ssl_socket_verify_error_from_ssl(ssl: *BoringSSL.SSL) us_bun_verify_error_t; - -pub const us_socket_events_t = extern struct { - on_open: ?*const fn (*Socket, i32, [*c]u8, i32) callconv(.C) ?*Socket = null, - on_data: ?*const fn (*Socket, [*c]u8, i32) callconv(.C) ?*Socket = null, - on_writable: ?*const fn (*Socket) callconv(.C) ?*Socket = null, - on_close: ?*const fn (*Socket, i32, ?*anyopaque) callconv(.C) ?*Socket = null, - - on_timeout: ?*const fn (*Socket) callconv(.C) ?*Socket = null, - on_long_timeout: ?*const fn (*Socket) callconv(.C) ?*Socket = null, - on_end: ?*const fn (*Socket) callconv(.C) ?*Socket = null, - on_connect_error: ?*const fn (*Socket, i32) callconv(.C) ?*Socket = null, - on_connect_error_connecting_socket: ?*const fn (*ConnectingSocket, i32) callconv(.C) ?*ConnectingSocket = null, - on_handshake: ?*const fn (*Socket, i32, us_bun_verify_error_t, ?*anyopaque) callconv(.C) void = null, -}; - -pub extern fn us_socket_wrap_with_tls(ssl: i32, s: *Socket, options: us_bun_socket_context_options_t, events: us_socket_events_t, socket_ext_size: i32) ?*Socket; -extern fn us_socket_verify_error(ssl: i32, context: *Socket) us_bun_verify_error_t; -extern fn SocketContextimestamp(ssl: i32, context: ?*SocketContext) c_ushort; -pub extern fn us_socket_context_add_server_name(ssl: i32, context: ?*SocketContext, hostname_pattern: [*c]const u8, options: us_socket_context_options_t, ?*anyopaque) void; -pub extern fn us_socket_context_remove_server_name(ssl: i32, context: ?*SocketContext, hostname_pattern: [*c]const u8) void; -extern fn us_socket_context_on_server_name(ssl: i32, context: ?*SocketContext, cb: ?*const fn (?*SocketContext, [*c]const u8) callconv(.C) void) void; -extern fn us_socket_context_get_native_handle(ssl: i32, context: ?*SocketContext) ?*anyopaque; -pub extern fn us_create_socket_context(ssl: i32, loop: ?*Loop, ext_size: i32, options: us_socket_context_options_t) ?*SocketContext; -pub extern fn us_create_bun_ssl_socket_context(loop: ?*Loop, ext_size: i32, options: us_bun_socket_context_options_t, err: *create_bun_socket_error_t) ?*SocketContext; -pub extern fn us_create_bun_nossl_socket_context(loop: ?*Loop, ext_size: i32) ?*SocketContext; -pub extern fn us_bun_socket_context_add_server_name(ssl: i32, context: ?*SocketContext, hostname_pattern: [*c]const u8, options: us_bun_socket_context_options_t, ?*anyopaque) void; -pub extern fn us_socket_context_free(ssl: i32, context: ?*SocketContext) void; -pub extern fn us_socket_context_ref(ssl: i32, context: ?*SocketContext) void; -pub extern fn us_socket_context_unref(ssl: i32, context: ?*SocketContext) void; -extern fn us_socket_context_on_open(ssl: i32, context: ?*SocketContext, on_open: *const fn (*Socket, i32, [*c]u8, i32) callconv(.C) ?*Socket) void; -extern fn us_socket_context_on_close(ssl: i32, context: ?*SocketContext, on_close: *const fn (*Socket, i32, ?*anyopaque) callconv(.C) ?*Socket) void; -extern fn us_socket_context_on_data(ssl: i32, context: ?*SocketContext, on_data: *const fn (*Socket, [*c]u8, i32) callconv(.C) ?*Socket) void; -extern fn us_socket_context_on_fd(ssl: i32, context: ?*SocketContext, on_fd: *const fn (*Socket, c_int) callconv(.C) ?*Socket) void; -extern fn us_socket_context_on_writable(ssl: i32, context: ?*SocketContext, on_writable: *const fn (*Socket) callconv(.C) ?*Socket) void; - -extern fn us_socket_context_on_handshake(ssl: i32, context: ?*SocketContext, on_handshake: *const fn (*Socket, i32, us_bun_verify_error_t, ?*anyopaque) callconv(.C) void, ?*anyopaque) void; - -extern fn us_socket_context_on_timeout(ssl: i32, context: ?*SocketContext, on_timeout: *const fn (*Socket) callconv(.C) ?*Socket) void; -extern fn us_socket_context_on_long_timeout(ssl: i32, context: ?*SocketContext, on_timeout: *const fn (*Socket) callconv(.C) ?*Socket) void; -extern fn us_socket_context_on_connect_error(ssl: i32, context: ?*SocketContext, on_connect_error: *const fn (*ConnectingSocket, i32) callconv(.C) ?*ConnectingSocket) void; -extern fn us_socket_context_on_socket_connect_error(ssl: i32, context: ?*SocketContext, on_connect_error: *const fn (*Socket, i32) callconv(.C) ?*Socket) void; -extern fn us_socket_context_on_end(ssl: i32, context: ?*SocketContext, on_end: *const fn (*Socket) callconv(.C) ?*Socket) void; -extern fn us_socket_context_ext(ssl: i32, context: ?*SocketContext) ?*anyopaque; - -pub extern fn us_socket_context_listen(ssl: i32, context: ?*SocketContext, host: ?[*:0]const u8, port: i32, options: i32, socket_ext_size: i32, err: *c_int) ?*ListenSocket; -pub extern fn us_socket_context_listen_unix(ssl: i32, context: ?*SocketContext, path: [*:0]const u8, pathlen: usize, options: i32, socket_ext_size: i32, err: *c_int) ?*ListenSocket; -pub extern fn us_socket_context_connect(ssl: i32, context: ?*SocketContext, host: [*:0]const u8, port: i32, options: i32, socket_ext_size: i32, has_dns_resolved: *i32) ?*anyopaque; -pub extern fn us_socket_context_connect_unix(ssl: i32, context: ?*SocketContext, path: [*c]const u8, pathlen: usize, options: i32, socket_ext_size: i32) ?*Socket; -pub extern fn us_socket_is_established(ssl: i32, s: ?*Socket) i32; -pub extern fn us_socket_context_loop(ssl: i32, context: ?*SocketContext) ?*Loop; -pub extern fn us_socket_context_adopt_socket(ssl: i32, context: ?*SocketContext, s: ?*Socket, ext_size: i32) ?*Socket; -pub extern fn us_create_child_socket_context(ssl: i32, context: ?*SocketContext, context_ext_size: i32) ?*SocketContext; - -pub const Poll = opaque { - pub fn create( - loop: *Loop, - comptime Data: type, - file: i32, - val: Data, - fallthrough: bool, - flags: Flags, - ) ?*Poll { - var poll = us_create_poll(loop, @as(i32, @intFromBool(fallthrough)), @sizeOf(Data)); - if (comptime Data != void) { - poll.data(Data).* = val; - } - var flags_int: i32 = 0; - if (flags.read) { - flags_int |= Flags.read_flag; - } - - if (flags.write) { - flags_int |= Flags.write_flag; - } - us_poll_init(poll, file, flags_int); - return poll; - } - - pub fn stop(self: *Poll, loop: *Loop) void { - us_poll_stop(self, loop); - } - - pub fn change(self: *Poll, loop: *Loop, events: i32) void { - us_poll_change(self, loop, events); - } - - pub fn getEvents(self: *Poll) i32 { - return us_poll_events(self); - } - - pub fn data(self: *Poll, comptime Data: type) *Data { - return us_poll_ext(self).?; - } - - pub fn fd(self: *Poll) std.posix.fd_t { - return us_poll_fd(self); - } - - pub fn start(self: *Poll, loop: *Loop, flags: Flags) void { - var flags_int: i32 = 0; - if (flags.read) { - flags_int |= Flags.read_flag; - } - - if (flags.write) { - flags_int |= Flags.write_flag; - } - - us_poll_start(self, loop, flags_int); - } - - pub const Flags = struct { - read: bool = false, - write: bool = false, - - //#define LIBUS_SOCKET_READABLE - pub const read_flag = if (Environment.isLinux) std.os.linux.EPOLL.IN else 1; - // #define LIBUS_SOCKET_WRITABLE - pub const write_flag = if (Environment.isLinux) std.os.linux.EPOLL.OUT else 2; - }; - - pub fn deinit(self: *Poll, loop: *Loop) void { - us_poll_free(self, loop); - } - - // (void* userData, int fd, int events, int error, struct us_poll_t *poll) - pub const CallbackType = *const fn (?*anyopaque, i32, i32, i32, *Poll) callconv(.C) void; - extern fn us_create_poll(loop: ?*Loop, fallthrough: i32, ext_size: c_uint) *Poll; - extern fn us_poll_set(poll: *Poll, events: i32, callback: CallbackType) *Poll; - extern fn us_poll_free(p: ?*Poll, loop: ?*Loop) void; - extern fn us_poll_init(p: ?*Poll, fd: i32, poll_type: i32) void; - extern fn us_poll_start(p: ?*Poll, loop: ?*Loop, events: i32) void; - extern fn us_poll_change(p: ?*Poll, loop: ?*Loop, events: i32) void; - extern fn us_poll_stop(p: ?*Poll, loop: ?*Loop) void; - extern fn us_poll_events(p: ?*Poll) i32; - extern fn us_poll_ext(p: ?*Poll) ?*anyopaque; - extern fn us_poll_fd(p: ?*Poll) std.posix.fd_t; - extern fn us_poll_resize(p: ?*Poll, loop: ?*Loop, ext_size: c_uint) ?*Poll; -}; - -extern fn us_connecting_socket_get_native_handle(ssl: i32, s: ?*ConnectingSocket) ?*anyopaque; - -extern fn us_connecting_socket_timeout(ssl: i32, s: ?*ConnectingSocket, seconds: c_uint) void; -extern fn us_connecting_socket_long_timeout(ssl: i32, s: ?*ConnectingSocket, seconds: c_uint) void; -extern fn us_connecting_socket_ext(ssl: i32, s: ?*ConnectingSocket) *anyopaque; -extern fn us_connecting_socket_context(ssl: i32, s: ?*ConnectingSocket) ?*SocketContext; -extern fn us_connecting_socket_shutdown(ssl: i32, s: ?*ConnectingSocket) void; -extern fn us_connecting_socket_is_closed(ssl: i32, s: ?*ConnectingSocket) i32; -extern fn us_connecting_socket_close(ssl: i32, s: ?*ConnectingSocket) void; -extern fn us_connecting_socket_shutdown_read(ssl: i32, s: ?*ConnectingSocket) void; -extern fn us_connecting_socket_is_shut_down(ssl: i32, s: ?*ConnectingSocket) i32; -extern fn us_connecting_socket_get_error(ssl: i32, s: ?*ConnectingSocket) i32; - -pub extern fn us_connecting_socket_get_loop(s: *ConnectingSocket) *Loop; - -pub const uws_app_s = opaque {}; -pub const uws_req_s = opaque {}; -pub const uws_header_iterator_s = opaque {}; -pub const uws_app_t = uws_app_s; - -pub const uws_socket_context_s = opaque {}; -pub const uws_socket_context_t = uws_socket_context_s; -pub const AnyWebSocket = union(enum) { - ssl: *NewApp(true).WebSocket, - tcp: *NewApp(false).WebSocket, - - pub fn raw(this: AnyWebSocket) *RawWebSocket { - return switch (this) { - .ssl => this.ssl.raw(), - .tcp => this.tcp.raw(), - }; - } - pub fn as(this: AnyWebSocket, comptime Type: type) ?*Type { - @setRuntimeSafety(false); - return switch (this) { - .ssl => this.ssl.as(Type), - .tcp => this.tcp.as(Type), - }; - } - - pub fn memoryCost(this: AnyWebSocket) usize { - return switch (this) { - .ssl => this.ssl.memoryCost(), - .tcp => this.tcp.memoryCost(), - }; - } - - pub fn close(this: AnyWebSocket) void { - const ssl_flag = @intFromBool(this == .ssl); - return uws_ws_close(ssl_flag, this.raw()); - } - - pub fn send(this: AnyWebSocket, message: []const u8, opcode: Opcode, compress: bool, fin: bool) SendStatus { - return switch (this) { - .ssl => uws_ws_send_with_options(1, this.ssl.raw(), message.ptr, message.len, opcode, compress, fin), - .tcp => uws_ws_send_with_options(0, this.tcp.raw(), message.ptr, message.len, opcode, compress, fin), - }; - } - pub fn sendLastFragment(this: AnyWebSocket, message: []const u8, compress: bool) SendStatus { - switch (this) { - .tcp => return uws_ws_send_last_fragment(0, this.raw(), message.ptr, message.len, compress), - .ssl => return uws_ws_send_last_fragment(1, this.raw(), message.ptr, message.len, compress), - } - } - pub fn end(this: AnyWebSocket, code: i32, message: []const u8) void { - switch (this) { - .tcp => uws_ws_end(0, this.tcp.raw(), code, message.ptr, message.len), - .ssl => uws_ws_end(1, this.ssl.raw(), code, message.ptr, message.len), - } - } - pub fn cork(this: AnyWebSocket, ctx: anytype, comptime callback: anytype) void { - const ContextType = @TypeOf(ctx); - const Wrapper = struct { - pub fn wrap(user_data: ?*anyopaque) callconv(.C) void { - @call(bun.callmod_inline, callback, .{bun.cast(ContextType, user_data.?)}); - } - }; - - switch (this) { - .ssl => uws_ws_cork(1, this.raw(), Wrapper.wrap, ctx), - .tcp => uws_ws_cork(0, this.raw(), Wrapper.wrap, ctx), - } - } - pub fn subscribe(this: AnyWebSocket, topic: []const u8) bool { - return switch (this) { - .ssl => uws_ws_subscribe(1, this.ssl.raw(), topic.ptr, topic.len), - .tcp => uws_ws_subscribe(0, this.tcp.raw(), topic.ptr, topic.len), - }; - } - pub fn unsubscribe(this: AnyWebSocket, topic: []const u8) bool { - return switch (this) { - .ssl => uws_ws_unsubscribe(1, this.raw(), topic.ptr, topic.len), - .tcp => uws_ws_unsubscribe(0, this.raw(), topic.ptr, topic.len), - }; - } - pub fn isSubscribed(this: AnyWebSocket, topic: []const u8) bool { - return switch (this) { - .ssl => uws_ws_is_subscribed(1, this.raw(), topic.ptr, topic.len), - .tcp => uws_ws_is_subscribed(0, this.raw(), topic.ptr, topic.len), - }; - } - // pub fn iterateTopics(this: AnyWebSocket) { - // return uws_ws_iterate_topics(ssl_flag, this.raw(), callback: ?*const fn ([*c]const u8, usize, ?*anyopaque) callconv(.C) void, user_data: ?*anyopaque) void; - // } - pub fn publish(this: AnyWebSocket, topic: []const u8, message: []const u8, opcode: Opcode, compress: bool) bool { - return switch (this) { - .ssl => uws_ws_publish_with_options(1, this.ssl.raw(), topic.ptr, topic.len, message.ptr, message.len, opcode, compress), - .tcp => uws_ws_publish_with_options(0, this.tcp.raw(), topic.ptr, topic.len, message.ptr, message.len, opcode, compress), - }; - } - pub fn publishWithOptions(ssl: bool, app: *anyopaque, topic: []const u8, message: []const u8, opcode: Opcode, compress: bool) bool { - return uws_publish( - @intFromBool(ssl), - @as(*uws_app_t, @ptrCast(app)), - topic.ptr, - topic.len, - message.ptr, - message.len, - opcode, - compress, - ); - } - pub fn getBufferedAmount(this: AnyWebSocket) usize { - return switch (this) { - .ssl => uws_ws_get_buffered_amount(1, this.ssl.raw()), - .tcp => uws_ws_get_buffered_amount(0, this.tcp.raw()), - }; - } - - pub fn getRemoteAddress(this: AnyWebSocket, buf: []u8) []u8 { - return switch (this) { - .ssl => this.ssl.getRemoteAddress(buf), - .tcp => this.tcp.getRemoteAddress(buf), - }; - } -}; - -pub const RawWebSocket = opaque { - pub fn memoryCost(this: *RawWebSocket, ssl_flag: i32) usize { - return uws_ws_memory_cost(ssl_flag, this); - } - - extern fn uws_ws_memory_cost(ssl: i32, ws: *RawWebSocket) usize; -}; - -pub const uws_websocket_handler = ?*const fn (*RawWebSocket) callconv(.C) void; -pub const uws_websocket_message_handler = ?*const fn (*RawWebSocket, [*c]const u8, usize, Opcode) callconv(.C) void; -pub const uws_websocket_close_handler = ?*const fn (*RawWebSocket, i32, [*c]const u8, usize) callconv(.C) void; -pub const uws_websocket_upgrade_handler = ?*const fn (*anyopaque, *uws_res, *Request, *uws_socket_context_t, usize) callconv(.C) void; - -pub const uws_websocket_ping_pong_handler = ?*const fn (*RawWebSocket, [*c]const u8, usize) callconv(.C) void; - -pub const WebSocketBehavior = extern struct { - compression: uws_compress_options_t = 0, - maxPayloadLength: c_uint = std.math.maxInt(u32), - idleTimeout: c_ushort = 120, - maxBackpressure: c_uint = 1024 * 1024, - closeOnBackpressureLimit: bool = false, - resetIdleTimeoutOnSend: bool = true, - sendPingsAutomatically: bool = true, - maxLifetime: c_ushort = 0, - upgrade: uws_websocket_upgrade_handler = null, - open: uws_websocket_handler = null, - message: uws_websocket_message_handler = null, - drain: uws_websocket_handler = null, - ping: uws_websocket_ping_pong_handler = null, - pong: uws_websocket_ping_pong_handler = null, - close: uws_websocket_close_handler = null, - - pub fn Wrap( - comptime ServerType: type, - comptime Type: type, - comptime ssl: bool, - ) type { - return extern struct { - const is_ssl = ssl; - const WebSocket = NewApp(is_ssl).WebSocket; - const Server = ServerType; - - const active_field_name = if (is_ssl) "ssl" else "tcp"; - - pub fn onOpen(raw_ws: *RawWebSocket) callconv(.C) void { - const ws = @unionInit(AnyWebSocket, active_field_name, @as(*WebSocket, @ptrCast(raw_ws))); - const this = ws.as(Type).?; - @call(bun.callmod_inline, Type.onOpen, .{ - this, - ws, - }); - } - - pub fn onMessage(raw_ws: *RawWebSocket, message: [*c]const u8, length: usize, opcode: Opcode) callconv(.C) void { - const ws = @unionInit(AnyWebSocket, active_field_name, @as(*WebSocket, @ptrCast(raw_ws))); - const this = ws.as(Type).?; - @call(bun.callmod_inline, Type.onMessage, .{ - this, - ws, - if (length > 0) message[0..length] else "", - opcode, - }); - } - - pub fn onDrain(raw_ws: *RawWebSocket) callconv(.C) void { - const ws = @unionInit(AnyWebSocket, active_field_name, @as(*WebSocket, @ptrCast(raw_ws))); - const this = ws.as(Type).?; - @call(bun.callmod_inline, Type.onDrain, .{ - this, - ws, - }); - } - - pub fn onPing(raw_ws: *RawWebSocket, message: [*c]const u8, length: usize) callconv(.C) void { - const ws = @unionInit(AnyWebSocket, active_field_name, @as(*WebSocket, @ptrCast(raw_ws))); - const this = ws.as(Type).?; - @call(bun.callmod_inline, Type.onPing, .{ - this, - ws, - if (length > 0) message[0..length] else "", - }); - } - - pub fn onPong(raw_ws: *RawWebSocket, message: [*c]const u8, length: usize) callconv(.C) void { - const ws = @unionInit(AnyWebSocket, active_field_name, @as(*WebSocket, @ptrCast(raw_ws))); - const this = ws.as(Type).?; - @call(bun.callmod_inline, Type.onPong, .{ - this, - ws, - if (length > 0) message[0..length] else "", - }); - } - - pub fn onClose(raw_ws: *RawWebSocket, code: i32, message: [*c]const u8, length: usize) callconv(.C) void { - const ws = @unionInit(AnyWebSocket, active_field_name, @as(*WebSocket, @ptrCast(raw_ws))); - const this = ws.as(Type).?; - @call(bun.callmod_inline, Type.onClose, .{ - this, - ws, - code, - if (length > 0 and message != null) message[0..length] else "", - }); - } - - pub fn onUpgrade(ptr: *anyopaque, res: *uws_res, req: *Request, context: *uws_socket_context_t, id: usize) callconv(.C) void { - @call(bun.callmod_inline, Server.onWebSocketUpgrade, .{ - bun.cast(*Server, ptr), - @as(*NewApp(is_ssl).Response, @ptrCast(res)), - req, - context, - id, - }); - } - - pub fn apply(behavior: WebSocketBehavior) WebSocketBehavior { - return .{ - .compression = behavior.compression, - .maxPayloadLength = behavior.maxPayloadLength, - .idleTimeout = behavior.idleTimeout, - .maxBackpressure = behavior.maxBackpressure, - .closeOnBackpressureLimit = behavior.closeOnBackpressureLimit, - .resetIdleTimeoutOnSend = behavior.resetIdleTimeoutOnSend, - .sendPingsAutomatically = behavior.sendPingsAutomatically, - .maxLifetime = behavior.maxLifetime, - .upgrade = onUpgrade, - .open = onOpen, - .message = if (@hasDecl(Type, "onMessage")) onMessage else null, - .drain = if (@hasDecl(Type, "onDrain")) onDrain else null, - .ping = if (@hasDecl(Type, "onPing")) onPing else null, - .pong = if (@hasDecl(Type, "onPong")) onPong else null, - .close = onClose, - }; - } - }; - } -}; -pub const uws_listen_handler = ?*const fn (?*ListenSocket, ?*anyopaque) callconv(.C) void; -pub const uws_method_handler = ?*const fn (*uws_res, *Request, ?*anyopaque) callconv(.C) void; -pub const uws_filter_handler = ?*const fn (*uws_res, i32, ?*anyopaque) callconv(.C) void; -pub const uws_missing_server_handler = ?*const fn ([*c]const u8, ?*anyopaque) callconv(.C) void; - -pub const Request = opaque { - pub fn isAncient(req: *Request) bool { - return uws_req_is_ancient(req); - } - pub fn getYield(req: *Request) bool { - return uws_req_get_yield(req); - } - pub fn setYield(req: *Request, yield: bool) void { - uws_req_set_yield(req, yield); - } - pub fn url(req: *Request) []const u8 { - var ptr: [*]const u8 = undefined; - return ptr[0..req.uws_req_get_url(&ptr)]; - } - pub fn method(req: *Request) []const u8 { - var ptr: [*]const u8 = undefined; - return ptr[0..req.uws_req_get_method(&ptr)]; - } - pub fn header(req: *Request, name: []const u8) ?[]const u8 { - bun.assert(std.ascii.isLower(name[0])); - - var ptr: [*]const u8 = undefined; - const len = req.uws_req_get_header(name.ptr, name.len, &ptr); - if (len == 0) return null; - return ptr[0..len]; - } - pub fn query(req: *Request, name: []const u8) []const u8 { - var ptr: [*]const u8 = undefined; - return ptr[0..req.uws_req_get_query(name.ptr, name.len, &ptr)]; - } - pub fn parameter(req: *Request, index: u16) []const u8 { - var ptr: [*]const u8 = undefined; - return ptr[0..req.uws_req_get_parameter(@as(c_ushort, @intCast(index)), &ptr)]; - } - - extern fn uws_req_is_ancient(res: *Request) bool; - extern fn uws_req_get_yield(res: *Request) bool; - extern fn uws_req_set_yield(res: *Request, yield: bool) void; - extern fn uws_req_get_url(res: *Request, dest: *[*]const u8) usize; - extern fn uws_req_get_method(res: *Request, dest: *[*]const u8) usize; - extern fn uws_req_get_header(res: *Request, lower_case_header: [*]const u8, lower_case_header_length: usize, dest: *[*]const u8) usize; - extern fn uws_req_get_query(res: *Request, key: [*c]const u8, key_length: usize, dest: *[*]const u8) usize; - extern fn uws_req_get_parameter(res: *Request, index: c_ushort, dest: *[*]const u8) usize; -}; - -pub const ListenSocket = opaque { - pub fn close(this: *ListenSocket, ssl: bool) void { - us_listen_socket_close(@intFromBool(ssl), this); - } - pub fn getLocalAddress(this: *ListenSocket, ssl: bool, buf: []u8) ![]const u8 { - return this.getSocket().localAddress(ssl, buf); - } - pub fn getLocalPort(this: *ListenSocket, ssl: bool) i32 { - return this.getSocket().localPort(ssl); - } - pub fn getSocket(this: *ListenSocket) *uws.Socket { - return @ptrCast(this); - } -}; -extern fn us_listen_socket_close(ssl: i32, ls: *ListenSocket) void; -extern fn uws_app_close(ssl: i32, app: *uws_app_s) void; -extern fn us_socket_context_close(ssl: i32, ctx: *anyopaque) void; -extern fn uws_app_set_on_clienterror(ssl: c_int, app: *uws_app_s, handler: *const fn (*anyopaque, c_int, *Socket, u8, ?[*]u8, c_int) callconv(.C) void, user_data: *anyopaque) void; - -pub const SocketAddress = struct { - ip: []const u8, - port: i32, - is_ipv6: bool, -}; - -pub const AnyResponse = union(enum) { - SSL: *NewApp(true).Response, - TCP: *NewApp(false).Response, - - pub fn socket(this: AnyResponse) *uws_res { - return switch (this) { - inline else => |resp| resp.downcast(), - }; - } - pub fn getRemoteSocketInfo(this: AnyResponse) ?SocketAddress { - return switch (this) { - inline else => |resp| resp.getRemoteSocketInfo(), - }; - } - pub fn flushHeaders(this: AnyResponse) void { - return switch (this) { - inline else => |resp| resp.flushHeaders(), - }; - } - pub fn getWriteOffset(this: AnyResponse) u64 { - return switch (this) { - inline else => |resp| resp.getWriteOffset(), - }; - } - - pub fn getBufferedAmount(this: AnyResponse) u64 { - return switch (this) { - inline else => |resp| resp.getBufferedAmount(), - }; - } - - pub fn writeContinue(this: AnyResponse) void { - return switch (this) { - inline else => |resp| resp.writeContinue(), - }; - } - - pub fn state(this: AnyResponse) State { - return switch (this) { - inline else => |resp| resp.state(), - }; - } - - pub inline fn init(response: anytype) AnyResponse { - return switch (@TypeOf(response)) { - *NewApp(true).Response => .{ .SSL = response }, - *NewApp(false).Response => .{ .TCP = response }, - else => @compileError(unreachable), - }; - } - - pub fn timeout(this: AnyResponse, seconds: u8) void { - switch (this) { - inline else => |resp| resp.timeout(seconds), - } - } - - pub fn onData(this: AnyResponse, comptime UserDataType: type, comptime handler: fn (UserDataType, []const u8, bool) void, optional_data: UserDataType) void { - return switch (this) { - inline .SSL, .TCP => |resp, ssl| resp.onData(UserDataType, struct { - pub fn onDataCallback(user_data: UserDataType, _: *uws.NewApp(ssl == .SSL).Response, data: []const u8, last: bool) void { - @call(.always_inline, handler, .{ user_data, data, last }); - } - }.onDataCallback, optional_data), - }; - } - - pub fn writeStatus(this: AnyResponse, status: []const u8) void { - return switch (this) { - inline else => |resp| resp.writeStatus(status), - }; - } - - pub fn writeHeader(this: AnyResponse, key: []const u8, value: []const u8) void { - return switch (this) { - inline else => |resp| resp.writeHeader(key, value), - }; - } - - pub fn write(this: AnyResponse, data: []const u8) WriteResult { - return switch (this) { - inline else => |resp| resp.write(data), - }; - } - - pub fn end(this: AnyResponse, data: []const u8, close_connection: bool) void { - return switch (this) { - inline else => |resp| resp.end(data, close_connection), - }; - } - - pub fn shouldCloseConnection(this: AnyResponse) bool { - return switch (this) { - inline else => |resp| resp.shouldCloseConnection(), - }; - } - - pub fn tryEnd(this: AnyResponse, data: []const u8, total_size: usize, close_connection: bool) bool { - return switch (this) { - inline else => |resp| resp.tryEnd(data, total_size, close_connection), - }; - } - - pub fn pause(this: AnyResponse) void { - return switch (this) { - inline else => |resp| resp.pause(), - }; - } - - pub fn @"resume"(this: AnyResponse) void { - return switch (this) { - inline else => |resp| resp.@"resume"(), - }; - } - - pub fn writeHeaderInt(this: AnyResponse, key: []const u8, value: u64) void { - return switch (this) { - inline else => |resp| resp.writeHeaderInt(key, value), - }; - } - - pub fn endWithoutBody(this: AnyResponse, close_connection: bool) void { - return switch (this) { - inline else => |resp| resp.endWithoutBody(close_connection), - }; - } - - pub fn onWritable(this: AnyResponse, comptime UserDataType: type, comptime handler: fn (UserDataType, u64, AnyResponse) bool, optional_data: UserDataType) void { - const wrapper = struct { - pub fn ssl_handler(user_data: UserDataType, offset: u64, resp: *NewApp(true).Response) bool { - return handler(user_data, offset, .{ .SSL = resp }); - } - - pub fn tcp_handler(user_data: UserDataType, offset: u64, resp: *NewApp(false).Response) bool { - return handler(user_data, offset, .{ .TCP = resp }); - } - }; - return switch (this) { - .SSL => |resp| resp.onWritable(UserDataType, wrapper.ssl_handler, optional_data), - .TCP => |resp| resp.onWritable(UserDataType, wrapper.tcp_handler, optional_data), - }; - } - - pub fn onTimeout(this: AnyResponse, comptime UserDataType: type, comptime handler: fn (UserDataType, AnyResponse) void, optional_data: UserDataType) void { - const wrapper = struct { - pub fn ssl_handler(user_data: UserDataType, resp: *NewApp(true).Response) void { - handler(user_data, .{ .SSL = resp }); - } - pub fn tcp_handler(user_data: UserDataType, resp: *NewApp(false).Response) void { - handler(user_data, .{ .TCP = resp }); - } - }; - - return switch (this) { - .SSL => |resp| resp.onTimeout(UserDataType, wrapper.ssl_handler, optional_data), - .TCP => |resp| resp.onTimeout(UserDataType, wrapper.tcp_handler, optional_data), - }; - } - - pub fn onAborted(this: AnyResponse, comptime UserDataType: type, comptime handler: fn (UserDataType, AnyResponse) void, optional_data: UserDataType) void { - const wrapper = struct { - pub fn ssl_handler(user_data: UserDataType, resp: *NewApp(true).Response) void { - handler(user_data, .{ .SSL = resp }); - } - pub fn tcp_handler(user_data: UserDataType, resp: *NewApp(false).Response) void { - handler(user_data, .{ .TCP = resp }); - } - }; - return switch (this) { - .SSL => |resp| resp.onAborted(UserDataType, wrapper.ssl_handler, optional_data), - .TCP => |resp| resp.onAborted(UserDataType, wrapper.tcp_handler, optional_data), - }; - } - - pub fn clearAborted(this: AnyResponse) void { - return switch (this) { - inline else => |resp| resp.clearAborted(), - }; - } - pub fn clearTimeout(this: AnyResponse) void { - return switch (this) { - inline else => |resp| resp.clearTimeout(), - }; - } - - pub fn clearOnWritable(this: AnyResponse) void { - return switch (this) { - inline else => |resp| resp.clearOnWritable(), - }; - } - - pub fn clearOnData(this: AnyResponse) void { - return switch (this) { - inline else => |resp| resp.clearOnData(), - }; - } - - pub fn endStream(this: AnyResponse, close_connection: bool) void { - return switch (this) { - inline else => |resp| resp.endStream(close_connection), - }; - } - - pub fn corked(this: AnyResponse, comptime handler: anytype, args_tuple: anytype) void { - return switch (this) { - inline else => |resp| resp.corked(handler, args_tuple), - }; - } - - pub fn runCorkedWithType(this: AnyResponse, comptime UserDataType: type, comptime handler: fn (UserDataType) void, optional_data: UserDataType) void { - return switch (this) { - inline else => |resp| resp.runCorkedWithType(UserDataType, handler, optional_data), - }; - } - - pub fn upgrade( - this: AnyResponse, - comptime Data: type, - data: Data, - sec_web_socket_key: []const u8, - sec_web_socket_protocol: []const u8, - sec_web_socket_extensions: []const u8, - ctx: ?*uws_socket_context_t, - ) *Socket { - return switch (this) { - inline else => |resp| resp.upgrade(Data, data, sec_web_socket_key, sec_web_socket_protocol, sec_web_socket_extensions, ctx), - }; - } -}; -pub fn NewApp(comptime ssl: bool) type { - // TODO: change to `opaque` when https://github.com/ziglang/zig/issues/22869 is fixed - return struct { - pub const is_ssl = ssl; - const ssl_flag: i32 = @intFromBool(ssl); - const ThisApp = @This(); - - pub fn close(this: *ThisApp) void { - return uws_app_close(ssl_flag, @as(*uws_app_s, @ptrCast(this))); - } - - pub fn create(opts: us_bun_socket_context_options_t) ?*ThisApp { - return @ptrCast(uws_create_app(ssl_flag, opts)); - } - pub fn destroy(app: *ThisApp) void { - return uws_app_destroy(ssl_flag, @as(*uws_app_s, @ptrCast(app))); - } - - pub fn setFlags(this: *ThisApp, require_host_header: bool, use_strict_method_validation: bool) void { - return uws_app_set_flags(ssl_flag, @as(*uws_app_t, @ptrCast(this)), require_host_header, use_strict_method_validation); - } - - pub fn setMaxHTTPHeaderSize(this: *ThisApp, max_header_size: u64) void { - return uws_app_set_max_http_header_size(ssl_flag, @as(*uws_app_t, @ptrCast(this)), max_header_size); - } - - pub fn clearRoutes(app: *ThisApp) void { - return uws_app_clear_routes(ssl_flag, @as(*uws_app_t, @ptrCast(app))); - } - - fn RouteHandler(comptime UserDataType: type, comptime handler: fn (UserDataType, *Request, *Response) void) type { - return struct { - pub fn handle(res: *uws_res, req: *Request, user_data: ?*anyopaque) callconv(.C) void { - if (comptime UserDataType == void) { - return @call( - .always_inline, - handler, - .{ - {}, - req, - @as(*Response, @ptrCast(@alignCast(res))), - }, - ); - } else { - return @call( - .always_inline, - handler, - .{ - @as(UserDataType, @ptrCast(@alignCast(user_data.?))), - req, - @as(*Response, @ptrCast(@alignCast(res))), - }, - ); - } - } - }; - } - - pub const ListenSocket = opaque { - pub inline fn close(this: *ThisApp.ListenSocket) void { - return us_listen_socket_close(ssl_flag, @as(*uws.ListenSocket, @ptrCast(this))); - } - pub inline fn getLocalPort(this: *ThisApp.ListenSocket) i32 { - const self: *uws.Socket = @ptrCast(this); - return self.localPort(ssl); - } - - pub fn socket(this: *ThisApp.ListenSocket) NewSocketHandler(ssl) { - return NewSocketHandler(ssl).from(@ptrCast(this)); - } - }; - - pub fn get( - app: *ThisApp, - pattern: []const u8, - comptime UserDataType: type, - user_data: UserDataType, - comptime handler: (fn (UserDataType, *Request, *Response) void), - ) void { - uws_app_get(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); - } - pub fn post( - app: *ThisApp, - pattern: []const u8, - comptime UserDataType: type, - user_data: UserDataType, - comptime handler: (fn (UserDataType, *Request, *Response) void), - ) void { - uws_app_post(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); - } - pub fn options( - app: *ThisApp, - pattern: []const u8, - comptime UserDataType: type, - user_data: UserDataType, - comptime handler: (fn (UserDataType, *Request, *Response) void), - ) void { - uws_app_options(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); - } - pub fn delete( - app: *ThisApp, - pattern: []const u8, - comptime UserDataType: type, - user_data: UserDataType, - comptime handler: (fn (UserDataType, *Request, *Response) void), - ) void { - uws_app_delete(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); - } - pub fn patch( - app: *ThisApp, - pattern: []const u8, - comptime UserDataType: type, - user_data: UserDataType, - comptime handler: (fn (UserDataType, *Request, *Response) void), - ) void { - uws_app_patch(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); - } - pub fn put( - app: *ThisApp, - pattern: []const u8, - comptime UserDataType: type, - user_data: UserDataType, - comptime handler: (fn (UserDataType, *Request, *Response) void), - ) void { - uws_app_put(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); - } - pub fn head( - app: *ThisApp, - pattern: []const u8, - comptime UserDataType: type, - user_data: UserDataType, - comptime handler: (fn (UserDataType, *Request, *Response) void), - ) void { - uws_app_head(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); - } - pub fn connect( - app: *ThisApp, - pattern: []const u8, - comptime UserDataType: type, - user_data: UserDataType, - comptime handler: (fn (UserDataType, *Request, *Response) void), - ) void { - uws_app_connect(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); - } - pub fn trace( - app: *ThisApp, - pattern: []const u8, - comptime UserDataType: type, - user_data: UserDataType, - comptime handler: (fn (UserDataType, *Request, *Response) void), - ) void { - uws_app_trace(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); - } - pub fn method( - app: *ThisApp, - method_: bun.http.Method, - pattern: []const u8, - comptime UserDataType: type, - user_data: UserDataType, - comptime handler: (fn (UserDataType, *Request, *Response) void), - ) void { - switch (method_) { - .GET => app.get(pattern, UserDataType, user_data, handler), - .POST => app.post(pattern, UserDataType, user_data, handler), - .PUT => app.put(pattern, UserDataType, user_data, handler), - .DELETE => app.delete(pattern, UserDataType, user_data, handler), - .PATCH => app.patch(pattern, UserDataType, user_data, handler), - .OPTIONS => app.options(pattern, UserDataType, user_data, handler), - .HEAD => app.head(pattern, UserDataType, user_data, handler), - .CONNECT => app.connect(pattern, UserDataType, user_data, handler), - .TRACE => app.trace(pattern, UserDataType, user_data, handler), - else => {}, - } - } - pub fn any( - app: *ThisApp, - pattern: []const u8, - comptime UserDataType: type, - user_data: UserDataType, - comptime handler: (fn (UserDataType, *Request, *Response) void), - ) void { - uws_app_any(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); - } - pub fn domain(app: *ThisApp, pattern: [:0]const u8) void { - uws_app_domain(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern); - } - pub fn run(app: *ThisApp) void { - return uws_app_run(ssl_flag, @as(*uws_app_t, @ptrCast(app))); - } - pub fn listen( - app: *ThisApp, - port: i32, - comptime UserData: type, - user_data: UserData, - comptime handler: fn (UserData, ?*ThisApp.ListenSocket, uws_app_listen_config_t) void, - ) void { - const Wrapper = struct { - pub fn handle(socket: ?*uws.ListenSocket, conf: uws_app_listen_config_t, data: ?*anyopaque) callconv(.C) void { - if (comptime UserData == void) { - @call(bun.callmod_inline, handler, .{ {}, @as(?*ThisApp.ListenSocket, @ptrCast(socket)), conf }); - } else { - @call(bun.callmod_inline, handler, .{ - @as(UserData, @ptrCast(@alignCast(data.?))), - @as(?*ThisApp.ListenSocket, @ptrCast(socket)), - conf, - }); - } - } - }; - return uws_app_listen(ssl_flag, @as(*uws_app_t, @ptrCast(app)), port, Wrapper.handle, user_data); - } - - pub fn onClientError( - app: *ThisApp, - comptime UserData: type, - user_data: UserData, - comptime handler: fn (data: UserData, socket: *Socket, error_code: u8, rawPacket: []const u8) void, - ) void { - const Wrapper = struct { - pub fn handle(data: *anyopaque, _: c_int, socket: *Socket, error_code: u8, raw_packet: ?[*]u8, raw_packet_length: c_int) callconv(.C) void { - @call(bun.callmod_inline, handler, .{ - @as(UserData, @ptrCast(@alignCast(data))), - socket, - error_code, - if (raw_packet) |bytes| bytes[0..(@max(raw_packet_length, 0))] else "", - }); - } - }; - return uws_app_set_on_clienterror(ssl_flag, @ptrCast(app), Wrapper.handle, @ptrCast(user_data)); - } - - pub fn listenWithConfig( - app: *ThisApp, - comptime UserData: type, - user_data: UserData, - comptime handler: fn (UserData, ?*ThisApp.ListenSocket) void, - config: uws_app_listen_config_t, - ) void { - const Wrapper = struct { - pub fn handle(socket: ?*uws.ListenSocket, data: ?*anyopaque) callconv(.C) void { - if (comptime UserData == void) { - @call(bun.callmod_inline, handler, .{ {}, @as(?*ThisApp.ListenSocket, @ptrCast(socket)) }); - } else { - @call(bun.callmod_inline, handler, .{ - @as(UserData, @ptrCast(@alignCast(data.?))), - @as(?*ThisApp.ListenSocket, @ptrCast(socket)), - }); - } - } - }; - return uws_app_listen_with_config(ssl_flag, @as(*uws_app_t, @ptrCast(app)), config.host, @as(u16, @intCast(config.port)), config.options, Wrapper.handle, user_data); - } - - pub fn listenOnUnixSocket( - app: *ThisApp, - comptime UserData: type, - user_data: UserData, - comptime handler: fn (UserData, ?*ThisApp.ListenSocket) void, - domain_name: [:0]const u8, - flags: i32, - ) void { - const Wrapper = struct { - pub fn handle(socket: ?*uws.ListenSocket, _: [*:0]const u8, _: i32, data: *anyopaque) callconv(.C) void { - if (comptime UserData == void) { - @call(bun.callmod_inline, handler, .{ {}, @as(?*ThisApp.ListenSocket, @ptrCast(socket)) }); - } else { - @call(bun.callmod_inline, handler, .{ - @as(UserData, @ptrCast(@alignCast(data))), - @as(?*ThisApp.ListenSocket, @ptrCast(socket)), - }); - } - } - }; - return uws_app_listen_domain_with_options( - ssl_flag, - @as(*uws_app_t, @ptrCast(app)), - domain_name.ptr, - domain_name.len, - flags, - Wrapper.handle, - user_data, - ); - } - - pub fn constructorFailed(app: *ThisApp) bool { - return uws_constructor_failed(ssl_flag, app); - } - pub fn numSubscribers(app: *ThisApp, topic: []const u8) u32 { - return uws_num_subscribers(ssl_flag, @as(*uws_app_t, @ptrCast(app)), topic.ptr, topic.len); - } - pub fn publish(app: *ThisApp, topic: []const u8, message: []const u8, opcode: Opcode, compress: bool) bool { - return uws_publish(ssl_flag, @as(*uws_app_t, @ptrCast(app)), topic.ptr, topic.len, message.ptr, message.len, opcode, compress); - } - pub fn getNativeHandle(app: *ThisApp) ?*anyopaque { - return uws_get_native_handle(ssl_flag, app); - } - pub fn removeServerName(app: *ThisApp, hostname_pattern: [*:0]const u8) void { - return uws_remove_server_name(ssl_flag, @as(*uws_app_t, @ptrCast(app)), hostname_pattern); - } - pub fn addServerName(app: *ThisApp, hostname_pattern: [*:0]const u8) void { - return uws_add_server_name(ssl_flag, @as(*uws_app_t, @ptrCast(app)), hostname_pattern); - } - pub fn addServerNameWithOptions(app: *ThisApp, hostname_pattern: [*:0]const u8, opts: us_bun_socket_context_options_t) !void { - if (uws_add_server_name_with_options(ssl_flag, @as(*uws_app_t, @ptrCast(app)), hostname_pattern, opts) != 0) { - return error.FailedToAddServerName; - } - } - pub fn missingServerName(app: *ThisApp, handler: uws_missing_server_handler, user_data: ?*anyopaque) void { - return uws_missing_server_name(ssl_flag, @as(*uws_app_t, @ptrCast(app)), handler, user_data); - } - pub fn filter(app: *ThisApp, handler: uws_filter_handler, user_data: ?*anyopaque) void { - return uws_filter(ssl_flag, @as(*uws_app_t, @ptrCast(app)), handler, user_data); - } - pub fn ws(app: *ThisApp, pattern: []const u8, ctx: *anyopaque, id: usize, behavior_: WebSocketBehavior) void { - var behavior = behavior_; - uws_ws(ssl_flag, @as(*uws_app_t, @ptrCast(app)), ctx, pattern.ptr, pattern.len, id, &behavior); - } - - pub const Response = opaque { - pub inline fn castRes(res: *uws_res) *Response { - return @as(*Response, @ptrCast(@alignCast(res))); - } - - pub inline fn downcast(res: *Response) *uws_res { - return @as(*uws_res, @ptrCast(@alignCast(res))); - } - - pub fn end(res: *Response, data: []const u8, close_connection: bool) void { - uws_res_end(ssl_flag, res.downcast(), data.ptr, data.len, close_connection); - } - - pub fn tryEnd(res: *Response, data: []const u8, total: usize, close_: bool) bool { - return uws_res_try_end(ssl_flag, res.downcast(), data.ptr, data.len, total, close_); - } - - pub fn flushHeaders(res: *Response) void { - uws_res_flush_headers(ssl_flag, res.downcast()); - } - - pub fn state(res: *const Response) State { - return uws_res_state(ssl_flag, @as(*const uws_res, @ptrCast(@alignCast(res)))); - } - - pub fn shouldCloseConnection(this: *const Response) bool { - return this.state().isHttpConnectionClose(); - } - - pub fn prepareForSendfile(res: *Response) void { - return uws_res_prepare_for_sendfile(ssl_flag, res.downcast()); - } - - pub fn uncork(_: *Response) void { - // uws_res_uncork( - // ssl_flag, - // res.downcast(), - // ); - } - pub fn pause(res: *Response) void { - uws_res_pause(ssl_flag, res.downcast()); - } - pub fn @"resume"(res: *Response) void { - uws_res_resume(ssl_flag, res.downcast()); - } - pub fn writeContinue(res: *Response) void { - uws_res_write_continue(ssl_flag, res.downcast()); - } - pub fn writeStatus(res: *Response, status: []const u8) void { - uws_res_write_status(ssl_flag, res.downcast(), status.ptr, status.len); - } - pub fn writeHeader(res: *Response, key: []const u8, value: []const u8) void { - uws_res_write_header(ssl_flag, res.downcast(), key.ptr, key.len, value.ptr, value.len); - } - pub fn writeHeaderInt(res: *Response, key: []const u8, value: u64) void { - uws_res_write_header_int(ssl_flag, res.downcast(), key.ptr, key.len, value); - } - pub fn endWithoutBody(res: *Response, close_connection: bool) void { - uws_res_end_without_body(ssl_flag, res.downcast(), close_connection); - } - pub fn endSendFile(res: *Response, write_offset: u64, close_connection: bool) void { - uws_res_end_sendfile(ssl_flag, res.downcast(), write_offset, close_connection); - } - pub fn timeout(res: *Response, seconds: u8) void { - uws_res_timeout(ssl_flag, res.downcast(), seconds); - } - pub fn resetTimeout(res: *Response) void { - uws_res_reset_timeout(ssl_flag, res.downcast()); - } - pub fn getBufferedAmount(res: *Response) u64 { - return uws_res_get_buffered_amount(ssl_flag, res.downcast()); - } - pub fn write(res: *Response, data: []const u8) WriteResult { - var len: usize = data.len; - return switch (uws_res_write(ssl_flag, res.downcast(), data.ptr, &len)) { - true => .{ .want_more = len }, - false => .{ .backpressure = len }, - }; - } - pub fn getWriteOffset(res: *Response) u64 { - return uws_res_get_write_offset(ssl_flag, res.downcast()); - } - pub fn overrideWriteOffset(res: *Response, offset: anytype) void { - uws_res_override_write_offset(ssl_flag, res.downcast(), @as(u64, @intCast(offset))); - } - pub fn hasResponded(res: *Response) bool { - return uws_res_has_responded(ssl_flag, res.downcast()); - } - - pub fn getNativeHandle(res: *Response) bun.FileDescriptor { - if (comptime Environment.isWindows) { - // on windows uSockets exposes SOCKET - return .fromNative(@ptrCast(uws_res_get_native_handle(ssl_flag, res.downcast()))); - } - - return .fromNative(@intCast(@intFromPtr(uws_res_get_native_handle(ssl_flag, res.downcast())))); - } - pub fn getRemoteAddressAsText(res: *Response) ?[]const u8 { - var buf: [*]const u8 = undefined; - const size = uws_res_get_remote_address_as_text(ssl_flag, res.downcast(), &buf); - return if (size > 0) buf[0..size] else null; - } - pub fn getRemoteSocketInfo(res: *Response) ?SocketAddress { - var address = SocketAddress{ - .ip = undefined, - .port = undefined, - .is_ipv6 = undefined, - }; - // This function will fill in the slots and return len. - // if len is zero it will not fill in the slots so it is ub to - // return the struct in that case. - address.ip.len = uws_res_get_remote_address_info( - res.downcast(), - &address.ip.ptr, - &address.port, - &address.is_ipv6, - ); - return if (address.ip.len > 0) address else null; - } - pub fn onWritable( - res: *Response, - comptime UserDataType: type, - comptime handler: fn (UserDataType, u64, *Response) bool, - user_data: UserDataType, - ) void { - const Wrapper = struct { - pub fn handle(this: *uws_res, amount: u64, data: ?*anyopaque) callconv(.C) bool { - if (comptime UserDataType == void) { - return @call(bun.callmod_inline, handler, .{ {}, amount, castRes(this) }); - } else { - return @call(bun.callmod_inline, handler, .{ - @as(UserDataType, @ptrCast(@alignCast(data.?))), - amount, - castRes(this), - }); - } - } - }; - uws_res_on_writable(ssl_flag, res.downcast(), Wrapper.handle, user_data); - } - - pub fn clearOnWritable(res: *Response) void { - uws_res_clear_on_writable(ssl_flag, res.downcast()); - } - pub inline fn markNeedsMore(res: *Response) void { - if (!ssl) { - us_socket_mark_needs_more_not_ssl(res.downcast()); - } - } - pub fn onAborted(res: *Response, comptime UserDataType: type, comptime handler: fn (UserDataType, *Response) void, optional_data: UserDataType) void { - const Wrapper = struct { - pub fn handle(this: *uws_res, user_data: ?*anyopaque) callconv(.C) void { - if (comptime UserDataType == void) { - @call(bun.callmod_inline, handler, .{ {}, castRes(this), {} }); - } else { - @call(bun.callmod_inline, handler, .{ @as(UserDataType, @ptrCast(@alignCast(user_data.?))), castRes(this) }); - } - } - }; - uws_res_on_aborted(ssl_flag, res.downcast(), Wrapper.handle, optional_data); - } - - pub fn clearAborted(res: *Response) void { - uws_res_on_aborted(ssl_flag, res.downcast(), null, null); - } - pub fn onTimeout(res: *Response, comptime UserDataType: type, comptime handler: fn (UserDataType, *Response) void, optional_data: UserDataType) void { - const Wrapper = struct { - pub fn handle(this: *uws_res, user_data: ?*anyopaque) callconv(.C) void { - if (comptime UserDataType == void) { - @call(bun.callmod_inline, handler, .{ {}, castRes(this) }); - } else { - @call(bun.callmod_inline, handler, .{ @as(UserDataType, @ptrCast(@alignCast(user_data.?))), castRes(this) }); - } - } - }; - uws_res_on_timeout(ssl_flag, res.downcast(), Wrapper.handle, optional_data); - } - - pub fn clearTimeout(res: *Response) void { - uws_res_on_timeout(ssl_flag, res.downcast(), null, null); - } - pub fn clearOnData(res: *Response) void { - uws_res_on_data(ssl_flag, res.downcast(), null, null); - } - - pub fn onData( - res: *Response, - comptime UserDataType: type, - comptime handler: fn (UserDataType, *Response, chunk: []const u8, last: bool) void, - optional_data: UserDataType, - ) void { - const Wrapper = struct { - pub fn handle(this: *uws_res, chunk_ptr: [*c]const u8, len: usize, last: bool, user_data: ?*anyopaque) callconv(.C) void { - if (comptime UserDataType == void) { - @call(bun.callmod_inline, handler, .{ - {}, - castRes(this), - if (len > 0) chunk_ptr[0..len] else "", - last, - }); - } else { - @call(bun.callmod_inline, handler, .{ - @as(UserDataType, @ptrCast(@alignCast(user_data.?))), - castRes(this), - if (len > 0) chunk_ptr[0..len] else "", - last, - }); - } - } - }; - - uws_res_on_data(ssl_flag, res.downcast(), Wrapper.handle, optional_data); - } - - pub fn endStream(res: *Response, close_connection: bool) void { - uws_res_end_stream(ssl_flag, res.downcast(), close_connection); - } - - pub fn corked( - res: *Response, - comptime handler: anytype, - args_tuple: anytype, - ) void { - const Wrapper = struct { - const handler_fn = handler; - const Args = *@TypeOf(args_tuple); - pub fn handle(user_data: ?*anyopaque) callconv(.C) void { - const args: Args = @alignCast(@ptrCast(user_data.?)); - @call(.always_inline, handler_fn, args.*); - } - }; - - uws_res_cork(ssl_flag, res.downcast(), @constCast(@ptrCast(&args_tuple)), Wrapper.handle); - } - - pub fn runCorkedWithType( - res: *Response, - comptime UserDataType: type, - comptime handler: fn (UserDataType) void, - optional_data: UserDataType, - ) void { - const Wrapper = struct { - pub fn handle(user_data: ?*anyopaque) callconv(.C) void { - if (comptime UserDataType == void) { - @call(bun.callmod_inline, handler, .{ - {}, - }); - } else { - @call(bun.callmod_inline, handler, .{ - @as(UserDataType, @ptrCast(@alignCast(user_data.?))), - }); - } - } - }; - - uws_res_cork(ssl_flag, res.downcast(), optional_data, Wrapper.handle); - } - - pub fn upgrade( - res: *Response, - comptime Data: type, - data: Data, - sec_web_socket_key: []const u8, - sec_web_socket_protocol: []const u8, - sec_web_socket_extensions: []const u8, - ctx: ?*uws_socket_context_t, - ) *Socket { - return uws_res_upgrade( - ssl_flag, - res.downcast(), - data, - sec_web_socket_key.ptr, - sec_web_socket_key.len, - sec_web_socket_protocol.ptr, - sec_web_socket_protocol.len, - sec_web_socket_extensions.ptr, - sec_web_socket_extensions.len, - ctx, - ); - } - }; - - pub const WebSocket = opaque { - pub fn raw(this: *WebSocket) *RawWebSocket { - return @as(*RawWebSocket, @ptrCast(this)); - } - pub fn as(this: *WebSocket, comptime Type: type) ?*Type { - @setRuntimeSafety(false); - return @as(?*Type, @ptrCast(@alignCast(uws_ws_get_user_data(ssl_flag, this.raw())))); - } - - pub fn close(this: *WebSocket) void { - return uws_ws_close(ssl_flag, this.raw()); - } - pub fn send(this: *WebSocket, message: []const u8, opcode: Opcode) SendStatus { - return uws_ws_send(ssl_flag, this.raw(), message.ptr, message.len, opcode); - } - pub fn sendWithOptions(this: *WebSocket, message: []const u8, opcode: Opcode, compress: bool, fin: bool) SendStatus { - return uws_ws_send_with_options(ssl_flag, this.raw(), message.ptr, message.len, opcode, compress, fin); - } - - pub fn memoryCost(this: *WebSocket) usize { - return this.raw().memoryCost(ssl_flag); - } - - // pub fn sendFragment(this: *WebSocket, message: []const u8) SendStatus { - // return uws_ws_send_fragment(ssl_flag, this.raw(), message: [*c]const u8, length: usize, compress: bool); - // } - // pub fn sendFirstFragment(this: *WebSocket, message: []const u8) SendStatus { - // return uws_ws_send_first_fragment(ssl_flag, this.raw(), message: [*c]const u8, length: usize, compress: bool); - // } - // pub fn sendFirstFragmentWithOpcode(this: *WebSocket, message: []const u8, opcode: u32, compress: bool) SendStatus { - // return uws_ws_send_first_fragment_with_opcode(ssl_flag, this.raw(), message: [*c]const u8, length: usize, opcode: Opcode, compress: bool); - // } - pub fn sendLastFragment(this: *WebSocket, message: []const u8, compress: bool) SendStatus { - return uws_ws_send_last_fragment(ssl_flag, this.raw(), message.ptr, message.len, compress); - } - pub fn end(this: *WebSocket, code: i32, message: []const u8) void { - return uws_ws_end(ssl_flag, this.raw(), code, message.ptr, message.len); - } - pub fn cork(this: *WebSocket, ctx: anytype, comptime callback: anytype) void { - const ContextType = @TypeOf(ctx); - const Wrapper = struct { - pub fn wrap(user_data: ?*anyopaque) callconv(.C) void { - @call(bun.callmod_inline, callback, .{bun.cast(ContextType, user_data.?)}); - } - }; - - return uws_ws_cork(ssl_flag, this.raw(), Wrapper.wrap, ctx); - } - pub fn subscribe(this: *WebSocket, topic: []const u8) bool { - return uws_ws_subscribe(ssl_flag, this.raw(), topic.ptr, topic.len); - } - pub fn unsubscribe(this: *WebSocket, topic: []const u8) bool { - return uws_ws_unsubscribe(ssl_flag, this.raw(), topic.ptr, topic.len); - } - pub fn isSubscribed(this: *WebSocket, topic: []const u8) bool { - return uws_ws_is_subscribed(ssl_flag, this.raw(), topic.ptr, topic.len); - } - // pub fn iterateTopics(this: *WebSocket) { - // return uws_ws_iterate_topics(ssl_flag, this.raw(), callback: ?*const fn ([*c]const u8, usize, ?*anyopaque) callconv(.C) void, user_data: ?*anyopaque) void; - // } - pub fn publish(this: *WebSocket, topic: []const u8, message: []const u8) bool { - return uws_ws_publish(ssl_flag, this.raw(), topic.ptr, topic.len, message.ptr, message.len); - } - pub fn publishWithOptions(this: *WebSocket, topic: []const u8, message: []const u8, opcode: Opcode, compress: bool) bool { - return uws_ws_publish_with_options(ssl_flag, this.raw(), topic.ptr, topic.len, message.ptr, message.len, opcode, compress); - } - pub fn getBufferedAmount(this: *WebSocket) u32 { - return uws_ws_get_buffered_amount(ssl_flag, this.raw()); - } - pub fn getRemoteAddress(this: *WebSocket, buf: []u8) []u8 { - var ptr: [*]u8 = undefined; - const len = uws_ws_get_remote_address(ssl_flag, this.raw(), &ptr); - bun.copy(u8, buf, ptr[0..len]); - return buf[0..len]; - } - }; - }; -} -extern fn uws_res_end_stream(ssl: i32, res: *uws_res, close_connection: bool) void; -extern fn uws_res_prepare_for_sendfile(ssl: i32, res: *uws_res) void; -extern fn uws_res_get_native_handle(ssl: i32, res: *uws_res) *Socket; -extern fn uws_res_get_remote_address_as_text(ssl: i32, res: *uws_res, dest: *[*]const u8) usize; -extern fn uws_create_app(ssl: i32, options: us_bun_socket_context_options_t) ?*uws_app_t; -extern fn uws_app_destroy(ssl: i32, app: *uws_app_t) void; -extern fn uws_app_set_flags(ssl: i32, app: *uws_app_t, require_host_header: bool, use_strict_method_validation: bool) void; -extern fn uws_app_set_max_http_header_size(ssl: i32, app: *uws_app_t, max_header_size: u64) void; -extern fn uws_app_get(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; -extern fn uws_app_post(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; -extern fn uws_app_options(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; -extern fn uws_app_delete(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; -extern fn uws_app_patch(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; -extern fn uws_app_put(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; -extern fn uws_app_head(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; -extern fn uws_app_connect(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; -extern fn uws_app_trace(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; -extern fn uws_app_any(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; -extern fn uws_app_run(ssl: i32, *uws_app_t) void; -extern fn uws_app_domain(ssl: i32, app: *uws_app_t, domain: [*c]const u8) void; -extern fn uws_app_listen(ssl: i32, app: *uws_app_t, port: i32, handler: uws_listen_handler, user_data: ?*anyopaque) void; -extern fn uws_app_listen_with_config( - ssl: i32, - app: *uws_app_t, - host: [*c]const u8, - port: u16, - options: i32, - handler: uws_listen_handler, - user_data: ?*anyopaque, -) void; -extern fn uws_constructor_failed(ssl: i32, app: *uws_app_t) bool; -extern fn uws_num_subscribers(ssl: i32, app: *uws_app_t, topic: [*c]const u8, topic_length: usize) c_uint; -extern fn uws_publish(ssl: i32, app: *uws_app_t, topic: [*c]const u8, topic_length: usize, message: [*c]const u8, message_length: usize, opcode: Opcode, compress: bool) bool; -extern fn uws_get_native_handle(ssl: i32, app: *anyopaque) ?*anyopaque; -extern fn uws_remove_server_name(ssl: i32, app: *uws_app_t, hostname_pattern: [*c]const u8) void; -extern fn uws_add_server_name(ssl: i32, app: *uws_app_t, hostname_pattern: [*c]const u8) void; -extern fn uws_add_server_name_with_options(ssl: i32, app: *uws_app_t, hostname_pattern: [*c]const u8, options: us_bun_socket_context_options_t) i32; -extern fn uws_missing_server_name(ssl: i32, app: *uws_app_t, handler: uws_missing_server_handler, user_data: ?*anyopaque) void; -extern fn uws_filter(ssl: i32, app: *uws_app_t, handler: uws_filter_handler, user_data: ?*anyopaque) void; -extern fn uws_ws(ssl: i32, app: *uws_app_t, ctx: *anyopaque, pattern: [*]const u8, pattern_len: usize, id: usize, behavior: *const WebSocketBehavior) void; - -extern fn uws_ws_get_user_data(ssl: i32, ws: ?*RawWebSocket) ?*anyopaque; -extern fn uws_ws_close(ssl: i32, ws: ?*RawWebSocket) void; -extern fn uws_ws_send(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, opcode: Opcode) SendStatus; -extern fn uws_ws_send_with_options(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, opcode: Opcode, compress: bool, fin: bool) SendStatus; -extern fn uws_ws_send_fragment(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, compress: bool) SendStatus; -extern fn uws_ws_send_first_fragment(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, compress: bool) SendStatus; -extern fn uws_ws_send_first_fragment_with_opcode(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, opcode: Opcode, compress: bool) SendStatus; -extern fn uws_ws_send_last_fragment(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, compress: bool) SendStatus; -extern fn uws_ws_end(ssl: i32, ws: ?*RawWebSocket, code: i32, message: [*c]const u8, length: usize) void; -extern fn uws_ws_cork(ssl: i32, ws: ?*RawWebSocket, handler: ?*const fn (?*anyopaque) callconv(.C) void, user_data: ?*anyopaque) void; -extern fn uws_ws_subscribe(ssl: i32, ws: ?*RawWebSocket, topic: [*c]const u8, length: usize) bool; -extern fn uws_ws_unsubscribe(ssl: i32, ws: ?*RawWebSocket, topic: [*c]const u8, length: usize) bool; -extern fn uws_ws_is_subscribed(ssl: i32, ws: ?*RawWebSocket, topic: [*c]const u8, length: usize) bool; -extern fn uws_ws_iterate_topics(ssl: i32, ws: ?*RawWebSocket, callback: ?*const fn ([*c]const u8, usize, ?*anyopaque) callconv(.C) void, user_data: ?*anyopaque) void; -extern fn uws_ws_publish(ssl: i32, ws: ?*RawWebSocket, topic: [*c]const u8, topic_length: usize, message: [*c]const u8, message_length: usize) bool; -extern fn uws_ws_publish_with_options(ssl: i32, ws: ?*RawWebSocket, topic: [*c]const u8, topic_length: usize, message: [*c]const u8, message_length: usize, opcode: Opcode, compress: bool) bool; -extern fn uws_ws_get_buffered_amount(ssl: i32, ws: ?*RawWebSocket) usize; -extern fn uws_ws_get_remote_address(ssl: i32, ws: ?*RawWebSocket, dest: *[*]u8) usize; -extern fn uws_ws_get_remote_address_as_text(ssl: i32, ws: ?*RawWebSocket, dest: *[*]u8) usize; -extern fn uws_res_get_remote_address_info(res: *uws_res, dest: *[*]const u8, port: *i32, is_ipv6: *bool) usize; - -const uws_res = opaque {}; -extern fn uws_res_uncork(ssl: i32, res: *uws_res) void; -extern fn uws_res_end(ssl: i32, res: *uws_res, data: [*c]const u8, length: usize, close_connection: bool) void; -extern fn uws_res_try_end( - ssl: i32, - res: *uws_res, - data: [*c]const u8, - length: usize, - total: usize, - close: bool, -) bool; -extern fn uws_res_flush_headers(ssl: i32, res: *uws_res) void; -extern fn uws_res_pause(ssl: i32, res: *uws_res) void; -extern fn uws_res_resume(ssl: i32, res: *uws_res) void; -extern fn uws_res_write_continue(ssl: i32, res: *uws_res) void; -extern fn uws_res_write_status(ssl: i32, res: *uws_res, status: [*c]const u8, length: usize) void; -extern fn uws_res_write_header(ssl: i32, res: *uws_res, key: [*c]const u8, key_length: usize, value: [*c]const u8, value_length: usize) void; -extern fn uws_res_write_header_int(ssl: i32, res: *uws_res, key: [*c]const u8, key_length: usize, value: u64) void; -extern fn uws_res_end_without_body(ssl: i32, res: *uws_res, close_connection: bool) void; -extern fn uws_res_end_sendfile(ssl: i32, res: *uws_res, write_offset: u64, close_connection: bool) void; -extern fn uws_res_timeout(ssl: i32, res: *uws_res, timeout: u8) void; -extern fn uws_res_reset_timeout(ssl: i32, res: *uws_res) void; -extern fn uws_res_get_buffered_amount(ssl: i32, res: *uws_res) u64; -extern fn uws_res_write(ssl: i32, res: *uws_res, data: ?[*]const u8, length: *usize) bool; -extern fn uws_res_get_write_offset(ssl: i32, res: *uws_res) u64; -extern fn uws_res_override_write_offset(ssl: i32, res: *uws_res, u64) void; -extern fn uws_res_has_responded(ssl: i32, res: *uws_res) bool; -extern fn uws_res_on_writable(ssl: i32, res: *uws_res, handler: ?*const fn (*uws_res, u64, ?*anyopaque) callconv(.C) bool, user_data: ?*anyopaque) void; -extern fn uws_res_clear_on_writable(ssl: i32, res: *uws_res) void; -extern fn uws_res_on_aborted(ssl: i32, res: *uws_res, handler: ?*const fn (*uws_res, ?*anyopaque) callconv(.C) void, optional_data: ?*anyopaque) void; -extern fn uws_res_on_timeout(ssl: i32, res: *uws_res, handler: ?*const fn (*uws_res, ?*anyopaque) callconv(.C) void, optional_data: ?*anyopaque) void; - -extern fn uws_res_on_data( - ssl: i32, - res: *uws_res, - handler: ?*const fn (*uws_res, [*c]const u8, usize, bool, ?*anyopaque) callconv(.C) void, - optional_data: ?*anyopaque, -) void; -extern fn uws_res_upgrade( - ssl: i32, - res: *uws_res, - data: ?*anyopaque, - sec_web_socket_key: [*c]const u8, - sec_web_socket_key_length: usize, - sec_web_socket_protocol: [*c]const u8, - sec_web_socket_protocol_length: usize, - sec_web_socket_extensions: [*c]const u8, - sec_web_socket_extensions_length: usize, - ws: ?*uws_socket_context_t, -) *Socket; -extern fn uws_res_cork(i32, res: *uws_res, ctx: *anyopaque, corker: *const (fn (?*anyopaque) callconv(.C) void)) void; -pub const LIBUS_RECV_BUFFER_LENGTH = 524288; pub const LIBUS_TIMEOUT_GRANULARITY = @as(i32, 4); pub const LIBUS_RECV_BUFFER_PADDING = @as(i32, 32); pub const LIBUS_EXT_ALIGNMENT = @as(i32, 16); -pub const LIBUS_SOCKET_DESCRIPTOR = std.posix.socket_t; pub const _COMPRESSOR_MASK: i32 = 255; pub const _DECOMPRESSOR_MASK: i32 = 3840; @@ -4135,13 +54,58 @@ pub const DEDICATED_COMPRESSOR_64KB: i32 = 214; pub const DEDICATED_COMPRESSOR_128KB: i32 = 231; pub const DEDICATED_COMPRESSOR_256KB: i32 = 248; pub const DEDICATED_COMPRESSOR: i32 = 248; -pub const uws_compress_options_t = i32; -pub const CONTINUATION: i32 = 0; -pub const TEXT: i32 = 1; -pub const BINARY: i32 = 2; -pub const CLOSE: i32 = 8; -pub const PING: i32 = 9; -pub const PONG: i32 = 10; + +pub const LIBUS_LISTEN_DEFAULT: i32 = 0; +pub const LIBUS_LISTEN_EXCLUSIVE_PORT: i32 = 1; +pub const LIBUS_SOCKET_ALLOW_HALF_OPEN: i32 = 2; +pub const LIBUS_LISTEN_REUSE_PORT: i32 = 4; +pub const LIBUS_SOCKET_IPV6_ONLY: i32 = 8; +pub const LIBUS_LISTEN_REUSE_ADDR: i32 = 16; +pub const LIBUS_LISTEN_DISALLOW_REUSE_PORT_FAILURE: i32 = 32; + +// TODO: refactor to error union +pub const create_bun_socket_error_t = enum(c_int) { + none = 0, + load_ca_file, + invalid_ca_file, + invalid_ca, + + pub fn toJS(this: create_bun_socket_error_t, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + return switch (this) { + .none => brk: { + bun.debugAssert(false); + break :brk .null; + }, + .load_ca_file => globalObject.ERR(.BORINGSSL, "Failed to load CA file", .{}).toJS(), + .invalid_ca_file => globalObject.ERR(.BORINGSSL, "Invalid CA file", .{}).toJS(), + .invalid_ca => globalObject.ERR(.BORINGSSL, "Invalid CA", .{}).toJS(), + }; + } +}; + +pub const us_bun_verify_error_t = extern struct { + error_no: i32 = 0, + code: [*c]const u8 = null, + reason: [*c]const u8 = null, + + pub fn toJS(this: *const us_bun_verify_error_t, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + const code = if (this.code == null) "" else this.code[0..bun.len(this.code)]; + const reason = if (this.reason == null) "" else this.reason[0..bun.len(this.reason)]; + + const fallback = JSC.SystemError{ + .code = bun.String.createUTF8(code), + .message = bun.String.createUTF8(reason), + }; + + return fallback.toErrorInstance(globalObject); + } +}; + +pub const SocketAddress = struct { + ip: []const u8, + port: i32, + is_ipv6: bool, +}; pub const Opcode = enum(i32) { continuation = 0, @@ -4151,6 +115,13 @@ pub const Opcode = enum(i32) { ping = 9, pong = 10, _, + + const CONTINUATION: i32 = 0; + const TEXT: i32 = 1; + const BINARY: i32 = 2; + const CLOSE: i32 = 8; + const PING: i32 = 9; + const PONG: i32 = 10; }; pub const SendStatus = enum(c_uint) { @@ -4158,498 +129,21 @@ pub const SendStatus = enum(c_uint) { success = 1, dropped = 2, }; -pub const uws_app_listen_config_t = extern struct { - port: c_int, - host: ?[*:0]const u8 = null, - options: c_int = 0, -}; -pub const AppListenConfig = uws_app_listen_config_t; - -extern fn us_socket_mark_needs_more_not_ssl(socket: ?*uws_res) void; - -extern fn uws_res_state(ssl: c_int, res: *const uws_res) State; - -pub const State = enum(u8) { - HTTP_STATUS_CALLED = 1, - HTTP_WRITE_CALLED = 2, - HTTP_END_CALLED = 4, - HTTP_RESPONSE_PENDING = 8, - HTTP_CONNECTION_CLOSE = 16, - HTTP_WROTE_CONTENT_LENGTH_HEADER = 32, - - _, - - pub inline fn isResponsePending(this: State) bool { - return @intFromEnum(this) & @intFromEnum(State.HTTP_RESPONSE_PENDING) != 0; - } - - pub inline fn hasWrittenContentLengthHeader(this: State) bool { - return @intFromEnum(this) & @intFromEnum(State.HTTP_WROTE_CONTENT_LENGTH_HEADER) != 0; - } - - pub inline fn isHttpEndCalled(this: State) bool { - return @intFromEnum(this) & @intFromEnum(State.HTTP_END_CALLED) != 0; - } - - pub inline fn isHttpWriteCalled(this: State) bool { - return @intFromEnum(this) & @intFromEnum(State.HTTP_WRITE_CALLED) != 0; - } - - pub inline fn isHttpStatusCalled(this: State) bool { - return @intFromEnum(this) & @intFromEnum(State.HTTP_STATUS_CALLED) != 0; - } - - pub inline fn isHttpConnectionClose(this: State) bool { - return @intFromEnum(this) & @intFromEnum(State.HTTP_CONNECTION_CLOSE) != 0; - } -}; - -extern fn uws_app_listen_domain_with_options( - ssl_flag: c_int, - app: *uws_app_t, - domain: [*:0]const u8, - pathlen: usize, - i32, - *const (fn (*ListenSocket, domain: [*:0]const u8, i32, *anyopaque) callconv(.C) void), - ?*anyopaque, -) void; - -/// This extends off of uws::Loop on Windows -pub const WindowsLoop = extern struct { - const uv = bun.windows.libuv; - - internal_loop_data: InternalLoopData align(16), - - uv_loop: *uv.Loop, - is_default: c_int, - pre: *uv.uv_prepare_t, - check: *uv.uv_check_t, - - pub fn uncork(this: *PosixLoop) void { - uws_res_clear_corked_socket(this); - } - - pub fn get() *WindowsLoop { - return uws_get_loop_with_native(bun.windows.libuv.Loop.get()); - } - - extern fn uws_get_loop_with_native(*anyopaque) *WindowsLoop; - - pub fn iterationNumber(this: *const WindowsLoop) u64 { - return this.internal_loop_data.iteration_nr; - } - - pub fn addActive(this: *const WindowsLoop, val: u32) void { - this.uv_loop.addActive(val); - } - - pub fn subActive(this: *const WindowsLoop, val: u32) void { - this.uv_loop.subActive(val); - } - - pub fn isActive(this: *const WindowsLoop) bool { - return this.uv_loop.isActive(); - } - - pub fn wakeup(this: *WindowsLoop) void { - us_wakeup_loop(this); - } - - pub const wake = wakeup; - - pub fn tickWithTimeout(this: *WindowsLoop, _: ?*const bun.timespec) void { - us_loop_run(this); - } - - pub fn tickWithoutIdle(this: *WindowsLoop) void { - us_loop_pump(this); - } - - pub fn create(comptime Handler: anytype) *WindowsLoop { - return us_create_loop( - null, - Handler.wakeup, - if (@hasDecl(Handler, "pre")) Handler.pre else null, - if (@hasDecl(Handler, "post")) Handler.post else null, - 0, - ).?; - } - - pub fn run(this: *WindowsLoop) void { - us_loop_run(this); - } - - // TODO: remove these two aliases - pub const tick = run; - pub const wait = run; - - pub fn inc(this: *WindowsLoop) void { - this.uv_loop.inc(); - } - - pub fn dec(this: *WindowsLoop) void { - this.uv_loop.dec(); - } - - pub const ref = inc; - pub const unref = dec; - - pub fn nextTick(this: *Loop, comptime UserType: type, user_data: UserType, comptime deferCallback: fn (ctx: UserType) void) void { - const Handler = struct { - pub fn callback(data: *anyopaque) callconv(.C) void { - deferCallback(@as(UserType, @ptrCast(@alignCast(data)))); - } - }; - uws_loop_defer(this, user_data, Handler.callback); - } - - fn NewHandler(comptime UserType: type, comptime callback_fn: fn (UserType) void) type { - return struct { - loop: *Loop, - pub fn removePost(handler: @This()) void { - return uws_loop_removePostHandler(handler.loop, callback); - } - pub fn removePre(handler: @This()) void { - return uws_loop_removePostHandler(handler.loop, callback); - } - pub fn callback(data: *anyopaque, _: *Loop) callconv(.C) void { - callback_fn(@as(UserType, @ptrCast(@alignCast(data)))); - } - }; - } -}; - -pub const Loop = if (bun.Environment.isWindows) WindowsLoop else PosixLoop; - -extern fn uws_get_loop() *Loop; -extern fn us_create_loop( - hint: ?*anyopaque, - wakeup_cb: ?*const fn (*Loop) callconv(.C) void, - pre_cb: ?*const fn (*Loop) callconv(.C) void, - post_cb: ?*const fn (*Loop) callconv(.C) void, - ext_size: c_uint, -) ?*Loop; -extern fn us_loop_free(loop: ?*Loop) void; -extern fn us_loop_ext(loop: ?*Loop) ?*anyopaque; -extern fn us_loop_run(loop: ?*Loop) void; -extern fn us_loop_pump(loop: ?*Loop) void; -extern fn us_wakeup_loop(loop: ?*Loop) void; -extern fn us_loop_integrate(loop: ?*Loop) void; -extern fn us_loop_iteration_number(loop: ?*Loop) c_longlong; -extern fn uws_loop_addPostHandler(loop: *Loop, ctx: *anyopaque, cb: *const (fn (ctx: *anyopaque, loop: *Loop) callconv(.C) void)) void; -extern fn uws_loop_removePostHandler(loop: *Loop, ctx: *anyopaque, cb: *const (fn (ctx: *anyopaque, loop: *Loop) callconv(.C) void)) void; -extern fn uws_loop_addPreHandler(loop: *Loop, ctx: *anyopaque, cb: *const (fn (ctx: *anyopaque, loop: *Loop) callconv(.C) void)) void; -extern fn uws_loop_removePreHandler(loop: *Loop, ctx: *anyopaque, cb: *const (fn (ctx: *anyopaque, loop: *Loop) callconv(.C) void)) void; -extern fn us_socket_pair( - ctx: *SocketContext, - ext_size: c_int, - fds: *[2]LIBUS_SOCKET_DESCRIPTOR, -) ?*Socket; - -pub extern fn us_socket_from_fd( - ctx: *SocketContext, - ext_size: c_int, - fd: LIBUS_SOCKET_DESCRIPTOR, - is_ipc: c_int, -) ?*Socket; - -pub fn newSocketFromPair(ctx: *SocketContext, ext_size: c_int, fds: *[2]LIBUS_SOCKET_DESCRIPTOR) ?SocketTCP { - return SocketTCP{ - .socket = us_socket_pair(ctx, ext_size, fds) orelse return null, - }; -} - -extern fn us_socket_get_error(ssl_flag: c_int, socket: *Socket) c_int; - -pub const AnySocket = union(enum) { - SocketTCP: SocketTCP, - SocketTLS: SocketTLS, - - pub fn setTimeout(this: AnySocket, seconds: c_uint) void { - switch (this) { - .SocketTCP => this.SocketTCP.setTimeout(seconds), - .SocketTLS => this.SocketTLS.setTimeout(seconds), - } - } - - pub fn shutdown(this: AnySocket) void { - switch (this) { - .SocketTCP => |sock| sock.shutdown(), - .SocketTLS => |sock| sock.shutdown(), - } - } - - pub fn shutdownRead(this: AnySocket) void { - switch (this) { - .SocketTCP => |sock| sock.shutdownRead(), - .SocketTLS => |sock| sock.shutdownRead(), - } - } - - pub fn isShutdown(this: AnySocket) bool { - return switch (this) { - .SocketTCP => this.SocketTCP.isShutdown(), - .SocketTLS => this.SocketTLS.isShutdown(), - }; - } - pub fn isClosed(this: AnySocket) bool { - return switch (this) { - inline else => |s| s.isClosed(), - }; - } - pub fn close(this: AnySocket) void { - switch (this) { - inline else => |s| s.close(.normal), - } - } - - pub fn terminate(this: AnySocket) void { - switch (this) { - inline else => |s| s.close(.failure), - } - } - - pub fn write(this: AnySocket, data: []const u8, msg_more: bool) i32 { - return switch (this) { - .SocketTCP => |sock| sock.write(data, msg_more), - .SocketTLS => |sock| sock.write(data, msg_more), - }; - } - - pub fn getNativeHandle(this: AnySocket) ?*anyopaque { - return switch (this.socket()) { - .connected => |sock| sock.getNativeHandle(this.isSSL()), - else => null, - }; - } - - pub fn localPort(this: AnySocket) i32 { - switch (this) { - .SocketTCP => |sock| sock.localPort(), - .SocketTLS => |sock| sock.localPort(), - } - } - - pub fn isSSL(this: AnySocket) bool { - return switch (this) { - .SocketTCP => false, - .SocketTLS => true, - }; - } - - pub fn socket(this: AnySocket) InternalSocket { - return switch (this) { - .SocketTCP => this.SocketTCP.socket, - .SocketTLS => this.SocketTLS.socket, - }; - } - - pub fn ext(this: AnySocket, comptime ContextType: type) ?*ContextType { - const ptr = this.socket().ext(this.isSSL()) orelse return null; - - return @ptrCast(@alignCast(ptr)); - } - - pub fn context(this: AnySocket) *SocketContext { - @setRuntimeSafety(true); - return switch (this) { - .SocketTCP => |sock| sock.context(), - .SocketTLS => |sock| sock.context(), - }.?; - } -}; - -pub const udp = struct { - pub const Socket = opaque { - pub fn create(loop: *Loop, data_cb: *const fn (*udp.Socket, *PacketBuffer, c_int) callconv(.C) void, drain_cb: *const fn (*udp.Socket) callconv(.C) void, close_cb: *const fn (*udp.Socket) callconv(.C) void, host: [*c]const u8, port: c_ushort, options: c_int, err: ?*c_int, user_data: ?*anyopaque) ?*udp.Socket { - return us_create_udp_socket(loop, data_cb, drain_cb, close_cb, host, port, options, err, user_data); - } - - pub fn send(this: *udp.Socket, payloads: []const [*]const u8, lengths: []const usize, addresses: []const ?*const anyopaque) c_int { - bun.assert(payloads.len == lengths.len and payloads.len == addresses.len); - return us_udp_socket_send(this, payloads.ptr, lengths.ptr, addresses.ptr, @intCast(payloads.len)); - } - - pub fn user(this: *udp.Socket) ?*anyopaque { - return us_udp_socket_user(this); - } - - pub fn bind(this: *udp.Socket, hostname: [*c]const u8, port: c_uint) c_int { - return us_udp_socket_bind(this, hostname, port); - } - - /// Get the bound port in host byte order - pub fn boundPort(this: *udp.Socket) c_int { - return us_udp_socket_bound_port(this); - } - - pub fn boundIp(this: *udp.Socket, buf: [*c]u8, length: *i32) void { - return us_udp_socket_bound_ip(this, buf, length); - } - - pub fn remoteIp(this: *udp.Socket, buf: [*c]u8, length: *i32) void { - return us_udp_socket_remote_ip(this, buf, length); - } - - pub fn close(this: *udp.Socket) void { - return us_udp_socket_close(this); - } - - pub fn connect(this: *udp.Socket, hostname: [*c]const u8, port: c_uint) c_int { - return us_udp_socket_connect(this, hostname, port); - } - - pub fn disconnect(this: *udp.Socket) c_int { - return us_udp_socket_disconnect(this); - } - - pub fn setBroadcast(this: *udp.Socket, enabled: bool) c_int { - return us_udp_socket_set_broadcast(this, @intCast(@intFromBool(enabled))); - } - - pub fn setUnicastTTL(this: *udp.Socket, ttl: i32) c_int { - return us_udp_socket_set_ttl_unicast(this, @intCast(ttl)); - } - - pub fn setMulticastTTL(this: *udp.Socket, ttl: i32) c_int { - return us_udp_socket_set_ttl_multicast(this, @intCast(ttl)); - } - - pub fn setMulticastLoopback(this: *udp.Socket, enabled: bool) c_int { - return us_udp_socket_set_multicast_loopback(this, @intCast(@intFromBool(enabled))); - } - - pub fn setMulticastInterface(this: *udp.Socket, iface: *const std.posix.sockaddr.storage) c_int { - return us_udp_socket_set_multicast_interface(this, iface); - } - - pub fn setMembership(this: *udp.Socket, address: *const std.posix.sockaddr.storage, iface: ?*const std.posix.sockaddr.storage, drop: bool) c_int { - return us_udp_socket_set_membership(this, address, iface, @intFromBool(drop)); - } - - pub fn setSourceSpecificMembership(this: *udp.Socket, source: *const std.posix.sockaddr.storage, group: *const std.posix.sockaddr.storage, iface: ?*const std.posix.sockaddr.storage, drop: bool) c_int { - return us_udp_socket_set_source_specific_membership(this, source, group, iface, @intFromBool(drop)); - } - }; - - extern fn us_create_udp_socket(loop: ?*Loop, data_cb: *const fn (*udp.Socket, *PacketBuffer, c_int) callconv(.C) void, drain_cb: *const fn (*udp.Socket) callconv(.C) void, close_cb: *const fn (*udp.Socket) callconv(.C) void, host: [*c]const u8, port: c_ushort, options: c_int, err: ?*c_int, user_data: ?*anyopaque) ?*udp.Socket; - extern fn us_udp_socket_connect(socket: ?*udp.Socket, hostname: [*c]const u8, port: c_uint) c_int; - extern fn us_udp_socket_disconnect(socket: ?*udp.Socket) c_int; - extern fn us_udp_socket_send(socket: ?*udp.Socket, [*c]const [*c]const u8, [*c]const usize, [*c]const ?*const anyopaque, c_int) c_int; - extern fn us_udp_socket_user(socket: ?*udp.Socket) ?*anyopaque; - extern fn us_udp_socket_bind(socket: ?*udp.Socket, hostname: [*c]const u8, port: c_uint) c_int; - extern fn us_udp_socket_bound_port(socket: ?*udp.Socket) c_int; - extern fn us_udp_socket_bound_ip(socket: ?*udp.Socket, buf: [*c]u8, length: [*c]i32) void; - extern fn us_udp_socket_remote_ip(socket: ?*udp.Socket, buf: [*c]u8, length: [*c]i32) void; - extern fn us_udp_socket_close(socket: ?*udp.Socket) void; - extern fn us_udp_socket_set_broadcast(socket: ?*udp.Socket, enabled: c_int) c_int; - extern fn us_udp_socket_set_ttl_unicast(socket: ?*udp.Socket, ttl: c_int) c_int; - extern fn us_udp_socket_set_ttl_multicast(socket: ?*udp.Socket, ttl: c_int) c_int; - extern fn us_udp_socket_set_multicast_loopback(socket: ?*udp.Socket, enabled: c_int) c_int; - extern fn us_udp_socket_set_multicast_interface(socket: ?*udp.Socket, iface: *const std.posix.sockaddr.storage) c_int; - extern fn us_udp_socket_set_membership(socket: ?*udp.Socket, address: *const std.posix.sockaddr.storage, iface: ?*const std.posix.sockaddr.storage, drop: c_int) c_int; - extern fn us_udp_socket_set_source_specific_membership(socket: ?*udp.Socket, source: *const std.posix.sockaddr.storage, group: *const std.posix.sockaddr.storage, iface: ?*const std.posix.sockaddr.storage, drop: c_int) c_int; - - pub const PacketBuffer = opaque { - pub fn getPeer(this: *PacketBuffer, index: c_int) *std.posix.sockaddr.storage { - return us_udp_packet_buffer_peer(this, index); - } - - pub fn getPayload(this: *PacketBuffer, index: c_int) []u8 { - const payload = us_udp_packet_buffer_payload(this, index); - const len = us_udp_packet_buffer_payload_length(this, index); - return payload[0..@as(usize, @intCast(len))]; - } - }; - - extern fn us_udp_packet_buffer_peer(buf: ?*PacketBuffer, index: c_int) *std.posix.sockaddr.storage; - extern fn us_udp_packet_buffer_payload(buf: ?*PacketBuffer, index: c_int) [*]u8; - extern fn us_udp_packet_buffer_payload_length(buf: ?*PacketBuffer, index: c_int) c_int; -}; extern fn bun_clear_loop_at_thread_exit() void; pub fn onThreadExit() void { bun_clear_loop_at_thread_exit(); } -extern fn uws_app_clear_routes(ssl_flag: c_int, app: *uws_app_t) void; -extern fn uws_res_clear_corked_socket(loop: *Loop) void; -pub extern fn us_socket_upgrade_to_tls(s: *Socket, new_context: *SocketContext, sni: ?[*:0]const u8) ?*Socket; - export fn BUN__warn__extra_ca_load_failed(filename: [*c]const u8, error_msg: [*c]const u8) void { bun.Output.warn("ignoring extra certs from {s}, load failed: {s}", .{ filename, error_msg }); } -/// Mixin to read an entire request body into memory and run a callback. -/// Consumers should make sure a reference count is held on the server, -/// and is unreferenced after one of the two callbacks are called. -/// -/// See DevServer.zig's ErrorReportRequest for an example. -pub fn BodyReaderMixin( - Wrap: type, - field: []const u8, - // `body` is freed after this function returns. - onBody: fn (*Wrap, body: []const u8, resp: AnyResponse) anyerror!void, - // Called on error or request abort - onError: fn (*Wrap) void, -) type { - return struct { - body: std.ArrayList(u8), +pub const LIBUS_SOCKET_DESCRIPTOR = switch (bun.Environment.isWindows) { + true => *anyopaque, + false => i32, +}; - pub fn init(allocator: std.mem.Allocator) @This() { - return .{ .body = .init(allocator) }; - } - - /// Memory is freed after the callback returns, or automatically on failure. - pub fn readBody(ctx: *@This(), resp: anytype) void { - const Mixin = @This(); - const Response = @TypeOf(resp); - const handlers = struct { - fn onDataGeneric(mixin: *Mixin, r: Response, chunk: []const u8, last: bool) void { - const any = AnyResponse.init(r); - onData(mixin, any, chunk, last) catch |e| switch (e) { - error.OutOfMemory => return mixin.onOOM(any), - else => return mixin.onInvalid(any), - }; - } - fn onAborted(mixin: *Mixin, _: Response) void { - mixin.body.deinit(); - onError(@fieldParentPtr(field, mixin)); - } - }; - resp.onData(*@This(), handlers.onDataGeneric, ctx); - resp.onAborted(*@This(), handlers.onAborted, ctx); - } - - fn onData(ctx: *@This(), resp: AnyResponse, chunk: []const u8, last: bool) !void { - if (last) { - var body = ctx.body; // stack copy so onBody can free everything - resp.clearAborted(); - resp.clearOnData(); - if (body.items.len > 0) { - try body.appendSlice(chunk); - try onBody(@fieldParentPtr(field, ctx), ctx.body.items, resp); - } else { - try onBody(@fieldParentPtr(field, ctx), chunk, resp); - } - body.deinit(); - } else { - try ctx.body.appendSlice(chunk); - } - } - - fn onOOM(ctx: *@This(), r: AnyResponse) void { - r.writeStatus("500 Internal Server Error"); - r.endWithoutBody(false); - ctx.body.deinit(); - onError(@fieldParentPtr(field, ctx)); - } - - fn onInvalid(ctx: *@This(), r: AnyResponse) void { - r.writeStatus("400 Bad Request"); - r.endWithoutBody(false); - ctx.body.deinit(); - onError(@fieldParentPtr(field, ctx)); - } - }; -} +const bun = @import("bun"); +const Environment = bun.Environment; +const JSC = bun.JSC; diff --git a/src/deps/uws/App.zig b/src/deps/uws/App.zig new file mode 100644 index 0000000000..b4d47b8347 --- /dev/null +++ b/src/deps/uws/App.zig @@ -0,0 +1,459 @@ +pub fn NewApp(comptime ssl: bool) type { + // TODO: change to `opaque` when https://github.com/ziglang/zig/issues/22869 is fixed + // This file provides Zig bindings for the uWebSockets App class. + // It wraps the C API exposed in libuwsockets.cpp which provides a C interface + // to the C++ uWebSockets library defined in App.h. + // + // The architecture is: + // 1. App.h - C++ uWebSockets library with TemplatedApp class + // - Defines the main TemplatedApp template class + // - Provides HTTP/WebSocket server functionality with SSL/non-SSL variants + // - Contains WebSocketBehavior struct for configuring WebSocket handlers + // - Implements routing methods (get, post, put, delete, etc.) + // - Manages WebSocket contexts, topic trees for pub/sub, and compression + // - Handles server name (SNI) support for SSL contexts + // - Provides listen() methods for binding to ports/unix sockets + // + // 2. libuwsockets.cpp - C wrapper functions that call the C++ methods + // - Exposes C functions like uws_create_app(), uws_app_get(), etc. + // - Handles SSL/non-SSL branching with if(ssl) checks + // - Converts between C types (char*, size_t) and C++ types (string_view) + // - Manages memory and object lifetime for C callers + // - Provides callback wrappers that convert C function pointers to C++ lambdas + // - Functions like uws_app_connect(), uws_app_trace() mirror C++ methods + // + // 3. App.zig - Zig bindings that call the C wrapper functions + // - NewApp() function returns a generic struct parameterized by SSL boolean + // - Methods like create(), destroy(), close() call corresponding C functions + // - Type-safe wrappers around raw C pointers and function calls + // - Converts Zig slices to C pointer/length pairs + // - Provides compile-time SSL flag selection via @intFromBool(ssl) + // - RouteHandler() provides type-safe callback mechanism for HTTP routes + // + // This layered approach allows Zig code to use high-performance uWebSockets + // functionality while maintaining memory safety and Zig's type system benefits. + // The C layer handles the impedance mismatch between Zig and C++, while the + // Zig layer provides idiomatic APIs for Zig developers. + return struct { + pub const is_ssl = ssl; + const ssl_flag: i32 = @intFromBool(ssl); + const ThisApp = @This(); + + pub fn close(this: *ThisApp) void { + return c.uws_app_close(ssl_flag, @as(*uws_app_s, @ptrCast(this))); + } + + pub fn create(opts: BunSocketContextOptions) ?*ThisApp { + return @ptrCast(c.uws_create_app(ssl_flag, opts)); + } + pub fn destroy(app: *ThisApp) void { + return c.uws_app_destroy(ssl_flag, @as(*uws_app_s, @ptrCast(app))); + } + + pub fn setFlags(this: *ThisApp, require_host_header: bool, use_strict_method_validation: bool) void { + return c.uws_app_set_flags(ssl_flag, @as(*uws_app_t, @ptrCast(this)), require_host_header, use_strict_method_validation); + } + + pub fn setMaxHTTPHeaderSize(this: *ThisApp, max_header_size: u64) void { + return c.uws_app_set_max_http_header_size(ssl_flag, @as(*uws_app_t, @ptrCast(this)), max_header_size); + } + + pub fn clearRoutes(app: *ThisApp) void { + return c.uws_app_clear_routes(ssl_flag, @as(*uws_app_t, @ptrCast(app))); + } + + pub fn publishWithOptions(app: *ThisApp, topic: []const u8, message: []const u8, opcode: Opcode, compress: bool) bool { + return c.uws_publish( + @intFromBool(ssl), + @ptrCast(app), + topic.ptr, + topic.len, + message.ptr, + message.len, + opcode, + compress, + ); + } + + fn RouteHandler(comptime UserDataType: type, comptime handler: fn (UserDataType, *Request, *Response) void) type { + return struct { + pub fn handle(res: *uws.uws_res, req: *Request, user_data: ?*anyopaque) callconv(.C) void { + if (comptime UserDataType == void) { + return @call( + .always_inline, + handler, + .{ + {}, + req, + @as(*Response, @ptrCast(@alignCast(res))), + }, + ); + } else { + return @call( + .always_inline, + handler, + .{ + @as(UserDataType, @ptrCast(@alignCast(user_data.?))), + req, + @as(*Response, @ptrCast(@alignCast(res))), + }, + ); + } + } + }; + } + + pub const ListenSocket = opaque { + pub inline fn close(this: *ThisApp.ListenSocket) void { + return @as(*uws.ListenSocket, @ptrCast(this)).close(ssl); + } + pub inline fn getLocalPort(this: *ThisApp.ListenSocket) i32 { + return @as(*uws.ListenSocket, @ptrCast(this)).getLocalPort(ssl); + } + + pub fn socket(this: *ThisApp.ListenSocket) uws.NewSocketHandler(ssl) { + return uws.NewSocketHandler(ssl).from(@ptrCast(this)); + } + }; + + pub fn get( + app: *ThisApp, + pattern: []const u8, + comptime UserDataType: type, + user_data: UserDataType, + comptime handler: (fn (UserDataType, *Request, *Response) void), + ) void { + c.uws_app_get(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); + } + pub fn post( + app: *ThisApp, + pattern: []const u8, + comptime UserDataType: type, + user_data: UserDataType, + comptime handler: (fn (UserDataType, *Request, *Response) void), + ) void { + c.uws_app_post(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); + } + pub fn options( + app: *ThisApp, + pattern: []const u8, + comptime UserDataType: type, + user_data: UserDataType, + comptime handler: (fn (UserDataType, *Request, *Response) void), + ) void { + c.uws_app_options(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); + } + pub fn delete( + app: *ThisApp, + pattern: []const u8, + comptime UserDataType: type, + user_data: UserDataType, + comptime handler: (fn (UserDataType, *Request, *Response) void), + ) void { + c.uws_app_delete(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); + } + pub fn patch( + app: *ThisApp, + pattern: []const u8, + comptime UserDataType: type, + user_data: UserDataType, + comptime handler: (fn (UserDataType, *Request, *Response) void), + ) void { + c.uws_app_patch(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); + } + pub fn put( + app: *ThisApp, + pattern: []const u8, + comptime UserDataType: type, + user_data: UserDataType, + comptime handler: (fn (UserDataType, *Request, *Response) void), + ) void { + c.uws_app_put(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); + } + pub fn head( + app: *ThisApp, + pattern: []const u8, + comptime UserDataType: type, + user_data: UserDataType, + comptime handler: (fn (UserDataType, *Request, *Response) void), + ) void { + c.uws_app_head(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); + } + pub fn connect( + app: *ThisApp, + pattern: []const u8, + comptime UserDataType: type, + user_data: UserDataType, + comptime handler: (fn (UserDataType, *Request, *Response) void), + ) void { + c.uws_app_connect(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); + } + pub fn trace( + app: *ThisApp, + pattern: []const u8, + comptime UserDataType: type, + user_data: UserDataType, + comptime handler: (fn (UserDataType, *Request, *Response) void), + ) void { + c.uws_app_trace(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); + } + pub fn method( + app: *ThisApp, + method_: bun.http.Method, + pattern: []const u8, + comptime UserDataType: type, + user_data: UserDataType, + comptime handler: (fn (UserDataType, *Request, *Response) void), + ) void { + switch (method_) { + .GET => app.get(pattern, UserDataType, user_data, handler), + .POST => app.post(pattern, UserDataType, user_data, handler), + .PUT => app.put(pattern, UserDataType, user_data, handler), + .DELETE => app.delete(pattern, UserDataType, user_data, handler), + .PATCH => app.patch(pattern, UserDataType, user_data, handler), + .OPTIONS => app.options(pattern, UserDataType, user_data, handler), + .HEAD => app.head(pattern, UserDataType, user_data, handler), + .CONNECT => app.connect(pattern, UserDataType, user_data, handler), + .TRACE => app.trace(pattern, UserDataType, user_data, handler), + else => {}, + } + } + pub fn any( + app: *ThisApp, + pattern: []const u8, + comptime UserDataType: type, + user_data: UserDataType, + comptime handler: (fn (UserDataType, *Request, *Response) void), + ) void { + c.uws_app_any(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data); + } + pub fn domain(app: *ThisApp, pattern: [:0]const u8) void { + c.uws_app_domain(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern); + } + pub fn run(app: *ThisApp) void { + return c.uws_app_run(ssl_flag, @as(*uws_app_t, @ptrCast(app))); + } + pub fn listen( + app: *ThisApp, + port: i32, + comptime UserData: type, + user_data: UserData, + comptime handler: fn (UserData, ?*ThisApp.ListenSocket, c.uws_app_listen_config_t) void, + ) void { + const Wrapper = struct { + pub fn handle(socket: ?*uws.ListenSocket, conf: c.uws_app_listen_config_t, data: ?*anyopaque) callconv(.C) void { + if (comptime UserData == void) { + @call(bun.callmod_inline, handler, .{ {}, @as(?*ThisApp.ListenSocket, @ptrCast(socket)), conf }); + } else { + @call(bun.callmod_inline, handler, .{ + @as(UserData, @ptrCast(@alignCast(data.?))), + @as(?*ThisApp.ListenSocket, @ptrCast(socket)), + conf, + }); + } + } + }; + return c.uws_app_listen(ssl_flag, @as(*uws_app_t, @ptrCast(app)), port, Wrapper.handle, user_data); + } + + pub fn onClientError( + app: *ThisApp, + comptime UserData: type, + user_data: UserData, + comptime handler: fn (data: UserData, socket: *us_socket_t, error_code: u8, rawPacket: []const u8) void, + ) void { + const Wrapper = struct { + pub fn handle(data: *anyopaque, _: c_int, socket: *us_socket_t, error_code: u8, raw_packet: ?[*]u8, raw_packet_length: c_int) callconv(.C) void { + @call(bun.callmod_inline, handler, .{ + @as(UserData, @ptrCast(@alignCast(data))), + socket, + error_code, + if (raw_packet) |bytes| bytes[0..(@max(raw_packet_length, 0))] else "", + }); + } + }; + return c.uws_app_set_on_clienterror(ssl_flag, @ptrCast(app), Wrapper.handle, @ptrCast(user_data)); + } + + pub fn listenWithConfig( + app: *ThisApp, + comptime UserData: type, + user_data: UserData, + comptime handler: fn (UserData, ?*ThisApp.ListenSocket) void, + config: c.uws_app_listen_config_t, + ) void { + const Wrapper = struct { + pub fn handle(socket: ?*uws.ListenSocket, data: ?*anyopaque) callconv(.C) void { + if (comptime UserData == void) { + @call(bun.callmod_inline, handler, .{ {}, @as(?*ThisApp.ListenSocket, @ptrCast(socket)) }); + } else { + @call(bun.callmod_inline, handler, .{ + @as(UserData, @ptrCast(@alignCast(data.?))), + @as(?*ThisApp.ListenSocket, @ptrCast(socket)), + }); + } + } + }; + return c.uws_app_listen_with_config(ssl_flag, @as(*uws_app_t, @ptrCast(app)), config.host, @as(u16, @intCast(config.port)), config.options, Wrapper.handle, user_data); + } + + pub fn listenOnUnixSocket( + app: *ThisApp, + comptime UserData: type, + user_data: UserData, + comptime handler: fn (UserData, ?*ThisApp.ListenSocket) void, + domain_name: [:0]const u8, + flags: i32, + ) void { + const Wrapper = struct { + pub fn handle(socket: ?*uws.ListenSocket, _: [*:0]const u8, _: i32, data: *anyopaque) callconv(.C) void { + if (comptime UserData == void) { + @call(bun.callmod_inline, handler, .{ {}, @as(?*ThisApp.ListenSocket, @ptrCast(socket)) }); + } else { + @call(bun.callmod_inline, handler, .{ + @as(UserData, @ptrCast(@alignCast(data))), + @as(?*ThisApp.ListenSocket, @ptrCast(socket)), + }); + } + } + }; + return c.uws_app_listen_domain_with_options( + ssl_flag, + @as(*uws_app_t, @ptrCast(app)), + domain_name.ptr, + domain_name.len, + flags, + Wrapper.handle, + user_data, + ); + } + + pub fn constructorFailed(app: *ThisApp) bool { + return c.uws_constructor_failed(ssl_flag, app); + } + pub fn numSubscribers(app: *ThisApp, topic: []const u8) u32 { + return c.uws_num_subscribers(ssl_flag, @as(*uws_app_t, @ptrCast(app)), topic.ptr, topic.len); + } + pub fn publish(app: *ThisApp, topic: []const u8, message: []const u8, opcode: Opcode, compress: bool) bool { + return c.uws_publish(ssl_flag, @as(*uws_app_t, @ptrCast(app)), topic.ptr, topic.len, message.ptr, message.len, opcode, compress); + } + pub fn getNativeHandle(app: *ThisApp) ?*anyopaque { + return c.uws_get_native_handle(ssl_flag, app); + } + pub fn removeServerName(app: *ThisApp, hostname_pattern: [*:0]const u8) void { + return c.uws_remove_server_name(ssl_flag, @as(*uws_app_t, @ptrCast(app)), hostname_pattern); + } + pub fn addServerName(app: *ThisApp, hostname_pattern: [*:0]const u8) void { + return c.uws_add_server_name(ssl_flag, @as(*uws_app_t, @ptrCast(app)), hostname_pattern); + } + pub fn addServerNameWithOptions(app: *ThisApp, hostname_pattern: [*:0]const u8, opts: BunSocketContextOptions) !void { + if (c.uws_add_server_name_with_options(ssl_flag, @as(*uws_app_t, @ptrCast(app)), hostname_pattern, opts) != 0) { + return error.FailedToAddServerName; + } + } + pub fn missingServerName(app: *ThisApp, handler: c.uws_missing_server_handler, user_data: ?*anyopaque) void { + return c.uws_missing_server_name(ssl_flag, @as(*uws_app_t, @ptrCast(app)), handler, user_data); + } + pub fn filter(app: *ThisApp, handler: c.uws_filter_handler, user_data: ?*anyopaque) void { + return c.uws_filter(ssl_flag, @as(*uws_app_t, @ptrCast(app)), handler, user_data); + } + pub fn ws(app: *ThisApp, pattern: []const u8, ctx: *anyopaque, id: usize, behavior_: WebSocketBehavior) void { + var behavior = behavior_; + uws_ws(ssl_flag, @as(*uws_app_t, @ptrCast(app)), ctx, pattern.ptr, pattern.len, id, &behavior); + } + + /// HTTP response object for handling HTTP responses. + /// + /// This wraps the uWS HttpResponse template class from HttpResponse.h, providing + /// methods for writing response data, setting headers, handling timeouts, and + /// managing the response lifecycle. The response object supports both regular + /// HTTP responses and chunked transfer encoding, and can handle large data + /// writes by automatically splitting them into appropriately sized chunks. + /// + /// Key features: + /// - Write response data with automatic chunking for large payloads + /// - Set HTTP status codes and headers + /// - Handle response timeouts and aborted requests + /// - Support for WebSocket upgrades + /// - Cork/uncork functionality for efficient batched writes + /// - Automatic handling of Connection: close semantics + pub const Response = @import("./Response.zig").NewResponse(ssl_flag); + pub const WebSocket = @import("./WebSocket.zig").NewWebSocket(ssl_flag); + const uws_ws = @import("./WebSocket.zig").c.uws_ws; + }; +} + +pub const uws_app_s = opaque {}; +pub const uws_app_t = uws_app_s; + +pub const c = struct { + pub const uws_listen_handler = ?*const fn (?*uws.ListenSocket, ?*anyopaque) callconv(.C) void; + pub const uws_method_handler = ?*const fn (*uws.uws_res, *Request, ?*anyopaque) callconv(.C) void; + pub const uws_filter_handler = ?*const fn (*uws.uws_res, i32, ?*anyopaque) callconv(.C) void; + pub const uws_missing_server_handler = ?*const fn ([*c]const u8, ?*anyopaque) callconv(.C) void; + + pub extern fn uws_app_close(ssl: i32, app: *uws_app_s) void; + pub extern fn uws_app_set_on_clienterror(ssl: c_int, app: *uws_app_s, handler: *const fn (*anyopaque, c_int, *us_socket_t, u8, ?[*]u8, c_int) callconv(.C) void, user_data: *anyopaque) void; + pub extern fn uws_create_app(ssl: i32, options: BunSocketContextOptions) ?*uws_app_t; + pub extern fn uws_app_destroy(ssl: i32, app: *uws_app_t) void; + pub extern fn uws_app_set_flags(ssl: i32, app: *uws_app_t, require_host_header: bool, use_strict_method_validation: bool) void; + pub extern fn uws_app_set_max_http_header_size(ssl: i32, app: *uws_app_t, max_header_size: u64) void; + pub extern fn uws_app_get(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; + pub extern fn uws_app_post(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; + pub extern fn uws_app_options(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; + pub extern fn uws_app_delete(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; + pub extern fn uws_app_patch(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; + pub extern fn uws_app_put(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; + pub extern fn uws_app_head(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; + pub extern fn uws_app_connect(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; + pub extern fn uws_app_trace(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; + pub extern fn uws_app_any(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void; + pub extern fn uws_app_run(ssl: i32, *uws_app_t) void; + pub extern fn uws_app_domain(ssl: i32, app: *uws_app_t, domain: [*c]const u8) void; + pub extern fn uws_app_listen(ssl: i32, app: *uws_app_t, port: i32, handler: uws_listen_handler, user_data: ?*anyopaque) void; + pub extern fn uws_app_listen_with_config( + ssl: i32, + app: *uws_app_t, + host: [*c]const u8, + port: u16, + options: i32, + handler: uws_listen_handler, + user_data: ?*anyopaque, + ) void; + pub extern fn uws_constructor_failed(ssl: i32, app: *uws_app_t) bool; + pub extern fn uws_num_subscribers(ssl: i32, app: *uws_app_t, topic: [*c]const u8, topic_length: usize) c_uint; + pub extern fn uws_publish(ssl: i32, app: *uws_app_t, topic: [*c]const u8, topic_length: usize, message: [*c]const u8, message_length: usize, opcode: Opcode, compress: bool) bool; + pub extern fn uws_get_native_handle(ssl: i32, app: *anyopaque) ?*anyopaque; + pub extern fn uws_remove_server_name(ssl: i32, app: *uws_app_t, hostname_pattern: [*c]const u8) void; + pub extern fn uws_add_server_name(ssl: i32, app: *uws_app_t, hostname_pattern: [*c]const u8) void; + pub extern fn uws_add_server_name_with_options(ssl: i32, app: *uws_app_t, hostname_pattern: [*c]const u8, options: BunSocketContextOptions) i32; + pub extern fn uws_missing_server_name(ssl: i32, app: *uws_app_t, handler: uws_missing_server_handler, user_data: ?*anyopaque) void; + pub extern fn uws_filter(ssl: i32, app: *uws_app_t, handler: uws_filter_handler, user_data: ?*anyopaque) void; + + pub const uws_app_listen_config_t = extern struct { + port: c_int, + host: ?[*:0]const u8 = null, + options: c_int = 0, + }; + + pub extern fn uws_app_listen_domain_with_options( + ssl_flag: c_int, + app: *uws_app_t, + domain: [*:0]const u8, + pathlen: usize, + i32, + *const (fn (*ListenSocket, domain: [*:0]const u8, i32, *anyopaque) callconv(.C) void), + ?*anyopaque, + ) void; + + pub extern fn uws_app_clear_routes(ssl_flag: c_int, app: *uws_app_t) void; +}; + +const bun = @import("bun"); +const uws = bun.uws; +const Request = bun.uws.Request; +const Opcode = bun.uws.Opcode; +const WebSocketBehavior = bun.uws.WebSocketBehavior; +const ListenSocket = bun.uws.ListenSocket; +const us_socket_t = bun.uws.us_socket_t; +const BunSocketContextOptions = bun.uws.SocketContext.BunSocketContextOptions; diff --git a/src/deps/uws/BodyReaderMixin.zig b/src/deps/uws/BodyReaderMixin.zig new file mode 100644 index 0000000000..a9aacae716 --- /dev/null +++ b/src/deps/uws/BodyReaderMixin.zig @@ -0,0 +1,77 @@ +/// Mixin to read an entire request body into memory and run a callback. +/// Consumers should make sure a reference count is held on the server, +/// and is unreferenced after one of the two callbacks are called. +/// +/// See DevServer.zig's ErrorReportRequest for an example. +pub fn BodyReaderMixin( + Wrap: type, + field: []const u8, + // `body` is freed after this function returns. + onBody: fn (*Wrap, body: []const u8, resp: uws.AnyResponse) anyerror!void, + // Called on error or request abort + onError: fn (*Wrap) void, +) type { + return struct { + body: std.ArrayList(u8), + + pub fn init(allocator: std.mem.Allocator) @This() { + return .{ .body = .init(allocator) }; + } + + /// Memory is freed after the callback returns, or automatically on failure. + pub fn readBody(ctx: *@This(), resp: anytype) void { + const Mixin = @This(); + const Response = @TypeOf(resp); + const handlers = struct { + fn onDataGeneric(mixin: *Mixin, r: Response, chunk: []const u8, last: bool) void { + const any = uws.AnyResponse.init(r); + onData(mixin, any, chunk, last) catch |e| switch (e) { + error.OutOfMemory => return mixin.onOOM(any), + else => return mixin.onInvalid(any), + }; + } + fn onAborted(mixin: *Mixin, _: Response) void { + mixin.body.deinit(); + onError(@fieldParentPtr(field, mixin)); + } + }; + resp.onData(*@This(), handlers.onDataGeneric, ctx); + resp.onAborted(*@This(), handlers.onAborted, ctx); + } + + fn onData(ctx: *@This(), resp: uws.AnyResponse, chunk: []const u8, last: bool) !void { + if (last) { + var body = ctx.body; // stack copy so onBody can free everything + resp.clearAborted(); + resp.clearOnData(); + if (body.items.len > 0) { + try body.appendSlice(chunk); + try onBody(@fieldParentPtr(field, ctx), ctx.body.items, resp); + } else { + try onBody(@fieldParentPtr(field, ctx), chunk, resp); + } + body.deinit(); + } else { + try ctx.body.appendSlice(chunk); + } + } + + fn onOOM(ctx: *@This(), r: uws.AnyResponse) void { + r.writeStatus("500 Internal Server Error"); + r.endWithoutBody(false); + ctx.body.deinit(); + onError(@fieldParentPtr(field, ctx)); + } + + fn onInvalid(ctx: *@This(), r: uws.AnyResponse) void { + r.writeStatus("400 Bad Request"); + r.endWithoutBody(false); + ctx.body.deinit(); + onError(@fieldParentPtr(field, ctx)); + } + }; +} + +const bun = @import("bun"); +const uws = bun.uws; +const std = @import("std"); diff --git a/src/deps/uws/ConnectingSocket.zig b/src/deps/uws/ConnectingSocket.zig new file mode 100644 index 0000000000..da9bc585ea --- /dev/null +++ b/src/deps/uws/ConnectingSocket.zig @@ -0,0 +1,85 @@ +/// A ConnectingSocket represents a socket that is in the process of establishing a connection. +/// Corresponds to `us_connecting_socket_t` in uSockets. +/// +/// This is an intermediate state between initiating a connection and having a fully connected socket. +/// The socket may be in one of several states: +/// - Performing DNS resolution to resolve the hostname to an IP address +/// - Establishing a TCP connection (non-blocking connect() in progress) +/// - Performing TLS/SSL handshake if this is an SSL connection +/// - Waiting for the connection to be accepted by the remote peer +/// +/// Unlike a connected socket, you cannot read from or write to a ConnectingSocket. +/// Once the connection is successfully established, it will be converted to a regular +/// Socket and the appropriate callback (onOpen) will be triggered. If the connection +/// fails, the onConnectError callback will be called instead. +/// +/// This design allows for non-blocking connection establishment while maintaining +/// a clear separation between sockets that are connecting vs. those that are ready +/// for I/O operations. +pub const ConnectingSocket = opaque { + pub fn close(this: *ConnectingSocket, ssl: bool) void { + c.us_connecting_socket_close(@intFromBool(ssl), this); + } + + pub fn context(this: *ConnectingSocket, ssl: bool) ?*uws.SocketContext { + return c.us_connecting_socket_context(@intFromBool(ssl), this); + } + + pub fn loop(this: *ConnectingSocket) *uws.Loop { + return c.us_connecting_socket_get_loop(this); + } + + pub fn ext(this: *ConnectingSocket, ssl: bool) *anyopaque { + return c.us_connecting_socket_ext(@intFromBool(ssl), this); + } + + pub fn getError(this: *ConnectingSocket, ssl: bool) i32 { + return c.us_connecting_socket_get_error(@intFromBool(ssl), this); + } + + pub fn getNativeHandle(this: *ConnectingSocket, ssl: bool) ?*anyopaque { + return c.us_connecting_socket_get_native_handle(@intFromBool(ssl), this); + } + + pub fn isClosed(this: *ConnectingSocket, ssl: bool) bool { + return c.us_connecting_socket_is_closed(@intFromBool(ssl), this) == 1; + } + + pub fn isShutdown(this: *ConnectingSocket, ssl: bool) bool { + return c.us_connecting_socket_is_shut_down(@intFromBool(ssl), this) == 1; + } + + pub fn longTimeout(this: *ConnectingSocket, ssl: bool, seconds: c_uint) void { + c.us_connecting_socket_long_timeout(@intFromBool(ssl), this, seconds); + } + + pub fn shutdown(this: *ConnectingSocket, ssl: bool) void { + c.us_connecting_socket_shutdown(@intFromBool(ssl), this); + } + + pub fn shutdownRead(this: *ConnectingSocket, ssl: bool) void { + c.us_connecting_socket_shutdown_read(@intFromBool(ssl), this); + } + + pub fn timeout(this: *ConnectingSocket, ssl: bool, seconds: c_uint) void { + c.us_connecting_socket_timeout(@intFromBool(ssl), this, seconds); + } +}; + +const c = struct { + pub extern fn us_connecting_socket_close(ssl: i32, s: *ConnectingSocket) void; + pub extern fn us_connecting_socket_context(ssl: i32, s: *ConnectingSocket) ?*uws.SocketContext; + pub extern fn us_connecting_socket_ext(ssl: i32, s: *ConnectingSocket) *anyopaque; + pub extern fn us_connecting_socket_get_error(ssl: i32, s: *ConnectingSocket) i32; + pub extern fn us_connecting_socket_get_native_handle(ssl: i32, s: *ConnectingSocket) ?*anyopaque; + pub extern fn us_connecting_socket_is_closed(ssl: i32, s: *ConnectingSocket) i32; + pub extern fn us_connecting_socket_is_shut_down(ssl: i32, s: *ConnectingSocket) i32; + pub extern fn us_connecting_socket_long_timeout(ssl: i32, s: *ConnectingSocket, seconds: c_uint) void; + pub extern fn us_connecting_socket_shutdown(ssl: i32, s: *ConnectingSocket) void; + pub extern fn us_connecting_socket_shutdown_read(ssl: i32, s: *ConnectingSocket) void; + pub extern fn us_connecting_socket_timeout(ssl: i32, s: *ConnectingSocket, seconds: c_uint) void; + pub extern fn us_connecting_socket_get_loop(s: *ConnectingSocket) *uws.Loop; +}; + +const bun = @import("bun"); +const uws = bun.uws; diff --git a/src/deps/uws/InternalLoopData.zig b/src/deps/uws/InternalLoopData.zig new file mode 100644 index 0000000000..f78d2b46e6 --- /dev/null +++ b/src/deps/uws/InternalLoopData.zig @@ -0,0 +1,65 @@ +pub const InternalLoopData = extern struct { + pub const us_internal_async = opaque {}; + + sweep_timer: ?*Timer, + wakeup_async: ?*us_internal_async, + last_write_failed: i32, + head: ?*SocketContext, + iterator: ?*SocketContext, + closed_context_head: ?*SocketContext, + recv_buf: [*]u8, + send_buf: [*]u8, + ssl_data: ?*anyopaque, + pre_cb: ?*fn (?*Loop) callconv(.C) void, + post_cb: ?*fn (?*Loop) callconv(.C) void, + closed_udp_head: ?*udp.Socket, + closed_head: ?*us_socket_t, + low_prio_head: ?*us_socket_t, + low_prio_budget: i32, + dns_ready_head: *ConnectingSocket, + closed_connecting_head: *ConnectingSocket, + mutex: bun.Mutex.ReleaseImpl.Type, + parent_ptr: ?*anyopaque, + parent_tag: c_char, + iteration_nr: usize, + jsc_vm: ?*JSC.VM, + + pub fn recvSlice(this: *InternalLoopData) []u8 { + return this.recv_buf[0..LIBUS_RECV_BUFFER_LENGTH]; + } + + pub fn setParentEventLoop(this: *InternalLoopData, parent: JSC.EventLoopHandle) void { + switch (parent) { + .js => |ptr| { + this.parent_tag = 1; + this.parent_ptr = ptr; + }, + .mini => |ptr| { + this.parent_tag = 2; + this.parent_ptr = ptr; + }, + } + } + + pub fn getParent(this: *InternalLoopData) JSC.EventLoopHandle { + const parent = this.parent_ptr orelse @panic("Parent loop not set - pointer is null"); + return switch (this.parent_tag) { + 0 => @panic("Parent loop not set - tag is zero"), + 1 => .{ .js = bun.cast(*JSC.EventLoop, parent) }, + 2 => .{ .mini = bun.cast(*JSC.MiniEventLoop, parent) }, + else => @panic("Parent loop data corrupted - tag is invalid"), + }; + } + + const LIBUS_RECV_BUFFER_LENGTH = 524288; +}; + +const bun = @import("bun"); +const JSC = bun.JSC; +const uws = bun.uws; +const Timer = uws.Timer; +const SocketContext = uws.SocketContext; +const ConnectingSocket = uws.ConnectingSocket; +const udp = uws.udp; +const us_socket_t = uws.us_socket_t; +const Loop = uws.Loop; diff --git a/src/deps/uws/ListenSocket.zig b/src/deps/uws/ListenSocket.zig new file mode 100644 index 0000000000..334bc98e6c --- /dev/null +++ b/src/deps/uws/ListenSocket.zig @@ -0,0 +1,21 @@ +pub const ListenSocket = opaque { + pub fn close(this: *ListenSocket, ssl: bool) void { + c.us_listen_socket_close(@intFromBool(ssl), this); + } + pub fn getLocalAddress(this: *ListenSocket, ssl: bool, buf: []u8) ![]const u8 { + return this.getSocket().localAddress(ssl, buf); + } + pub fn getLocalPort(this: *ListenSocket, ssl: bool) i32 { + return this.getSocket().localPort(ssl); + } + pub fn getSocket(this: *ListenSocket) *uws.us_socket_t { + return @ptrCast(this); + } +}; + +const c = struct { + pub extern fn us_listen_socket_close(ssl: i32, ls: *ListenSocket) void; +}; + +const bun = @import("bun"); +const uws = bun.uws; diff --git a/src/deps/uws/Loop.zig b/src/deps/uws/Loop.zig new file mode 100644 index 0000000000..4525780560 --- /dev/null +++ b/src/deps/uws/Loop.zig @@ -0,0 +1,298 @@ +pub const PosixLoop = extern struct { + internal_loop_data: InternalLoopData align(16), + + /// Number of non-fallthrough polls in the loop + num_polls: i32, + + /// Number of ready polls this iteration + num_ready_polls: i32, + + /// Current index in list of ready polls + current_ready_poll: i32, + + /// Loop's own file descriptor + fd: i32, + + /// Number of polls owned by Bun + active: u32 = 0, + + /// The list of ready polls + ready_polls: [1024]EventType align(16), + + const EventType = switch (Environment.os) { + .linux => std.os.linux.epoll_event, + .mac => std.posix.system.kevent64_s, + // TODO: + .windows => *anyopaque, + else => @compileError("Unsupported OS"), + }; + + pub fn uncork(this: *PosixLoop) void { + c.uws_res_clear_corked_socket(this); + } + + pub fn iterationNumber(this: *const PosixLoop) u64 { + return this.internal_loop_data.iteration_nr; + } + + pub fn inc(this: *PosixLoop) void { + log("inc {d} + 1 = {d}", .{ this.num_polls, this.num_polls + 1 }); + this.num_polls += 1; + } + + pub fn dec(this: *PosixLoop) void { + log("dec {d} - 1 = {d}", .{ this.num_polls, this.num_polls - 1 }); + this.num_polls -= 1; + } + + pub fn ref(this: *PosixLoop) void { + log("ref {d} + 1 = {d} | {d} + 1 = {d}", .{ this.num_polls, this.num_polls + 1, this.active, this.active + 1 }); + this.num_polls += 1; + this.active += 1; + } + + pub fn unref(this: *PosixLoop) void { + log("unref {d} - 1 = {d} | {d} - 1 = {d}", .{ this.num_polls, this.num_polls - 1, this.active, this.active -| 1 }); + this.num_polls -= 1; + this.active -|= 1; + } + + pub fn isActive(this: *const Loop) bool { + return this.active > 0; + } + + // This exists as a method so that we can stick a debugger in here + pub fn addActive(this: *PosixLoop, value: u32) void { + log("add {d} + {d} = {d}", .{ this.active, value, this.active +| value }); + this.active +|= value; + } + + // This exists as a method so that we can stick a debugger in here + pub fn subActive(this: *PosixLoop, value: u32) void { + log("sub {d} - {d} = {d}", .{ this.active, value, this.active -| value }); + this.active -|= value; + } + + pub fn unrefCount(this: *PosixLoop, count: i32) void { + log("unref x {d}", .{count}); + this.num_polls -= count; + this.active -|= @as(u32, @intCast(count)); + } + + pub fn get() *Loop { + return c.uws_get_loop(); + } + + pub fn create(comptime Handler: anytype) *Loop { + return c.us_create_loop( + null, + Handler.wakeup, + if (@hasDecl(Handler, "pre")) Handler.pre else null, + if (@hasDecl(Handler, "post")) Handler.post else null, + 0, + ).?; + } + + pub fn wakeup(this: *PosixLoop) void { + return c.us_wakeup_loop(this); + } + + pub const wake = wakeup; + + pub fn tick(this: *PosixLoop) void { + c.us_loop_run_bun_tick(this, null); + } + + pub fn tickWithoutIdle(this: *PosixLoop) void { + const timespec = bun.timespec{ .sec = 0, .nsec = 0 }; + c.us_loop_run_bun_tick(this, ×pec); + } + + pub fn tickWithTimeout(this: *PosixLoop, timespec: ?*const bun.timespec) void { + c.us_loop_run_bun_tick(this, timespec); + } + + pub fn nextTick(this: *PosixLoop, comptime UserType: type, user_data: UserType, comptime deferCallback: fn (ctx: UserType) void) void { + const Handler = struct { + pub fn callback(data: *anyopaque) callconv(.C) void { + deferCallback(@as(UserType, @ptrCast(@alignCast(data)))); + } + }; + c.uws_loop_defer(this, user_data, Handler.callback); + } + + fn NewHandler(comptime UserType: type, comptime callback_fn: fn (UserType) void) type { + return struct { + loop: *Loop, + pub fn removePost(handler: @This()) void { + return c.uws_loop_removePostHandler(handler.loop, callback); + } + pub fn removePre(handler: @This()) void { + return c.uws_loop_removePostHandler(handler.loop, callback); + } + pub fn callback(data: *anyopaque, _: *Loop) callconv(.C) void { + callback_fn(@as(UserType, @ptrCast(@alignCast(data)))); + } + }; + } + + pub fn addPostHandler(this: *PosixLoop, comptime UserType: type, ctx: UserType, comptime callback: fn (UserType) void) NewHandler(UserType, callback) { + const Handler = NewHandler(UserType, callback); + + c.uws_loop_addPostHandler(this, ctx, Handler.callback); + return Handler{ + .loop = this, + }; + } + + pub fn addPreHandler(this: *PosixLoop, comptime UserType: type, ctx: UserType, comptime callback: fn (UserType) void) NewHandler(UserType, callback) { + const Handler = NewHandler(UserType, callback); + + c.uws_loop_addPreHandler(this, ctx, Handler.callback); + return Handler{ + .loop = this, + }; + } + + pub fn run(this: *PosixLoop) void { + c.us_loop_run(this); + } +}; + +pub const WindowsLoop = extern struct { + const uv = bun.windows.libuv; + + internal_loop_data: InternalLoopData align(16), + + uv_loop: *uv.Loop, + is_default: c_int, + pre: *uv.uv_prepare_t, + check: *uv.uv_check_t, + + pub fn uncork(this: *PosixLoop) void { + c.uws_res_clear_corked_socket(this); + } + + pub fn get() *WindowsLoop { + return c.uws_get_loop_with_native(bun.windows.libuv.Loop.get()); + } + + pub fn iterationNumber(this: *const WindowsLoop) u64 { + return this.internal_loop_data.iteration_nr; + } + + pub fn addActive(this: *const WindowsLoop, val: u32) void { + this.uv_loop.addActive(val); + } + + pub fn subActive(this: *const WindowsLoop, val: u32) void { + this.uv_loop.subActive(val); + } + + pub fn isActive(this: *const WindowsLoop) bool { + return this.uv_loop.isActive(); + } + + pub fn wakeup(this: *WindowsLoop) void { + c.us_wakeup_loop(this); + } + + pub const wake = wakeup; + + pub fn tickWithTimeout(this: *WindowsLoop, _: ?*const bun.timespec) void { + c.us_loop_run(this); + } + + pub fn tickWithoutIdle(this: *WindowsLoop) void { + c.us_loop_pump(this); + } + + pub fn create(comptime Handler: anytype) *WindowsLoop { + return c.us_create_loop( + null, + Handler.wakeup, + if (@hasDecl(Handler, "pre")) Handler.pre else null, + if (@hasDecl(Handler, "post")) Handler.post else null, + 0, + ).?; + } + + pub fn run(this: *WindowsLoop) void { + c.us_loop_run(this); + } + + // TODO: remove these two aliases + pub const tick = run; + pub const wait = run; + + pub fn inc(this: *WindowsLoop) void { + this.uv_loop.inc(); + } + + pub fn dec(this: *WindowsLoop) void { + this.uv_loop.dec(); + } + + pub const ref = inc; + pub const unref = dec; + + pub fn nextTick(this: *Loop, comptime UserType: type, user_data: UserType, comptime deferCallback: fn (ctx: UserType) void) void { + const Handler = struct { + pub fn callback(data: *anyopaque) callconv(.C) void { + deferCallback(@as(UserType, @ptrCast(@alignCast(data)))); + } + }; + c.uws_loop_defer(this, user_data, Handler.callback); + } + + fn NewHandler(comptime UserType: type, comptime callback_fn: fn (UserType) void) type { + return struct { + loop: *Loop, + pub fn removePost(handler: @This()) void { + return c.uws_loop_removePostHandler(handler.loop, callback); + } + pub fn removePre(handler: @This()) void { + return c.uws_loop_removePostHandler(handler.loop, callback); + } + pub fn callback(data: *anyopaque, _: *Loop) callconv(.C) void { + callback_fn(@as(UserType, @ptrCast(@alignCast(data)))); + } + }; + } +}; + +pub const Loop = if (bun.Environment.isWindows) WindowsLoop else PosixLoop; + +const c = struct { + pub extern fn us_create_loop( + hint: ?*anyopaque, + wakeup_cb: ?*const fn (*Loop) callconv(.C) void, + pre_cb: ?*const fn (*Loop) callconv(.C) void, + post_cb: ?*const fn (*Loop) callconv(.C) void, + ext_size: c_uint, + ) ?*Loop; + pub extern fn us_loop_free(loop: ?*Loop) void; + pub extern fn us_loop_ext(loop: ?*Loop) ?*anyopaque; + pub extern fn us_loop_run(loop: ?*Loop) void; + pub extern fn us_loop_pump(loop: ?*Loop) void; + pub extern fn us_wakeup_loop(loop: ?*Loop) void; + pub extern fn us_loop_integrate(loop: ?*Loop) void; + pub extern fn us_loop_iteration_number(loop: ?*Loop) c_longlong; + pub extern fn uws_loop_addPostHandler(loop: *Loop, ctx: *anyopaque, cb: *const (fn (ctx: *anyopaque, loop: *Loop) callconv(.C) void)) void; + pub extern fn uws_loop_removePostHandler(loop: *Loop, ctx: *anyopaque, cb: *const (fn (ctx: *anyopaque, loop: *Loop) callconv(.C) void)) void; + pub extern fn uws_loop_addPreHandler(loop: *Loop, ctx: *anyopaque, cb: *const (fn (ctx: *anyopaque, loop: *Loop) callconv(.C) void)) void; + pub extern fn uws_loop_removePreHandler(loop: *Loop, ctx: *anyopaque, cb: *const (fn (ctx: *anyopaque, loop: *Loop) callconv(.C) void)) void; + pub extern fn us_loop_run_bun_tick(loop: ?*Loop, timouetMs: ?*const bun.timespec) void; + pub extern fn uws_get_loop() *Loop; + pub extern fn uws_get_loop_with_native(*anyopaque) *WindowsLoop; + pub extern fn uws_loop_defer(loop: *Loop, ctx: *anyopaque, cb: *const (fn (ctx: *anyopaque) callconv(.C) void)) void; + pub extern fn uws_res_clear_corked_socket(loop: *Loop) void; +}; + +const bun = @import("bun"); +const uws = bun.uws; + +const InternalLoopData = uws.InternalLoopData; +const Environment = bun.Environment; +const std = @import("std"); +const log = bun.Output.scoped(.Loop, false); diff --git a/src/deps/uws/Request.zig b/src/deps/uws/Request.zig new file mode 100644 index 0000000000..b0b40080c3 --- /dev/null +++ b/src/deps/uws/Request.zig @@ -0,0 +1,50 @@ +/// uWS::Request C++ -> Zig bindings. +pub const Request = opaque { + pub fn isAncient(req: *Request) bool { + return c.uws_req_is_ancient(req); + } + pub fn getYield(req: *Request) bool { + return c.uws_req_get_yield(req); + } + pub fn setYield(req: *Request, yield: bool) void { + c.uws_req_set_yield(req, yield); + } + pub fn url(req: *Request) []const u8 { + var ptr: [*]const u8 = undefined; + return ptr[0..c.uws_req_get_url(req, &ptr)]; + } + pub fn method(req: *Request) []const u8 { + var ptr: [*]const u8 = undefined; + return ptr[0..c.uws_req_get_method(req, &ptr)]; + } + pub fn header(req: *Request, name: []const u8) ?[]const u8 { + bun.assert(std.ascii.isLower(name[0])); + + var ptr: [*]const u8 = undefined; + const len = c.uws_req_get_header(req, name.ptr, name.len, &ptr); + if (len == 0) return null; + return ptr[0..len]; + } + pub fn query(req: *Request, name: []const u8) []const u8 { + var ptr: [*]const u8 = undefined; + return ptr[0..c.uws_req_get_query(req, name.ptr, name.len, &ptr)]; + } + pub fn parameter(req: *Request, index: u16) []const u8 { + var ptr: [*]const u8 = undefined; + return ptr[0..c.uws_req_get_parameter(req, @as(c_ushort, @intCast(index)), &ptr)]; + } +}; + +const c = struct { + pub extern fn uws_req_is_ancient(res: *Request) bool; + pub extern fn uws_req_get_yield(res: *Request) bool; + pub extern fn uws_req_set_yield(res: *Request, yield: bool) void; + pub extern fn uws_req_get_url(res: *Request, dest: *[*]const u8) usize; + pub extern fn uws_req_get_method(res: *Request, dest: *[*]const u8) usize; + pub extern fn uws_req_get_header(res: *Request, lower_case_header: [*]const u8, lower_case_header_length: usize, dest: *[*]const u8) usize; + pub extern fn uws_req_get_query(res: *Request, key: [*c]const u8, key_length: usize, dest: *[*]const u8) usize; + pub extern fn uws_req_get_parameter(res: *Request, index: c_ushort, dest: *[*]const u8) usize; +}; + +const bun = @import("bun"); +const std = @import("std"); diff --git a/src/deps/uws/Response.zig b/src/deps/uws/Response.zig new file mode 100644 index 0000000000..2ac2306846 --- /dev/null +++ b/src/deps/uws/Response.zig @@ -0,0 +1,642 @@ +/// Zig wrapper around uws::Response from µWebSockets. +/// +/// This provides a type-safe Zig interface to the underlying C++ uws::Response template. +/// The `ssl_flag` parameter determines whether this wraps uws::Response (SSL/TLS) +/// or uws::Response (plain HTTP). +/// +/// The wrapper: +/// - Uses opaque types to hide the C++ implementation details +/// - Provides compile-time SSL/TLS specialization via the ssl_flag parameter +/// - Offers safe casting between Zig and C representations +/// - Maintains zero-cost abstractions over the underlying µWebSockets API +pub fn NewResponse(ssl_flag: i32) type { + return opaque { + const Response = @This(); + const ssl = ssl_flag == 1; + + pub inline fn castRes(res: *c.uws_res) *Response { + return @as(*Response, @ptrCast(@alignCast(res))); + } + + pub inline fn downcast(res: *Response) *c.uws_res { + return @as(*c.uws_res, @ptrCast(@alignCast(res))); + } + + pub fn end(res: *Response, data: []const u8, close_connection: bool) void { + c.uws_res_end(ssl_flag, res.downcast(), data.ptr, data.len, close_connection); + } + + pub fn tryEnd(res: *Response, data: []const u8, total: usize, close_: bool) bool { + return c.uws_res_try_end(ssl_flag, res.downcast(), data.ptr, data.len, total, close_); + } + + pub fn flushHeaders(res: *Response) void { + c.uws_res_flush_headers(ssl_flag, res.downcast()); + } + + pub fn state(res: *const Response) State { + return c.uws_res_state(ssl_flag, @as(*const c.uws_res, @ptrCast(@alignCast(res)))); + } + + pub fn shouldCloseConnection(this: *const Response) bool { + return this.state().isHttpConnectionClose(); + } + + pub fn prepareForSendfile(res: *Response) void { + return c.uws_res_prepare_for_sendfile(ssl_flag, res.downcast()); + } + + pub fn uncork(_: *Response) void { + // c.uws_res_uncork( + // ssl_flag, + // res.downcast(), + // ); + } + pub fn pause(res: *Response) void { + c.uws_res_pause(ssl_flag, res.downcast()); + } + pub fn @"resume"(res: *Response) void { + c.uws_res_resume(ssl_flag, res.downcast()); + } + pub fn writeContinue(res: *Response) void { + c.uws_res_write_continue(ssl_flag, res.downcast()); + } + pub fn writeStatus(res: *Response, status: []const u8) void { + c.uws_res_write_status(ssl_flag, res.downcast(), status.ptr, status.len); + } + pub fn writeHeader(res: *Response, key: []const u8, value: []const u8) void { + c.uws_res_write_header(ssl_flag, res.downcast(), key.ptr, key.len, value.ptr, value.len); + } + pub fn writeHeaderInt(res: *Response, key: []const u8, value: u64) void { + c.uws_res_write_header_int(ssl_flag, res.downcast(), key.ptr, key.len, value); + } + pub fn endWithoutBody(res: *Response, close_connection: bool) void { + c.uws_res_end_without_body(ssl_flag, res.downcast(), close_connection); + } + pub fn endSendFile(res: *Response, write_offset: u64, close_connection: bool) void { + c.uws_res_end_sendfile(ssl_flag, res.downcast(), write_offset, close_connection); + } + pub fn timeout(res: *Response, seconds: u8) void { + c.uws_res_timeout(ssl_flag, res.downcast(), seconds); + } + pub fn resetTimeout(res: *Response) void { + c.uws_res_reset_timeout(ssl_flag, res.downcast()); + } + pub fn getBufferedAmount(res: *Response) u64 { + return c.uws_res_get_buffered_amount(ssl_flag, res.downcast()); + } + pub fn write(res: *Response, data: []const u8) WriteResult { + var len: usize = data.len; + return switch (c.uws_res_write(ssl_flag, res.downcast(), data.ptr, &len)) { + true => .{ .want_more = len }, + false => .{ .backpressure = len }, + }; + } + pub fn getWriteOffset(res: *Response) u64 { + return c.uws_res_get_write_offset(ssl_flag, res.downcast()); + } + pub fn overrideWriteOffset(res: *Response, offset: anytype) void { + c.uws_res_override_write_offset(ssl_flag, res.downcast(), @as(u64, @intCast(offset))); + } + pub fn hasResponded(res: *Response) bool { + return c.uws_res_has_responded(ssl_flag, res.downcast()); + } + + pub fn getNativeHandle(res: *Response) bun.FileDescriptor { + if (comptime Environment.isWindows) { + // on windows uSockets exposes SOCKET + return .fromNative(@ptrCast(c.uws_res_get_native_handle(ssl_flag, res.downcast()))); + } + + return .fromNative(@intCast(@intFromPtr(c.uws_res_get_native_handle(ssl_flag, res.downcast())))); + } + pub fn getRemoteAddressAsText(res: *Response) ?[]const u8 { + var buf: [*]const u8 = undefined; + const size = c.uws_res_get_remote_address_as_text(ssl_flag, res.downcast(), &buf); + return if (size > 0) buf[0..size] else null; + } + pub fn getRemoteSocketInfo(res: *Response) ?SocketAddress { + var address = SocketAddress{ + .ip = undefined, + .port = undefined, + .is_ipv6 = undefined, + }; + // This function will fill in the slots and return len. + // if len is zero it will not fill in the slots so it is ub to + // return the struct in that case. + address.ip.len = c.uws_res_get_remote_address_info( + res.downcast(), + &address.ip.ptr, + &address.port, + &address.is_ipv6, + ); + return if (address.ip.len > 0) address else null; + } + pub fn onWritable( + res: *Response, + comptime UserDataType: type, + comptime handler: fn (UserDataType, u64, *Response) bool, + user_data: UserDataType, + ) void { + const Wrapper = struct { + pub fn handle(this: *c.uws_res, amount: u64, data: ?*anyopaque) callconv(.C) bool { + if (comptime UserDataType == void) { + return @call(bun.callmod_inline, handler, .{ {}, amount, castRes(this) }); + } else { + return @call(bun.callmod_inline, handler, .{ + @as(UserDataType, @ptrCast(@alignCast(data.?))), + amount, + castRes(this), + }); + } + } + }; + c.uws_res_on_writable(ssl_flag, res.downcast(), Wrapper.handle, user_data); + } + + pub fn clearOnWritable(res: *Response) void { + c.uws_res_clear_on_writable(ssl_flag, res.downcast()); + } + pub inline fn markNeedsMore(res: *Response) void { + if (!ssl) { + c.us_socket_mark_needs_more_not_ssl(res.downcast()); + } + } + pub fn onAborted(res: *Response, comptime UserDataType: type, comptime handler: fn (UserDataType, *Response) void, optional_data: UserDataType) void { + const Wrapper = struct { + pub fn handle(this: *c.uws_res, user_data: ?*anyopaque) callconv(.C) void { + if (comptime UserDataType == void) { + @call(bun.callmod_inline, handler, .{ {}, castRes(this), {} }); + } else { + @call(bun.callmod_inline, handler, .{ @as(UserDataType, @ptrCast(@alignCast(user_data.?))), castRes(this) }); + } + } + }; + c.uws_res_on_aborted(ssl_flag, res.downcast(), Wrapper.handle, optional_data); + } + + pub fn clearAborted(res: *Response) void { + c.uws_res_on_aborted(ssl_flag, res.downcast(), null, null); + } + pub fn onTimeout(res: *Response, comptime UserDataType: type, comptime handler: fn (UserDataType, *Response) void, optional_data: UserDataType) void { + const Wrapper = struct { + pub fn handle(this: *c.uws_res, user_data: ?*anyopaque) callconv(.C) void { + if (comptime UserDataType == void) { + @call(bun.callmod_inline, handler, .{ {}, castRes(this) }); + } else { + @call(bun.callmod_inline, handler, .{ @as(UserDataType, @ptrCast(@alignCast(user_data.?))), castRes(this) }); + } + } + }; + c.uws_res_on_timeout(ssl_flag, res.downcast(), Wrapper.handle, optional_data); + } + + pub fn clearTimeout(res: *Response) void { + c.uws_res_on_timeout(ssl_flag, res.downcast(), null, null); + } + pub fn clearOnData(res: *Response) void { + c.uws_res_on_data(ssl_flag, res.downcast(), null, null); + } + + pub fn onData( + res: *Response, + comptime UserDataType: type, + comptime handler: fn (UserDataType, *Response, chunk: []const u8, last: bool) void, + optional_data: UserDataType, + ) void { + const Wrapper = struct { + const handler_fn = handler; + pub fn handle(this: *c.uws_res, chunk_ptr: [*c]const u8, len: usize, last: bool, user_data: ?*anyopaque) callconv(.C) void { + if (comptime UserDataType == void) { + @call(bun.callmod_inline, handler_fn, .{ + {}, + castRes(this), + if (len > 0) chunk_ptr[0..len] else "", + last, + }); + } else { + @call(bun.callmod_inline, handler_fn, .{ + @as(UserDataType, @ptrCast(@alignCast(user_data.?))), + castRes(this), + if (len > 0) chunk_ptr[0..len] else "", + last, + }); + } + } + }; + + c.uws_res_on_data(ssl_flag, res.downcast(), Wrapper.handle, optional_data); + } + + pub fn endStream(res: *Response, close_connection: bool) void { + c.uws_res_end_stream(ssl_flag, res.downcast(), close_connection); + } + + pub fn corked( + res: *Response, + comptime handler: anytype, + args_tuple: anytype, + ) void { + const Wrapper = struct { + const handler_fn = handler; + const Args = *@TypeOf(args_tuple); + pub fn handle(user_data: ?*anyopaque) callconv(.C) void { + const args: Args = @alignCast(@ptrCast(user_data.?)); + @call(.always_inline, handler_fn, args.*); + } + }; + + c.uws_res_cork(ssl_flag, res.downcast(), @constCast(@ptrCast(&args_tuple)), Wrapper.handle); + } + + pub fn runCorkedWithType( + res: *Response, + comptime UserDataType: type, + comptime handler: fn (UserDataType) void, + optional_data: UserDataType, + ) void { + const Wrapper = struct { + pub fn handle(user_data: ?*anyopaque) callconv(.C) void { + if (comptime UserDataType == void) { + @call(bun.callmod_inline, handler, .{ + {}, + }); + } else { + @call(bun.callmod_inline, handler, .{ + @as(UserDataType, @ptrCast(@alignCast(user_data.?))), + }); + } + } + }; + + c.uws_res_cork(ssl_flag, res.downcast(), optional_data, Wrapper.handle); + } + + pub fn upgrade( + res: *Response, + comptime Data: type, + data: Data, + sec_web_socket_key: []const u8, + sec_web_socket_protocol: []const u8, + sec_web_socket_extensions: []const u8, + ctx: ?*uws.SocketContext, + ) *Socket { + return c.uws_res_upgrade( + ssl_flag, + res.downcast(), + data, + sec_web_socket_key.ptr, + sec_web_socket_key.len, + sec_web_socket_protocol.ptr, + sec_web_socket_protocol.len, + sec_web_socket_extensions.ptr, + sec_web_socket_extensions.len, + ctx, + ); + } + }; +} + +pub const TCPResponse = NewResponse(0); +pub const TLSResponse = NewResponse(1); + +pub const AnyResponse = union(enum) { + SSL: *uws.NewApp(true).Response, + TCP: *uws.NewApp(false).Response, + + pub fn socket(this: AnyResponse) *c.uws_res { + return switch (this) { + inline else => |resp| resp.downcast(), + }; + } + pub fn getRemoteSocketInfo(this: AnyResponse) ?SocketAddress { + return switch (this) { + inline else => |resp| resp.getRemoteSocketInfo(), + }; + } + pub fn flushHeaders(this: AnyResponse) void { + return switch (this) { + inline else => |resp| resp.flushHeaders(), + }; + } + pub fn getWriteOffset(this: AnyResponse) u64 { + return switch (this) { + inline else => |resp| resp.getWriteOffset(), + }; + } + + pub fn getBufferedAmount(this: AnyResponse) u64 { + return switch (this) { + inline else => |resp| resp.getBufferedAmount(), + }; + } + + pub fn writeContinue(this: AnyResponse) void { + return switch (this) { + inline else => |resp| resp.writeContinue(), + }; + } + + pub fn state(this: AnyResponse) State { + return switch (this) { + inline else => |resp| resp.state(), + }; + } + + pub inline fn init(response: anytype) AnyResponse { + return switch (@TypeOf(response)) { + *uws.NewApp(true).Response => .{ .SSL = response }, + *uws.NewApp(false).Response => .{ .TCP = response }, + else => @compileError(unreachable), + }; + } + + pub fn timeout(this: AnyResponse, seconds: u8) void { + switch (this) { + inline else => |resp| resp.timeout(seconds), + } + } + + pub fn onData(this: AnyResponse, comptime UserDataType: type, comptime handler: fn (UserDataType, []const u8, bool) void, optional_data: UserDataType) void { + return switch (this) { + inline .SSL, .TCP => |resp, ssl| resp.onData(UserDataType, struct { + pub fn onDataCallback(user_data: UserDataType, _: *uws.NewApp(ssl == .SSL).Response, data: []const u8, last: bool) void { + @call(.always_inline, handler, .{ user_data, data, last }); + } + }.onDataCallback, optional_data), + }; + } + + pub fn writeStatus(this: AnyResponse, status: []const u8) void { + return switch (this) { + inline else => |resp| resp.writeStatus(status), + }; + } + + pub fn writeHeader(this: AnyResponse, key: []const u8, value: []const u8) void { + return switch (this) { + inline else => |resp| resp.writeHeader(key, value), + }; + } + + pub fn write(this: AnyResponse, data: []const u8) WriteResult { + return switch (this) { + inline else => |resp| resp.write(data), + }; + } + + pub fn end(this: AnyResponse, data: []const u8, close_connection: bool) void { + return switch (this) { + inline else => |resp| resp.end(data, close_connection), + }; + } + + pub fn shouldCloseConnection(this: AnyResponse) bool { + return switch (this) { + inline else => |resp| resp.shouldCloseConnection(), + }; + } + + pub fn tryEnd(this: AnyResponse, data: []const u8, total_size: usize, close_connection: bool) bool { + return switch (this) { + inline else => |resp| resp.tryEnd(data, total_size, close_connection), + }; + } + + pub fn pause(this: AnyResponse) void { + return switch (this) { + inline else => |resp| resp.pause(), + }; + } + + pub fn @"resume"(this: AnyResponse) void { + return switch (this) { + inline else => |resp| resp.@"resume"(), + }; + } + + pub fn writeHeaderInt(this: AnyResponse, key: []const u8, value: u64) void { + return switch (this) { + inline else => |resp| resp.writeHeaderInt(key, value), + }; + } + + pub fn endWithoutBody(this: AnyResponse, close_connection: bool) void { + return switch (this) { + inline else => |resp| resp.endWithoutBody(close_connection), + }; + } + + pub fn onWritable(this: AnyResponse, comptime UserDataType: type, comptime handler: fn (UserDataType, u64, AnyResponse) bool, optional_data: UserDataType) void { + const wrapper = struct { + pub fn ssl_handler(user_data: UserDataType, offset: u64, resp: *uws.NewApp(true).Response) bool { + return handler(user_data, offset, .{ .SSL = resp }); + } + + pub fn tcp_handler(user_data: UserDataType, offset: u64, resp: *uws.NewApp(false).Response) bool { + return handler(user_data, offset, .{ .TCP = resp }); + } + }; + return switch (this) { + .SSL => |resp| resp.onWritable(UserDataType, wrapper.ssl_handler, optional_data), + .TCP => |resp| resp.onWritable(UserDataType, wrapper.tcp_handler, optional_data), + }; + } + + pub fn onTimeout(this: AnyResponse, comptime UserDataType: type, comptime handler: fn (UserDataType, AnyResponse) void, optional_data: UserDataType) void { + const wrapper = struct { + pub fn ssl_handler(user_data: UserDataType, resp: *uws.NewApp(true).Response) void { + handler(user_data, .{ .SSL = resp }); + } + pub fn tcp_handler(user_data: UserDataType, resp: *uws.NewApp(false).Response) void { + handler(user_data, .{ .TCP = resp }); + } + }; + + return switch (this) { + .SSL => |resp| resp.onTimeout(UserDataType, wrapper.ssl_handler, optional_data), + .TCP => |resp| resp.onTimeout(UserDataType, wrapper.tcp_handler, optional_data), + }; + } + + pub fn onAborted(this: AnyResponse, comptime UserDataType: type, comptime handler: fn (UserDataType, AnyResponse) void, optional_data: UserDataType) void { + const wrapper = struct { + pub fn ssl_handler(user_data: UserDataType, resp: *uws.NewApp(true).Response) void { + handler(user_data, .{ .SSL = resp }); + } + pub fn tcp_handler(user_data: UserDataType, resp: *uws.NewApp(false).Response) void { + handler(user_data, .{ .TCP = resp }); + } + }; + return switch (this) { + .SSL => |resp| resp.onAborted(UserDataType, wrapper.ssl_handler, optional_data), + .TCP => |resp| resp.onAborted(UserDataType, wrapper.tcp_handler, optional_data), + }; + } + + pub fn clearAborted(this: AnyResponse) void { + return switch (this) { + inline else => |resp| resp.clearAborted(), + }; + } + pub fn clearTimeout(this: AnyResponse) void { + return switch (this) { + inline else => |resp| resp.clearTimeout(), + }; + } + + pub fn clearOnWritable(this: AnyResponse) void { + return switch (this) { + inline else => |resp| resp.clearOnWritable(), + }; + } + + pub fn clearOnData(this: AnyResponse) void { + return switch (this) { + inline else => |resp| resp.clearOnData(), + }; + } + + pub fn endStream(this: AnyResponse, close_connection: bool) void { + return switch (this) { + inline else => |resp| resp.endStream(close_connection), + }; + } + + pub fn corked(this: AnyResponse, comptime handler: anytype, args_tuple: anytype) void { + return switch (this) { + inline else => |resp| resp.corked(handler, args_tuple), + }; + } + + pub fn runCorkedWithType(this: AnyResponse, comptime UserDataType: type, comptime handler: fn (UserDataType) void, optional_data: UserDataType) void { + return switch (this) { + inline else => |resp| resp.runCorkedWithType(UserDataType, handler, optional_data), + }; + } + + pub fn upgrade( + this: AnyResponse, + comptime Data: type, + data: Data, + sec_web_socket_key: []const u8, + sec_web_socket_protocol: []const u8, + sec_web_socket_extensions: []const u8, + ctx: ?*uws.SocketContext, + ) *Socket { + return switch (this) { + inline else => |resp| resp.upgrade(Data, data, sec_web_socket_key, sec_web_socket_protocol, sec_web_socket_extensions, ctx), + }; + } +}; + +pub const State = enum(u8) { + HTTP_STATUS_CALLED = 1, + HTTP_WRITE_CALLED = 2, + HTTP_END_CALLED = 4, + HTTP_RESPONSE_PENDING = 8, + HTTP_CONNECTION_CLOSE = 16, + HTTP_WROTE_CONTENT_LENGTH_HEADER = 32, + + _, + + pub inline fn isResponsePending(this: State) bool { + return @intFromEnum(this) & @intFromEnum(State.HTTP_RESPONSE_PENDING) != 0; + } + + pub inline fn hasWrittenContentLengthHeader(this: State) bool { + return @intFromEnum(this) & @intFromEnum(State.HTTP_WROTE_CONTENT_LENGTH_HEADER) != 0; + } + + pub inline fn isHttpEndCalled(this: State) bool { + return @intFromEnum(this) & @intFromEnum(State.HTTP_END_CALLED) != 0; + } + + pub inline fn isHttpWriteCalled(this: State) bool { + return @intFromEnum(this) & @intFromEnum(State.HTTP_WRITE_CALLED) != 0; + } + + pub inline fn isHttpStatusCalled(this: State) bool { + return @intFromEnum(this) & @intFromEnum(State.HTTP_STATUS_CALLED) != 0; + } + + pub inline fn isHttpConnectionClose(this: State) bool { + return @intFromEnum(this) & @intFromEnum(State.HTTP_CONNECTION_CLOSE) != 0; + } +}; + +pub const WriteResult = union(enum) { + want_more: usize, + backpressure: usize, +}; + +pub const uws_res = c.uws_res; + +const c = struct { + pub const uws_res = opaque {}; + pub extern fn us_socket_mark_needs_more_not_ssl(socket: ?*c.uws_res) void; + pub extern fn uws_res_state(ssl: c_int, res: *const c.uws_res) State; + pub extern fn uws_res_get_remote_address_info(res: *c.uws_res, dest: *[*]const u8, port: *i32, is_ipv6: *bool) usize; + pub extern fn uws_res_uncork(ssl: i32, res: *c.uws_res) void; + pub extern fn uws_res_end(ssl: i32, res: *c.uws_res, data: [*c]const u8, length: usize, close_connection: bool) void; + pub extern fn uws_res_flush_headers(ssl: i32, res: *c.uws_res) void; + pub extern fn uws_res_pause(ssl: i32, res: *c.uws_res) void; + pub extern fn uws_res_resume(ssl: i32, res: *c.uws_res) void; + pub extern fn uws_res_write_continue(ssl: i32, res: *c.uws_res) void; + pub extern fn uws_res_write_status(ssl: i32, res: *c.uws_res, status: [*c]const u8, length: usize) void; + pub extern fn uws_res_write_header(ssl: i32, res: *c.uws_res, key: [*c]const u8, key_length: usize, value: [*c]const u8, value_length: usize) void; + pub extern fn uws_res_write_header_int(ssl: i32, res: *c.uws_res, key: [*c]const u8, key_length: usize, value: u64) void; + pub extern fn uws_res_end_without_body(ssl: i32, res: *c.uws_res, close_connection: bool) void; + pub extern fn uws_res_end_sendfile(ssl: i32, res: *c.uws_res, write_offset: u64, close_connection: bool) void; + pub extern fn uws_res_timeout(ssl: i32, res: *c.uws_res, timeout: u8) void; + pub extern fn uws_res_reset_timeout(ssl: i32, res: *c.uws_res) void; + pub extern fn uws_res_get_buffered_amount(ssl: i32, res: *c.uws_res) u64; + pub extern fn uws_res_write(ssl: i32, res: *c.uws_res, data: ?[*]const u8, length: *usize) bool; + pub extern fn uws_res_get_write_offset(ssl: i32, res: *c.uws_res) u64; + pub extern fn uws_res_override_write_offset(ssl: i32, res: *c.uws_res, u64) void; + pub extern fn uws_res_has_responded(ssl: i32, res: *c.uws_res) bool; + pub extern fn uws_res_on_writable(ssl: i32, res: *c.uws_res, handler: ?*const fn (*c.uws_res, u64, ?*anyopaque) callconv(.C) bool, user_data: ?*anyopaque) void; + pub extern fn uws_res_clear_on_writable(ssl: i32, res: *c.uws_res) void; + pub extern fn uws_res_on_aborted(ssl: i32, res: *c.uws_res, handler: ?*const fn (*c.uws_res, ?*anyopaque) callconv(.C) void, optional_data: ?*anyopaque) void; + pub extern fn uws_res_on_timeout(ssl: i32, res: *c.uws_res, handler: ?*const fn (*c.uws_res, ?*anyopaque) callconv(.C) void, optional_data: ?*anyopaque) void; + pub extern fn uws_res_try_end( + ssl: i32, + res: *c.uws_res, + data: ?[*]const u8, + length: usize, + total: usize, + close: bool, + ) bool; + pub extern fn uws_res_end_stream(ssl: i32, res: *c.uws_res, close_connection: bool) void; + pub extern fn uws_res_prepare_for_sendfile(ssl: i32, res: *c.uws_res) void; + pub extern fn uws_res_get_native_handle(ssl: i32, res: *c.uws_res) *Socket; + pub extern fn uws_res_get_remote_address_as_text(ssl: i32, res: *c.uws_res, dest: *[*]const u8) usize; + + pub extern fn uws_res_on_data( + ssl: i32, + res: *c.uws_res, + handler: ?*const fn (*c.uws_res, [*c]const u8, usize, bool, ?*anyopaque) callconv(.C) void, + optional_data: ?*anyopaque, + ) void; + pub extern fn uws_res_upgrade( + ssl: i32, + res: *c.uws_res, + data: ?*anyopaque, + sec_web_socket_key: [*c]const u8, + sec_web_socket_key_length: usize, + sec_web_socket_protocol: [*c]const u8, + sec_web_socket_protocol_length: usize, + sec_web_socket_extensions: [*c]const u8, + sec_web_socket_extensions_length: usize, + ws: ?*uws.SocketContext, + ) *Socket; + pub extern fn uws_res_cork(i32, res: *c.uws_res, ctx: *anyopaque, corker: *const (fn (?*anyopaque) callconv(.C) void)) void; +}; + +const bun = @import("bun"); +const uws = bun.uws; +const Socket = uws.Socket; +const SocketContext = uws.SocketContext; +const Environment = bun.Environment; + +const SocketAddress = uws.SocketAddress; diff --git a/src/deps/uws/SocketContext.zig b/src/deps/uws/SocketContext.zig new file mode 100644 index 0000000000..b741b69ab9 --- /dev/null +++ b/src/deps/uws/SocketContext.zig @@ -0,0 +1,293 @@ +/// Zig wrapper around `us_socket_context_t` from uSockets. +/// +/// Stores shared state for a group of sockets sharing the same configuration. +/// +/// Key responsibilities: +/// - Socket creation and configuration management +/// - SSL/TLS certificate and security settings +/// - Event callback registration (open, close, data, error, etc.) +/// +/// The wrapper: +/// - Provides compile-time SSL/TLS specialization via boolean parameters +/// - Offers safe casting between Zig and C representations +/// - Maintains zero-cost abstractions over the underlying uSockets API +/// - Supports both plain TCP and SSL/TLS socket contexts +/// +/// Usage patterns: +/// - Create contexts using createNoSSLContext() or createSSLContext() +/// - Configure callbacks and options before creating sockets +/// - Use ref()/unref() for reference counting when sharing contexts +/// - Clean up with appropriate deinit methods when done +pub const SocketContext = opaque { + pub fn getNativeHandle(this: *SocketContext, comptime ssl: bool) *anyopaque { + return c.us_socket_context_get_native_handle(@intFromBool(ssl), this).?; + } + + fn _deinit_ssl(this: *SocketContext) void { + c.us_socket_context_free(@as(i32, 1), this); + } + + fn _deinit(this: *SocketContext) void { + c.us_socket_context_free(@as(i32, 0), this); + } + + pub fn ref(this: *SocketContext, comptime ssl: bool) *SocketContext { + c.us_socket_context_ref(@intFromBool(ssl), this); + return this; + } + + pub fn unref(this: *SocketContext, comptime ssl: bool) *SocketContext { + c.us_socket_context_unref(@intFromBool(ssl), this); + return this; + } + + pub fn addServerName(this: *SocketContext, ssl: bool, hostname_pattern: [*c]const u8, options: BunSocketContextOptions) void { + c.us_bun_socket_context_add_server_name(@intFromBool(ssl), this, hostname_pattern, options, null); + } + + // TODO: refactor to "create" with optional ssl options + pub fn createNoSSLContext(loop_ptr: *Loop, ext_size: i32) ?*SocketContext { + return c.us_create_bun_nossl_socket_context(loop_ptr, ext_size); + } + + // TODO: refactor to error union + pub fn createSSLContext(loop_ptr: *Loop, ext_size: i32, options: BunSocketContextOptions, err: *uws.create_bun_socket_error_t) ?*SocketContext { + return c.us_create_bun_ssl_socket_context(loop_ptr, ext_size, options, err); + } + + pub fn cleanCallbacks(ctx: *SocketContext, is_ssl: bool) void { + const ssl_int: i32 = @intFromBool(is_ssl); + // replace callbacks with dummy ones + const DummyCallbacks = struct { + fn open(socket: *us_socket_t, _: i32, _: [*c]u8, _: i32) callconv(.C) ?*us_socket_t { + return socket; + } + fn close(socket: *us_socket_t, _: i32, _: ?*anyopaque) callconv(.C) ?*us_socket_t { + return socket; + } + fn data(socket: *us_socket_t, _: [*c]u8, _: i32) callconv(.C) ?*us_socket_t { + return socket; + } + fn fd(socket: *us_socket_t, _: c_int) callconv(.C) ?*us_socket_t { + return socket; + } + fn writable(socket: *us_socket_t) callconv(.C) ?*us_socket_t { + return socket; + } + fn timeout(socket: *us_socket_t) callconv(.C) ?*us_socket_t { + return socket; + } + fn connect_error(socket: *ConnectingSocket, _: i32) callconv(.C) ?*ConnectingSocket { + return socket; + } + fn socket_connect_error(socket: *us_socket_t, _: i32) callconv(.C) ?*us_socket_t { + return socket; + } + fn end(socket: *us_socket_t) callconv(.C) ?*us_socket_t { + return socket; + } + fn handshake(_: *us_socket_t, _: i32, _: us_bun_verify_error_t, _: ?*anyopaque) callconv(.C) void {} + fn long_timeout(socket: *us_socket_t) callconv(.C) ?*us_socket_t { + return socket; + } + }; + c.us_socket_context_on_open(ssl_int, ctx, DummyCallbacks.open); + c.us_socket_context_on_close(ssl_int, ctx, DummyCallbacks.close); + c.us_socket_context_on_data(ssl_int, ctx, DummyCallbacks.data); + c.us_socket_context_on_fd(ssl_int, ctx, DummyCallbacks.fd); + c.us_socket_context_on_writable(ssl_int, ctx, DummyCallbacks.writable); + c.us_socket_context_on_timeout(ssl_int, ctx, DummyCallbacks.timeout); + c.us_socket_context_on_connect_error(ssl_int, ctx, DummyCallbacks.connect_error); + c.us_socket_context_on_socket_connect_error(ssl_int, ctx, DummyCallbacks.socket_connect_error); + c.us_socket_context_on_end(ssl_int, ctx, DummyCallbacks.end); + c.us_socket_context_on_handshake(ssl_int, ctx, DummyCallbacks.handshake, null); + c.us_socket_context_on_long_timeout(ssl_int, ctx, DummyCallbacks.long_timeout); + } + + fn getLoop(this: *SocketContext, ssl: bool) ?*Loop { + return c.us_socket_context_loop(@intFromBool(ssl), this); + } + + /// closes and deinit the SocketContexts + pub fn deinit(this: *SocketContext, ssl: bool) void { + // we clean the callbacks to avoid UAF because we are deiniting + this.cleanCallbacks(ssl); + this.close(ssl); + //always deinit in next iteration + if (ssl) { + Loop.get().nextTick(*SocketContext, this, SocketContext._deinit_ssl); + } else { + Loop.get().nextTick(*SocketContext, this, SocketContext._deinit); + } + } + + pub fn close(this: *SocketContext, ssl: bool) void { + debug("us_socket_context_close({d})", .{@intFromPtr(this)}); + c.us_socket_context_close(@intFromBool(ssl), this); + } + + pub fn ext(this: *SocketContext, ssl: bool, comptime ContextType: type) ?*ContextType { + const alignment = if (ContextType == *anyopaque) + @sizeOf(usize) + else + std.meta.alignment(ContextType); + + const ptr = c.us_socket_context_ext( + @intFromBool(ssl), + this, + ) orelse return null; + + return @as(*align(alignment) ContextType, @ptrCast(@alignCast(ptr))); + } + + pub fn onOpen(this: *SocketContext, ssl: bool, on_open: ?*const fn (*us_socket_t, i32, [*c]u8, i32) callconv(.C) ?*us_socket_t) void { + c.us_socket_context_on_open(@intFromBool(ssl), this, on_open); + } + + pub fn onClose(this: *SocketContext, ssl: bool, on_close: ?*const fn (*us_socket_t, i32, ?*anyopaque) callconv(.C) ?*us_socket_t) void { + c.us_socket_context_on_close(@intFromBool(ssl), this, on_close); + } + + pub fn onData(this: *SocketContext, ssl: bool, on_data: ?*const fn (*us_socket_t, [*c]u8, i32) callconv(.C) ?*us_socket_t) void { + c.us_socket_context_on_data(@intFromBool(ssl), this, on_data); + } + + pub fn onFd(this: *SocketContext, ssl: bool, on_fd: ?*const fn (*us_socket_t, c_int) callconv(.C) ?*us_socket_t) void { + c.us_socket_context_on_fd(@intFromBool(ssl), this, on_fd); + } + + pub fn onHandshake(this: *SocketContext, ssl: bool, on_handshake: ?*const fn (*us_socket_t, i32, us_bun_verify_error_t, ?*anyopaque) callconv(.C) void) void { + c.us_socket_context_on_handshake(@intFromBool(ssl), this, on_handshake, null); + } + + pub fn onLongTimeout(this: *SocketContext, ssl: bool, on_timeout: ?*const fn (*us_socket_t) callconv(.C) ?*us_socket_t) void { + c.us_socket_context_on_long_timeout(@intFromBool(ssl), this, on_timeout); + } + + pub fn onWritable(this: *SocketContext, ssl: bool, on_writable: ?*const fn (*us_socket_t) callconv(.C) ?*us_socket_t) void { + c.us_socket_context_on_writable(@intFromBool(ssl), this, on_writable); + } + + pub fn onTimeout(this: *SocketContext, ssl: bool, on_timeout: ?*const fn (*us_socket_t) callconv(.C) ?*us_socket_t) void { + c.us_socket_context_on_timeout(@intFromBool(ssl), this, on_timeout); + } + + pub fn onConnectError(this: *SocketContext, ssl: bool, on_connect_error: ?*const fn (*ConnectingSocket, i32) callconv(.C) ?*ConnectingSocket) void { + c.us_socket_context_on_connect_error(@intFromBool(ssl), this, on_connect_error); + } + + pub fn onSocketConnectError(this: *SocketContext, ssl: bool, on_connect_error: ?*const fn (*us_socket_t, i32) callconv(.C) ?*us_socket_t) void { + c.us_socket_context_on_socket_connect_error(@intFromBool(ssl), this, on_connect_error); + } + + pub fn onEnd(this: *SocketContext, ssl: bool, on_end: ?*const fn (*us_socket_t) callconv(.C) ?*us_socket_t) void { + c.us_socket_context_on_end(@intFromBool(ssl), this, on_end); + } + + pub fn onServerName(this: *SocketContext, ssl: bool, on_server_name: ?*const fn (*SocketContext, [*c]const u8) callconv(.C) void) void { + c.us_socket_context_on_server_name(@intFromBool(ssl), this, on_server_name); + } + + pub fn removeServerName(this: *SocketContext, ssl: bool, hostname_pattern: [*:0]const u8) void { + c.us_socket_context_remove_server_name(@intFromBool(ssl), this, hostname_pattern); + } + + pub fn adoptSocket(this: *SocketContext, ssl: bool, s: *us_socket_t, ext_size: i32) ?*us_socket_t { + return c.us_socket_context_adopt_socket(@intFromBool(ssl), this, s, ext_size); + } + + pub fn connect(this: *SocketContext, ssl: bool, host: [*:0]const u8, port: i32, options: i32, socket_ext_size: i32, has_dns_resolved: *i32) ?*anyopaque { + return c.us_socket_context_connect(@intFromBool(ssl), this, host, port, options, socket_ext_size, has_dns_resolved); + } + + pub fn connectUnix(this: *SocketContext, ssl: bool, path: [:0]const u8, options: i32, socket_ext_size: i32) ?*us_socket_t { + return c.us_socket_context_connect_unix(@intFromBool(ssl), this, path.ptr, path.len, options, socket_ext_size); + } + + pub fn free(this: *SocketContext, ssl: bool) void { + c.us_socket_context_free(@intFromBool(ssl), this); + } + + pub fn listen(this: *SocketContext, ssl: bool, host: ?[*:0]const u8, port: i32, options: i32, socket_ext_size: i32, err: *c_int) ?*ListenSocket { + return c.us_socket_context_listen(@intFromBool(ssl), this, host, port, options, socket_ext_size, err); + } + + pub fn listenUnix(this: *SocketContext, ssl: bool, path: [*:0]const u8, pathlen: usize, options: i32, socket_ext_size: i32, err: *c_int) ?*ListenSocket { + return c.us_socket_context_listen_unix(@intFromBool(ssl), this, path, pathlen, options, socket_ext_size, err); + } + + pub fn loop(this: *SocketContext, ssl: bool) ?*Loop { + return c.us_socket_context_loop(@intFromBool(ssl), this); + } + + /// Corresponds to `us_bun_socket_context_options_t` + pub const BunSocketContextOptions = extern struct { + key_file_name: [*c]const u8 = null, + cert_file_name: [*c]const u8 = null, + passphrase: [*c]const u8 = null, + dh_params_file_name: [*c]const u8 = null, + ca_file_name: [*c]const u8 = null, + ssl_ciphers: [*c]const u8 = null, + ssl_prefer_low_memory_usage: i32 = 0, + key: ?[*]?[*:0]const u8 = null, + key_count: u32 = 0, + cert: ?[*]?[*:0]const u8 = null, + cert_count: u32 = 0, + ca: ?[*]?[*:0]const u8 = null, + ca_count: u32 = 0, + secure_options: u32 = 0, + reject_unauthorized: i32 = 0, + request_cert: i32 = 0, + client_renegotiation_limit: u32 = 3, + client_renegotiation_window: u32 = 600, + + pub fn createSSLContext(options: BunSocketContextOptions, err: *uws.create_bun_socket_error_t) ?*BoringSSL.SSL_CTX { + return c.create_ssl_context_from_bun_options(options, err); + } + }; +}; + +pub const c = struct { + pub extern fn us_bun_socket_context_add_server_name(ssl: i32, context: ?*SocketContext, hostname_pattern: [*c]const u8, options: SocketContext.BunSocketContextOptions, ?*anyopaque) void; + pub extern fn us_create_bun_nossl_socket_context(loop: ?*Loop, ext_size: i32) ?*SocketContext; + pub extern fn us_create_bun_ssl_socket_context(loop: ?*Loop, ext_size: i32, options: SocketContext.BunSocketContextOptions, err: *create_bun_socket_error_t) ?*SocketContext; + pub extern fn us_create_child_socket_context(ssl: i32, context: ?*SocketContext, context_ext_size: i32) ?*SocketContext; + pub extern fn us_socket_context_adopt_socket(ssl: i32, context: *SocketContext, s: *us_socket_t, ext_size: i32) ?*us_socket_t; + pub extern fn us_socket_context_close(ssl: i32, ctx: *anyopaque) void; + pub extern fn us_socket_context_connect(ssl: i32, context: *SocketContext, host: [*:0]const u8, port: i32, options: i32, socket_ext_size: i32, has_dns_resolved: *i32) ?*anyopaque; + pub extern fn us_socket_context_connect_unix(ssl: i32, context: *SocketContext, path: [*:0]const u8, pathlen: usize, options: i32, socket_ext_size: i32) ?*us_socket_t; + pub extern fn us_socket_context_ext(ssl: i32, context: *SocketContext) ?*anyopaque; + pub extern fn us_socket_context_free(ssl: i32, context: *SocketContext) void; + pub extern fn us_socket_context_get_native_handle(ssl: i32, context: *SocketContext) ?*anyopaque; + pub extern fn us_socket_context_listen(ssl: i32, context: *SocketContext, host: ?[*:0]const u8, port: i32, options: i32, socket_ext_size: i32, err: *c_int) ?*ListenSocket; + pub extern fn us_socket_context_listen_unix(ssl: i32, context: *SocketContext, path: [*:0]const u8, pathlen: usize, options: i32, socket_ext_size: i32, err: *c_int) ?*ListenSocket; + pub extern fn us_socket_context_loop(ssl: i32, context: *SocketContext) ?*Loop; + pub extern fn us_socket_context_on_close(ssl: i32, context: *SocketContext, on_close: ?*const fn (*us_socket_t, i32, ?*anyopaque) callconv(.C) ?*us_socket_t) void; + pub extern fn us_socket_context_on_connect_error(ssl: i32, context: *SocketContext, on_connect_error: ?*const fn (*uws.ConnectingSocket, i32) callconv(.C) ?*uws.ConnectingSocket) void; + pub extern fn us_socket_context_on_data(ssl: i32, context: *SocketContext, on_data: ?*const fn (*us_socket_t, [*c]u8, i32) callconv(.C) ?*us_socket_t) void; + pub extern fn us_socket_context_on_end(ssl: i32, context: *SocketContext, on_end: ?*const fn (*us_socket_t) callconv(.C) ?*us_socket_t) void; + pub extern fn us_socket_context_on_fd(ssl: i32, context: *SocketContext, on_fd: ?*const fn (*us_socket_t, c_int) callconv(.C) ?*us_socket_t) void; + pub extern fn us_socket_context_on_handshake(ssl: i32, context: *SocketContext, on_handshake: ?*const fn (*us_socket_t, i32, us_bun_verify_error_t, ?*anyopaque) callconv(.C) void, ?*anyopaque) void; + pub extern fn us_socket_context_on_long_timeout(ssl: i32, context: *SocketContext, on_timeout: ?*const fn (*us_socket_t) callconv(.C) ?*us_socket_t) void; + pub extern fn us_socket_context_on_open(ssl: i32, context: *SocketContext, on_open: ?*const fn (*us_socket_t, i32, [*c]u8, i32) callconv(.C) ?*us_socket_t) void; + pub extern fn us_socket_context_on_server_name(ssl: i32, context: *SocketContext, cb: ?*const fn (?*SocketContext, [*c]const u8) callconv(.C) void) void; + pub extern fn us_socket_context_on_socket_connect_error(ssl: i32, context: *SocketContext, on_connect_error: ?*const fn (*us_socket_t, i32) callconv(.C) ?*us_socket_t) void; + pub extern fn us_socket_context_on_timeout(ssl: i32, context: *SocketContext, on_timeout: ?*const fn (*us_socket_t) callconv(.C) ?*us_socket_t) void; + pub extern fn us_socket_context_on_writable(ssl: i32, context: *SocketContext, on_writable: ?*const fn (*us_socket_t) callconv(.C) ?*us_socket_t) void; + pub extern fn us_socket_context_ref(ssl: i32, context: *SocketContext) void; + pub extern fn us_socket_context_remove_server_name(ssl: i32, context: *SocketContext, hostname_pattern: [*c]const u8) void; + pub extern fn us_socket_context_unref(ssl: i32, context: *SocketContext) void; + pub extern fn create_ssl_context_from_bun_options(options: SocketContext.BunSocketContextOptions, err: *create_bun_socket_error_t) ?*BoringSSL.SSL_CTX; +}; + +const bun = @import("bun"); +const uws = bun.uws; +const Loop = uws.Loop; +const us_socket_t = uws.us_socket_t; +const us_bun_verify_error_t = uws.us_bun_verify_error_t; +const create_bun_socket_error_t = uws.create_bun_socket_error_t; +const ListenSocket = uws.ListenSocket; +const debug = bun.Output.scoped(.uws, false); +const std = @import("std"); +const ConnectingSocket = uws.ConnectingSocket; +const BoringSSL = bun.BoringSSL.c; diff --git a/src/deps/uws/Timer.zig b/src/deps/uws/Timer.zig new file mode 100644 index 0000000000..9ca4f1e9ea --- /dev/null +++ b/src/deps/uws/Timer.zig @@ -0,0 +1,62 @@ +/// **DEPRECATED** +/// **DO NOT USE IN NEW CODE!** +/// +/// Use `JSC.EventLoopTimer` instead. +/// +/// This code will be deleted eventually! It is very inefficient on POSIX. On +/// Linux, it holds an entire file descriptor for every single timer. On macOS, +/// it's several system calls. +pub const Timer = opaque { + pub fn create(loop: *Loop, ptr: anytype) *Timer { + const Type = @TypeOf(ptr); + + // never fallthrough poll + // the problem is uSockets hardcodes it on the other end + // so we can never free non-fallthrough polls + return c.us_create_timer(loop, 0, @sizeOf(Type)) orelse bun.Output.panic("us_create_timer: returned null: {d}", .{std.c._errno().*}); + } + + pub fn createFallthrough(loop: *Loop, ptr: anytype) *Timer { + const Type = @TypeOf(ptr); + + // never fallthrough poll + // the problem is uSockets hardcodes it on the other end + // so we can never free non-fallthrough polls + return c.us_create_timer(loop, 1, @sizeOf(Type)) orelse bun.Output.panic("us_create_timer: returned null: {d}", .{std.c._errno().*}); + } + + pub fn set(this: *Timer, ptr: anytype, cb: ?*const fn (*Timer) callconv(.C) void, ms: i32, repeat_ms: i32) void { + c.us_timer_set(this, cb, ms, repeat_ms); + const value_ptr = c.us_timer_ext(this); + @setRuntimeSafety(false); + @as(*@TypeOf(ptr), @ptrCast(@alignCast(value_ptr))).* = ptr; + } + + pub fn deinit(this: *Timer, comptime fallthrough: bool) void { + debug("Timer.deinit()", .{}); + c.us_timer_close(this, @intFromBool(fallthrough)); + } + + pub fn ext(this: *Timer, comptime Type: type) ?*Type { + return @as(*Type, @ptrCast(@alignCast(c.us_timer_ext(this).*.?))); + } + + pub fn as(this: *Timer, comptime Type: type) Type { + @setRuntimeSafety(false); + return @as(*?Type, @ptrCast(@alignCast(c.us_timer_ext(this)))).*.?; + } +}; + +const c = struct { + pub extern fn us_create_timer(loop: ?*Loop, fallthrough: i32, ext_size: c_uint) ?*Timer; + pub extern fn us_timer_ext(timer: ?*Timer) *?*anyopaque; + pub extern fn us_timer_close(timer: ?*Timer, fallthrough: i32) void; + pub extern fn us_timer_set(timer: ?*Timer, cb: ?*const fn (*Timer) callconv(.C) void, ms: i32, repeat_ms: i32) void; + pub extern fn us_timer_loop(t: ?*Timer) ?*Loop; +}; + +const bun = @import("bun"); +const uws = bun.uws; +const Loop = uws.Loop; +const debug = bun.Output.scoped(.uws, false); +const std = @import("std"); diff --git a/src/deps/uws/UpgradedDuplex.zig b/src/deps/uws/UpgradedDuplex.zig new file mode 100644 index 0000000000..d05fa1e247 --- /dev/null +++ b/src/deps/uws/UpgradedDuplex.zig @@ -0,0 +1,484 @@ +//! UpgradedDuplex provides TLS/SSL encryption for Node.js-style duplex streams. +//! +//! This is used when you need to add TLS encryption to streams that are not traditional +//! network sockets. In Node.js, you can have duplex streams that represent arbitrary +//! read/write channels - these could be in-memory streams, custom transport protocols, +//! or any other bidirectional data flow that implements the duplex stream interface. +//! +//! Since these duplex streams don't have native SSL support (they're not actual socket +//! file descriptors), +//! +//! The duplex stream manages the SSL handshake, certificate validation, encryption/decryption, +//! and integrates with Bun's event loop for timeouts and async operations. It maintains +//! JavaScript callbacks for handling connection events and errors. +const UpgradedDuplex = @This(); + +wrapper: ?WrapperType, +origin: JSC.Strong.Optional = .empty, // any duplex +global: ?*JSC.JSGlobalObject = null, +ssl_error: CertError = .{}, +vm: *JSC.VirtualMachine, +handlers: Handlers, +onDataCallback: JSC.Strong.Optional = .empty, +onEndCallback: JSC.Strong.Optional = .empty, +onWritableCallback: JSC.Strong.Optional = .empty, +onCloseCallback: JSC.Strong.Optional = .empty, +event_loop_timer: EventLoopTimer = .{ + .next = .{}, + .tag = .UpgradedDuplex, +}, +current_timeout: u32 = 0, + +pub const CertError = struct { + error_no: i32 = 0, + code: [:0]const u8 = "", + reason: [:0]const u8 = "", + + pub fn deinit(this: *CertError) void { + if (this.code.len > 0) { + bun.default_allocator.free(this.code); + } + if (this.reason.len > 0) { + bun.default_allocator.free(this.reason); + } + } +}; + +const WrapperType = SSLWrapper(*UpgradedDuplex); + +pub const Handlers = struct { + ctx: *anyopaque, + onOpen: *const fn (*anyopaque) void, + onHandshake: *const fn (*anyopaque, bool, uws.us_bun_verify_error_t) void, + onData: *const fn (*anyopaque, []const u8) void, + onClose: *const fn (*anyopaque) void, + onEnd: *const fn (*anyopaque) void, + onWritable: *const fn (*anyopaque) void, + onError: *const fn (*anyopaque, JSC.JSValue) void, + onTimeout: *const fn (*anyopaque) void, +}; + +fn onOpen(this: *UpgradedDuplex) void { + log("onOpen", .{}); + this.handlers.onOpen(this.handlers.ctx); +} + +fn onData(this: *UpgradedDuplex, decoded_data: []const u8) void { + log("onData ({})", .{decoded_data.len}); + this.handlers.onData(this.handlers.ctx, decoded_data); +} + +fn onHandshake(this: *UpgradedDuplex, handshake_success: bool, ssl_error: uws.us_bun_verify_error_t) void { + log("onHandshake", .{}); + + this.ssl_error = .{ + .error_no = ssl_error.error_no, + .code = if (ssl_error.code == null or ssl_error.error_no == 0) "" else bun.default_allocator.dupeZ(u8, ssl_error.code[0..bun.len(ssl_error.code) :0]) catch bun.outOfMemory(), + .reason = if (ssl_error.reason == null or ssl_error.error_no == 0) "" else bun.default_allocator.dupeZ(u8, ssl_error.reason[0..bun.len(ssl_error.reason) :0]) catch bun.outOfMemory(), + }; + this.handlers.onHandshake(this.handlers.ctx, handshake_success, ssl_error); +} + +fn onClose(this: *UpgradedDuplex) void { + log("onClose", .{}); + defer this.deinit(); + + this.handlers.onClose(this.handlers.ctx); + // closes the underlying duplex + this.callWriteOrEnd(null, false); +} + +fn callWriteOrEnd(this: *UpgradedDuplex, data: ?[]const u8, msg_more: bool) void { + if (this.vm.isShuttingDown()) { + return; + } + if (this.origin.get()) |duplex| { + const globalThis = this.global.?; + const writeOrEnd = if (msg_more) duplex.getFunction(globalThis, "write") catch return orelse return else duplex.getFunction(globalThis, "end") catch return orelse return; + if (data) |data_| { + const buffer = JSC.ArrayBuffer.BinaryType.toJS(.Buffer, data_, globalThis); + buffer.ensureStillAlive(); + + _ = writeOrEnd.call(globalThis, duplex, &.{buffer}) catch |err| { + this.handlers.onError(this.handlers.ctx, globalThis.takeException(err)); + }; + } else { + _ = writeOrEnd.call(globalThis, duplex, &.{.null}) catch |err| { + this.handlers.onError(this.handlers.ctx, globalThis.takeException(err)); + }; + } + } +} + +fn internalWrite(this: *UpgradedDuplex, encoded_data: []const u8) void { + this.resetTimeout(); + + // Possible scenarios: + // Scenario 1: will not write if vm is shutting down (we cannot do anything about it) + // Scenario 2: will not write if a exception is thrown (will be handled by onError) + // Scenario 3: will be queued in memory and will be flushed later + // Scenario 4: no write/end function exists (will be handled by onError) + this.callWriteOrEnd(encoded_data, true); +} + +pub fn flush(this: *UpgradedDuplex) void { + if (this.wrapper) |*wrapper| { + _ = wrapper.flush(); + } +} + +fn onInternalReceiveData(this: *UpgradedDuplex, data: []const u8) void { + if (this.wrapper) |*wrapper| { + this.resetTimeout(); + wrapper.receiveData(data); + } +} + +fn onReceivedData( + globalObject: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, +) bun.JSError!JSC.JSValue { + log("onReceivedData", .{}); + + const function = callframe.callee(); + const args = callframe.arguments_old(1); + + if (JSC.host_fn.getFunctionData(function)) |self| { + const this = @as(*UpgradedDuplex, @ptrCast(@alignCast(self))); + if (args.len >= 1) { + const data_arg = args.ptr[0]; + if (this.origin.has()) { + if (data_arg.isEmptyOrUndefinedOrNull()) { + return JSC.JSValue.jsUndefined(); + } + if (data_arg.asArrayBuffer(globalObject)) |array_buffer| { + // yay we can read the data + const payload = array_buffer.slice(); + this.onInternalReceiveData(payload); + } else { + // node.js errors in this case with the same error, lets keep it consistent + const error_value = globalObject.ERR(.STREAM_WRAP, "Stream has StringDecoder set or is in objectMode", .{}).toJS(); + error_value.ensureStillAlive(); + this.handlers.onError(this.handlers.ctx, error_value); + } + } + } + } + return JSC.JSValue.jsUndefined(); +} + +fn onEnd( + globalObject: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, +) void { + log("onEnd", .{}); + _ = globalObject; + const function = callframe.callee(); + + if (JSC.host_fn.getFunctionData(function)) |self| { + const this = @as(*UpgradedDuplex, @ptrCast(@alignCast(self))); + + if (this.wrapper != null) { + this.handlers.onEnd(this.handlers.ctx); + } + } +} + +fn onWritable( + globalObject: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, +) bun.JSError!JSC.JSValue { + log("onWritable", .{}); + + _ = globalObject; + const function = callframe.callee(); + + if (JSC.host_fn.getFunctionData(function)) |self| { + const this = @as(*UpgradedDuplex, @ptrCast(@alignCast(self))); + // flush pending data + if (this.wrapper) |*wrapper| { + _ = wrapper.flush(); + } + // call onWritable (will flush on demand) + this.handlers.onWritable(this.handlers.ctx); + } + + return JSC.JSValue.jsUndefined(); +} + +fn onCloseJS( + globalObject: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, +) bun.JSError!JSC.JSValue { + log("onCloseJS", .{}); + + _ = globalObject; + const function = callframe.callee(); + + if (JSC.host_fn.getFunctionData(function)) |self| { + const this = @as(*UpgradedDuplex, @ptrCast(@alignCast(self))); + // flush pending data + if (this.wrapper) |*wrapper| { + _ = wrapper.shutdown(true); + } + } + + return JSC.JSValue.jsUndefined(); +} + +pub fn onTimeout(this: *UpgradedDuplex) EventLoopTimer.Arm { + log("onTimeout", .{}); + + const has_been_cleared = this.event_loop_timer.state == .CANCELLED or this.vm.scriptExecutionStatus() != .running; + + this.event_loop_timer.state = .FIRED; + this.event_loop_timer.heap = .{}; + + if (has_been_cleared) { + return .disarm; + } + + this.handlers.onTimeout(this.handlers.ctx); + + return .disarm; +} + +pub fn from( + globalThis: *JSC.JSGlobalObject, + origin: JSC.JSValue, + handlers: UpgradedDuplex.Handlers, +) UpgradedDuplex { + return UpgradedDuplex{ + .vm = globalThis.bunVM(), + .origin = .create(origin, globalThis), + .global = globalThis, + .wrapper = null, + .handlers = handlers, + }; +} + +pub fn getJSHandlers(this: *UpgradedDuplex, globalThis: *JSC.JSGlobalObject) bun.JSError!JSC.JSValue { + const array = try JSC.JSValue.createEmptyArray(globalThis, 4); + array.ensureStillAlive(); + + { + const callback = this.onDataCallback.get() orelse brk: { + const dataCallback = JSC.host_fn.NewFunctionWithData( + globalThis, + null, + 0, + onReceivedData, + false, + this, + ); + dataCallback.ensureStillAlive(); + + JSC.host_fn.setFunctionData(dataCallback, this); + + this.onDataCallback = .create(dataCallback, globalThis); + break :brk dataCallback; + }; + array.putIndex(globalThis, 0, callback); + } + + { + const callback = this.onEndCallback.get() orelse brk: { + const endCallback = JSC.host_fn.NewFunctionWithData( + globalThis, + null, + 0, + onReceivedData, + false, + this, + ); + endCallback.ensureStillAlive(); + + JSC.host_fn.setFunctionData(endCallback, this); + + this.onEndCallback = .create(endCallback, globalThis); + break :brk endCallback; + }; + array.putIndex(globalThis, 1, callback); + } + + { + const callback = this.onWritableCallback.get() orelse brk: { + const writableCallback = JSC.host_fn.NewFunctionWithData( + globalThis, + null, + 0, + onWritable, + false, + this, + ); + writableCallback.ensureStillAlive(); + + JSC.host_fn.setFunctionData(writableCallback, this); + this.onWritableCallback = .create(writableCallback, globalThis); + break :brk writableCallback; + }; + array.putIndex(globalThis, 2, callback); + } + + { + const callback = this.onCloseCallback.get() orelse brk: { + const closeCallback = JSC.host_fn.NewFunctionWithData( + globalThis, + null, + 0, + onCloseJS, + false, + this, + ); + closeCallback.ensureStillAlive(); + + JSC.host_fn.setFunctionData(closeCallback, this); + this.onCloseCallback = .create(closeCallback, globalThis); + break :brk closeCallback; + }; + array.putIndex(globalThis, 3, callback); + } + + return array; +} + +pub fn startTLS(this: *UpgradedDuplex, ssl_options: JSC.API.ServerConfig.SSLConfig, is_client: bool) !void { + this.wrapper = try WrapperType.init(ssl_options, is_client, .{ + .ctx = this, + .onOpen = UpgradedDuplex.onOpen, + .onHandshake = UpgradedDuplex.onHandshake, + .onData = UpgradedDuplex.onData, + .onClose = UpgradedDuplex.onClose, + .write = UpgradedDuplex.internalWrite, + }); + + this.wrapper.?.start(); +} + +pub fn encodeAndWrite(this: *UpgradedDuplex, data: []const u8, is_end: bool) i32 { + log("encodeAndWrite (len: {} - is_end: {})", .{ data.len, is_end }); + if (this.wrapper) |*wrapper| { + return @as(i32, @intCast(wrapper.writeData(data) catch 0)); + } + return 0; +} + +pub fn rawWrite(this: *UpgradedDuplex, encoded_data: []const u8, _: bool) i32 { + this.internalWrite(encoded_data); + return @intCast(encoded_data.len); +} + +pub fn close(this: *UpgradedDuplex) void { + if (this.wrapper) |*wrapper| { + _ = wrapper.shutdown(true); + } +} + +pub fn shutdown(this: *UpgradedDuplex) void { + if (this.wrapper) |*wrapper| { + _ = wrapper.shutdown(false); + } +} + +pub fn shutdownRead(this: *UpgradedDuplex) void { + if (this.wrapper) |*wrapper| { + _ = wrapper.shutdownRead(); + } +} + +pub fn isShutdown(this: *UpgradedDuplex) bool { + if (this.wrapper) |wrapper| { + return wrapper.isShutdown(); + } + return true; +} + +pub fn isClosed(this: *UpgradedDuplex) bool { + if (this.wrapper) |wrapper| { + return wrapper.isClosed(); + } + return true; +} + +pub fn isEstablished(this: *UpgradedDuplex) bool { + return !this.isClosed(); +} + +pub fn ssl(this: *UpgradedDuplex) ?*BoringSSL.SSL { + if (this.wrapper) |wrapper| { + return wrapper.ssl; + } + return null; +} + +pub fn sslError(this: *UpgradedDuplex) us_bun_verify_error_t { + return .{ + .error_no = this.ssl_error.error_no, + .code = @ptrCast(this.ssl_error.code.ptr), + .reason = @ptrCast(this.ssl_error.reason.ptr), + }; +} + +pub fn resetTimeout(this: *UpgradedDuplex) void { + this.setTimeoutInMilliseconds(this.current_timeout); +} +pub fn setTimeoutInMilliseconds(this: *UpgradedDuplex, ms: c_uint) void { + if (this.event_loop_timer.state == .ACTIVE) { + this.vm.timer.remove(&this.event_loop_timer); + } + this.current_timeout = ms; + + // if the interval is 0 means that we stop the timer + if (ms == 0) { + return; + } + + // reschedule the timer + this.event_loop_timer.next = bun.timespec.msFromNow(ms); + this.vm.timer.insert(&this.event_loop_timer); +} +pub fn setTimeout(this: *UpgradedDuplex, seconds: c_uint) void { + log("setTimeout({d})", .{seconds}); + this.setTimeoutInMilliseconds(seconds * 1000); +} + +pub fn deinit(this: *UpgradedDuplex) void { + log("deinit", .{}); + // clear the timer + this.setTimeout(0); + + if (this.wrapper) |*wrapper| { + wrapper.deinit(); + this.wrapper = null; + } + + this.origin.deinit(); + if (this.onDataCallback.get()) |callback| { + JSC.host_fn.setFunctionData(callback, null); + this.onDataCallback.deinit(); + } + if (this.onEndCallback.get()) |callback| { + JSC.host_fn.setFunctionData(callback, null); + this.onEndCallback.deinit(); + } + if (this.onWritableCallback.get()) |callback| { + JSC.host_fn.setFunctionData(callback, null); + this.onWritableCallback.deinit(); + } + if (this.onCloseCallback.get()) |callback| { + JSC.host_fn.setFunctionData(callback, null); + this.onCloseCallback.deinit(); + } + var ssl_error = this.ssl_error; + ssl_error.deinit(); + this.ssl_error = .{}; +} + +const bun = @import("bun"); +const JSC = bun.JSC; +const uws = bun.uws; +const BoringSSL = bun.BoringSSL.c; +const EventLoopTimer = bun.api.Timer.EventLoopTimer; +const us_bun_verify_error_t = uws.us_bun_verify_error_t; +const log = bun.Output.scoped(.UpgradedDuplex, false); +const SSLWrapper = @import("../../bun.js/api/bun/ssl_wrapper.zig").SSLWrapper; diff --git a/src/deps/uws/WebSocket.zig b/src/deps/uws/WebSocket.zig new file mode 100644 index 0000000000..97b9d1f5a1 --- /dev/null +++ b/src/deps/uws/WebSocket.zig @@ -0,0 +1,350 @@ +pub fn NewWebSocket(comptime ssl_flag: c_int) type { + return opaque { + const WebSocket = @This(); + + pub fn raw(this: *WebSocket) *RawWebSocket { + return @as(*RawWebSocket, @ptrCast(this)); + } + pub fn as(this: *WebSocket, comptime Type: type) ?*Type { + @setRuntimeSafety(false); + return @as(?*Type, @ptrCast(@alignCast(c.uws_ws_get_user_data(ssl_flag, this.raw())))); + } + + pub fn close(this: *WebSocket) void { + return c.uws_ws_close(ssl_flag, this.raw()); + } + pub fn send(this: *WebSocket, message: []const u8, opcode: Opcode) SendStatus { + return c.uws_ws_send(ssl_flag, this.raw(), message.ptr, message.len, opcode); + } + pub fn sendWithOptions(this: *WebSocket, message: []const u8, opcode: Opcode, compress: bool, fin: bool) SendStatus { + return c.uws_ws_send_with_options(ssl_flag, this.raw(), message.ptr, message.len, opcode, compress, fin); + } + + pub fn memoryCost(this: *WebSocket) usize { + return this.raw().memoryCost(ssl_flag); + } + + pub fn sendLastFragment(this: *WebSocket, message: []const u8, compress: bool) SendStatus { + return c.uws_ws_send_last_fragment(ssl_flag, this.raw(), message.ptr, message.len, compress); + } + pub fn end(this: *WebSocket, code: i32, message: []const u8) void { + return c.uws_ws_end(ssl_flag, this.raw(), code, message.ptr, message.len); + } + pub fn cork(this: *WebSocket, ctx: anytype, comptime callback: anytype) void { + const ContextType = @TypeOf(ctx); + const Wrapper = struct { + pub fn wrap(user_data: ?*anyopaque) callconv(.C) void { + @call(bun.callmod_inline, callback, .{bun.cast(ContextType, user_data.?)}); + } + }; + + return c.uws_ws_cork(ssl_flag, this.raw(), Wrapper.wrap, ctx); + } + pub fn subscribe(this: *WebSocket, topic: []const u8) bool { + return c.uws_ws_subscribe(ssl_flag, this.raw(), topic.ptr, topic.len); + } + pub fn unsubscribe(this: *WebSocket, topic: []const u8) bool { + return c.uws_ws_unsubscribe(ssl_flag, this.raw(), topic.ptr, topic.len); + } + pub fn isSubscribed(this: *WebSocket, topic: []const u8) bool { + return c.uws_ws_is_subscribed(ssl_flag, this.raw(), topic.ptr, topic.len); + } + + pub fn publish(this: *WebSocket, topic: []const u8, message: []const u8) bool { + return c.uws_ws_publish(ssl_flag, this.raw(), topic.ptr, topic.len, message.ptr, message.len); + } + pub fn publishWithOptions(this: *WebSocket, topic: []const u8, message: []const u8, opcode: Opcode, compress: bool) bool { + return c.uws_ws_publish_with_options(ssl_flag, this.raw(), topic.ptr, topic.len, message.ptr, message.len, opcode, compress); + } + pub fn getBufferedAmount(this: *WebSocket) u32 { + return c.uws_ws_get_buffered_amount(ssl_flag, this.raw()); + } + pub fn getRemoteAddress(this: *WebSocket, buf: []u8) []u8 { + var ptr: [*]u8 = undefined; + const len = c.uws_ws_get_remote_address(ssl_flag, this.raw(), &ptr); + bun.copy(u8, buf, ptr[0..len]); + return buf[0..len]; + } + }; +} + +pub const RawWebSocket = opaque { + pub fn memoryCost(this: *RawWebSocket, ssl_flag: i32) usize { + return c.uws_ws_memory_cost(ssl_flag, this); + } +}; + +pub const AnyWebSocket = union(enum) { + ssl: *uws.NewApp(true).WebSocket, + tcp: *uws.NewApp(false).WebSocket, + + pub fn raw(this: AnyWebSocket) *RawWebSocket { + return switch (this) { + .ssl => this.ssl.raw(), + .tcp => this.tcp.raw(), + }; + } + pub fn as(this: AnyWebSocket, comptime Type: type) ?*Type { + @setRuntimeSafety(false); + return switch (this) { + .ssl => this.ssl.as(Type), + .tcp => this.tcp.as(Type), + }; + } + + pub fn memoryCost(this: AnyWebSocket) usize { + return switch (this) { + .ssl => this.ssl.memoryCost(), + .tcp => this.tcp.memoryCost(), + }; + } + + pub fn close(this: AnyWebSocket) void { + const ssl_flag = @intFromBool(this == .ssl); + return c.uws_ws_close(ssl_flag, this.raw()); + } + + pub fn send(this: AnyWebSocket, message: []const u8, opcode: Opcode, compress: bool, fin: bool) SendStatus { + return switch (this) { + .ssl => c.uws_ws_send_with_options(1, this.ssl.raw(), message.ptr, message.len, opcode, compress, fin), + .tcp => c.uws_ws_send_with_options(0, this.tcp.raw(), message.ptr, message.len, opcode, compress, fin), + }; + } + pub fn sendLastFragment(this: AnyWebSocket, message: []const u8, compress: bool) SendStatus { + switch (this) { + .tcp => return c.uws_ws_send_last_fragment(0, this.raw(), message.ptr, message.len, compress), + .ssl => return c.uws_ws_send_last_fragment(1, this.raw(), message.ptr, message.len, compress), + } + } + pub fn end(this: AnyWebSocket, code: i32, message: []const u8) void { + switch (this) { + .tcp => c.uws_ws_end(0, this.tcp.raw(), code, message.ptr, message.len), + .ssl => c.uws_ws_end(1, this.ssl.raw(), code, message.ptr, message.len), + } + } + pub fn cork(this: AnyWebSocket, ctx: anytype, comptime callback: anytype) void { + const ContextType = @TypeOf(ctx); + const Wrapper = struct { + pub fn wrap(user_data: ?*anyopaque) callconv(.C) void { + @call(bun.callmod_inline, callback, .{bun.cast(ContextType, user_data.?)}); + } + }; + + switch (this) { + .ssl => c.uws_ws_cork(1, this.raw(), Wrapper.wrap, ctx), + .tcp => c.uws_ws_cork(0, this.raw(), Wrapper.wrap, ctx), + } + } + pub fn subscribe(this: AnyWebSocket, topic: []const u8) bool { + return switch (this) { + .ssl => c.uws_ws_subscribe(1, this.ssl.raw(), topic.ptr, topic.len), + .tcp => c.uws_ws_subscribe(0, this.tcp.raw(), topic.ptr, topic.len), + }; + } + pub fn unsubscribe(this: AnyWebSocket, topic: []const u8) bool { + return switch (this) { + .ssl => c.uws_ws_unsubscribe(1, this.raw(), topic.ptr, topic.len), + .tcp => c.uws_ws_unsubscribe(0, this.raw(), topic.ptr, topic.len), + }; + } + pub fn isSubscribed(this: AnyWebSocket, topic: []const u8) bool { + return switch (this) { + .ssl => c.uws_ws_is_subscribed(1, this.raw(), topic.ptr, topic.len), + .tcp => c.uws_ws_is_subscribed(0, this.raw(), topic.ptr, topic.len), + }; + } + // pub fn iterateTopics(this: AnyWebSocket) { + // return uws_ws_iterate_topics(ssl_flag, this.raw(), callback: ?*const fn ([*c]const u8, usize, ?*anyopaque) callconv(.C) void, user_data: ?*anyopaque) void; + // } + pub fn publish(this: AnyWebSocket, topic: []const u8, message: []const u8, opcode: Opcode, compress: bool) bool { + return switch (this) { + .ssl => c.uws_ws_publish_with_options(1, this.ssl.raw(), topic.ptr, topic.len, message.ptr, message.len, opcode, compress), + .tcp => c.uws_ws_publish_with_options(0, this.tcp.raw(), topic.ptr, topic.len, message.ptr, message.len, opcode, compress), + }; + } + + pub fn publishWithOptions(ssl: bool, app: *anyopaque, topic: []const u8, message: []const u8, opcode: Opcode, compress: bool) bool { + return switch (ssl) { + inline else => |tls| uws.NewApp(tls).publishWithOptions(@ptrCast(app), topic, message, opcode, compress), + }; + } + + pub fn getBufferedAmount(this: AnyWebSocket) usize { + return switch (this) { + .ssl => c.uws_ws_get_buffered_amount(1, this.ssl.raw()), + .tcp => c.uws_ws_get_buffered_amount(0, this.tcp.raw()), + }; + } + + pub fn getRemoteAddress(this: AnyWebSocket, buf: []u8) []u8 { + return switch (this) { + .ssl => this.ssl.getRemoteAddress(buf), + .tcp => this.tcp.getRemoteAddress(buf), + }; + } +}; + +pub const WebSocketBehavior = extern struct { + compression: c.uws_compress_options_t = 0, + maxPayloadLength: c_uint = std.math.maxInt(u32), + idleTimeout: c_ushort = 120, + maxBackpressure: c_uint = 1024 * 1024, + closeOnBackpressureLimit: bool = false, + resetIdleTimeoutOnSend: bool = true, + sendPingsAutomatically: bool = true, + maxLifetime: c_ushort = 0, + upgrade: uws_websocket_upgrade_handler = null, + open: uws_websocket_handler = null, + message: uws_websocket_message_handler = null, + drain: uws_websocket_handler = null, + ping: uws_websocket_ping_pong_handler = null, + pong: uws_websocket_ping_pong_handler = null, + close: uws_websocket_close_handler = null, + + pub fn Wrap( + comptime ServerType: type, + comptime Type: type, + comptime ssl: bool, + ) type { + return extern struct { + const is_ssl = ssl; + const WebSocket = NewApp(is_ssl).WebSocket; + const Server = ServerType; + + const active_field_name = if (is_ssl) "ssl" else "tcp"; + + pub fn onOpen(raw_ws: *RawWebSocket) callconv(.C) void { + const ws = @unionInit(AnyWebSocket, active_field_name, @as(*WebSocket, @ptrCast(raw_ws))); + const this = ws.as(Type).?; + @call(bun.callmod_inline, Type.onOpen, .{ + this, + ws, + }); + } + + pub fn onMessage(raw_ws: *RawWebSocket, message: [*c]const u8, length: usize, opcode: Opcode) callconv(.C) void { + const ws = @unionInit(AnyWebSocket, active_field_name, @as(*WebSocket, @ptrCast(raw_ws))); + const this = ws.as(Type).?; + @call(bun.callmod_inline, Type.onMessage, .{ + this, + ws, + if (length > 0) message[0..length] else "", + opcode, + }); + } + + pub fn onDrain(raw_ws: *RawWebSocket) callconv(.C) void { + const ws = @unionInit(AnyWebSocket, active_field_name, @as(*WebSocket, @ptrCast(raw_ws))); + const this = ws.as(Type).?; + @call(bun.callmod_inline, Type.onDrain, .{ + this, + ws, + }); + } + + pub fn onPing(raw_ws: *RawWebSocket, message: [*c]const u8, length: usize) callconv(.C) void { + const ws = @unionInit(AnyWebSocket, active_field_name, @as(*WebSocket, @ptrCast(raw_ws))); + const this = ws.as(Type).?; + @call(bun.callmod_inline, Type.onPing, .{ + this, + ws, + if (length > 0) message[0..length] else "", + }); + } + + pub fn onPong(raw_ws: *RawWebSocket, message: [*c]const u8, length: usize) callconv(.C) void { + const ws = @unionInit(AnyWebSocket, active_field_name, @as(*WebSocket, @ptrCast(raw_ws))); + const this = ws.as(Type).?; + @call(bun.callmod_inline, Type.onPong, .{ + this, + ws, + if (length > 0) message[0..length] else "", + }); + } + + pub fn onClose(raw_ws: *RawWebSocket, code: i32, message: [*c]const u8, length: usize) callconv(.C) void { + const ws = @unionInit(AnyWebSocket, active_field_name, @as(*WebSocket, @ptrCast(raw_ws))); + const this = ws.as(Type).?; + @call(bun.callmod_inline, Type.onClose, .{ + this, + ws, + code, + if (length > 0 and message != null) message[0..length] else "", + }); + } + + pub fn onUpgrade(ptr: *anyopaque, res: *uws_res, req: *Request, context: *uws.SocketContext, id: usize) callconv(.C) void { + @call(bun.callmod_inline, Server.onWebSocketUpgrade, .{ + bun.cast(*Server, ptr), + @as(*NewApp(is_ssl).Response, @ptrCast(res)), + req, + context, + id, + }); + } + + pub fn apply(behavior: WebSocketBehavior) WebSocketBehavior { + return .{ + .compression = behavior.compression, + .maxPayloadLength = behavior.maxPayloadLength, + .idleTimeout = behavior.idleTimeout, + .maxBackpressure = behavior.maxBackpressure, + .closeOnBackpressureLimit = behavior.closeOnBackpressureLimit, + .resetIdleTimeoutOnSend = behavior.resetIdleTimeoutOnSend, + .sendPingsAutomatically = behavior.sendPingsAutomatically, + .maxLifetime = behavior.maxLifetime, + .upgrade = onUpgrade, + .open = onOpen, + .message = if (@hasDecl(Type, "onMessage")) onMessage else null, + .drain = if (@hasDecl(Type, "onDrain")) onDrain else null, + .ping = if (@hasDecl(Type, "onPing")) onPing else null, + .pong = if (@hasDecl(Type, "onPong")) onPong else null, + .close = onClose, + }; + } + }; + } + + const uws_websocket_handler = ?*const fn (*RawWebSocket) callconv(.C) void; + const uws_websocket_message_handler = ?*const fn (*RawWebSocket, [*c]const u8, usize, Opcode) callconv(.C) void; + const uws_websocket_close_handler = ?*const fn (*RawWebSocket, i32, [*c]const u8, usize) callconv(.C) void; + const uws_websocket_upgrade_handler = ?*const fn (*anyopaque, *uws_res, *Request, *SocketContext, usize) callconv(.C) void; + const uws_websocket_ping_pong_handler = ?*const fn (*RawWebSocket, [*c]const u8, usize) callconv(.C) void; +}; + +pub const c = struct { + pub extern fn uws_ws_memory_cost(ssl: i32, ws: *RawWebSocket) usize; + pub extern fn uws_ws(ssl: i32, app: *uws_app_t, ctx: *anyopaque, pattern: [*]const u8, pattern_len: usize, id: usize, behavior: *const WebSocketBehavior) void; + pub extern fn uws_ws_get_user_data(ssl: i32, ws: ?*RawWebSocket) ?*anyopaque; + pub extern fn uws_ws_close(ssl: i32, ws: ?*RawWebSocket) void; + pub extern fn uws_ws_send(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, opcode: Opcode) SendStatus; + pub extern fn uws_ws_send_with_options(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, opcode: Opcode, compress: bool, fin: bool) SendStatus; + pub extern fn uws_ws_send_fragment(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, compress: bool) SendStatus; + pub extern fn uws_ws_send_first_fragment(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, compress: bool) SendStatus; + pub extern fn uws_ws_send_first_fragment_with_opcode(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, opcode: Opcode, compress: bool) SendStatus; + pub extern fn uws_ws_send_last_fragment(ssl: i32, ws: ?*RawWebSocket, message: [*c]const u8, length: usize, compress: bool) SendStatus; + pub extern fn uws_ws_end(ssl: i32, ws: ?*RawWebSocket, code: i32, message: [*c]const u8, length: usize) void; + pub extern fn uws_ws_cork(ssl: i32, ws: ?*RawWebSocket, handler: ?*const fn (?*anyopaque) callconv(.C) void, user_data: ?*anyopaque) void; + pub extern fn uws_ws_subscribe(ssl: i32, ws: ?*RawWebSocket, topic: [*c]const u8, length: usize) bool; + pub extern fn uws_ws_unsubscribe(ssl: i32, ws: ?*RawWebSocket, topic: [*c]const u8, length: usize) bool; + pub extern fn uws_ws_is_subscribed(ssl: i32, ws: ?*RawWebSocket, topic: [*c]const u8, length: usize) bool; + pub extern fn uws_ws_iterate_topics(ssl: i32, ws: ?*RawWebSocket, callback: ?*const fn ([*c]const u8, usize, ?*anyopaque) callconv(.C) void, user_data: ?*anyopaque) void; + pub extern fn uws_ws_publish(ssl: i32, ws: ?*RawWebSocket, topic: [*]const u8, topic_length: usize, message: [*]const u8, message_length: usize) bool; + pub extern fn uws_ws_publish_with_options(ssl: i32, ws: ?*RawWebSocket, topic: [*c]const u8, topic_length: usize, message: [*c]const u8, message_length: usize, opcode: Opcode, compress: bool) bool; + pub extern fn uws_ws_get_buffered_amount(ssl: i32, ws: ?*RawWebSocket) usize; + pub extern fn uws_ws_get_remote_address(ssl: i32, ws: ?*RawWebSocket, dest: *[*]u8) usize; + pub extern fn uws_ws_get_remote_address_as_text(ssl: i32, ws: ?*RawWebSocket, dest: *[*]u8) usize; + + pub const uws_compress_options_t = i32; +}; + +const bun = @import("bun"); +const uws = bun.uws; +const Opcode = uws.Opcode; +const SendStatus = uws.SendStatus; +const uws_app_t = @import("./App.zig").uws_app_t; +const std = @import("std"); +const NewApp = uws.NewApp; +const Request = uws.Request; +const uws_res = uws.uws_res; +const SocketContext = uws.SocketContext; diff --git a/src/deps/uws/WindowsNamedPipe.zig b/src/deps/uws/WindowsNamedPipe.zig new file mode 100644 index 0000000000..21939374d8 --- /dev/null +++ b/src/deps/uws/WindowsNamedPipe.zig @@ -0,0 +1,587 @@ +/// Wrapper that provides a socket-like API for Windows Named Pipes. +/// +/// This allows us to use the same networking interface and event handling +/// patterns across platforms, treating Named Pipes as if they were regular +/// sockets. The wrapper translates between µWebSockets' socket-based API +/// and Windows Named Pipe operations, enabling seamless cross-platform +/// IPC without requiring separate code paths for Windows vs Unix domain sockets. +/// +/// Integration with µWebSockets/uSockets: +/// - Uses the same event loop and timer mechanisms as other socket types +/// - Implements compatible handlers (onOpen, onData, onClose, etc.) that match uSockets callbacks +/// - Supports SSL/TLS wrapping through the same BoringSSL integration used by TCP sockets +/// - Provides streaming writer interface that mirrors uSockets' write operations +/// - Maintains the same connection lifecycle and state management as network sockets +/// - Enables transparent use of Named Pipes in contexts expecting standard socket APIs +/// +/// Uses libuv for the underlying Named Pipe operations while maintaining compatibility +/// with µWebSockets, bridging the gap between libuv's pipe handling and uSockets' +/// unified socket interface. +const WindowsNamedPipe = @This(); + +wrapper: ?WrapperType, +pipe: if (Environment.isWindows) ?*uv.Pipe else void, // any duplex +vm: *bun.JSC.VirtualMachine, //TODO: create a timeout version that dont need the JSC VM + +writer: bun.io.StreamingWriter(WindowsNamedPipe, .{ + .onClose = onClose, + .onWritable = onWritable, + .onError = onError, + .onWrite = onWrite, +}) = .{}, + +incoming: bun.ByteList = .{}, // Maybe we should use IPCBuffer here as well +ssl_error: CertError = .{}, +handlers: Handlers, +connect_req: uv.uv_connect_t = std.mem.zeroes(uv.uv_connect_t), + +event_loop_timer: EventLoopTimer = .{ + .next = .{}, + .tag = .WindowsNamedPipe, +}, +current_timeout: u32 = 0, +flags: Flags = .{}, + +pub const Flags = packed struct(u8) { + disconnected: bool = true, + is_closed: bool = false, + is_client: bool = false, + is_ssl: bool = false, + _: u4 = 0, +}; +pub const Handlers = struct { + ctx: *anyopaque, + onOpen: *const fn (*anyopaque) void, + onHandshake: *const fn (*anyopaque, bool, uws.us_bun_verify_error_t) void, + onData: *const fn (*anyopaque, []const u8) void, + onClose: *const fn (*anyopaque) void, + onEnd: *const fn (*anyopaque) void, + onWritable: *const fn (*anyopaque) void, + onError: *const fn (*anyopaque, bun.sys.Error) void, + onTimeout: *const fn (*anyopaque) void, +}; + +fn onWritable( + this: *WindowsNamedPipe, +) void { + log("onWritable", .{}); + // flush pending data + this.flush(); + // call onWritable (will flush on demand) + this.handlers.onWritable(this.handlers.ctx); +} + +fn onPipeClose(this: *WindowsNamedPipe) void { + log("onPipeClose", .{}); + this.flags.disconnected = true; + this.pipe = null; + this.onClose(); +} + +fn onReadAlloc(this: *WindowsNamedPipe, suggested_size: usize) []u8 { + var available = this.incoming.available(); + if (available.len < suggested_size) { + this.incoming.ensureUnusedCapacity(bun.default_allocator, suggested_size) catch bun.outOfMemory(); + available = this.incoming.available(); + } + return available.ptr[0..suggested_size]; +} + +fn onRead(this: *WindowsNamedPipe, buffer: []const u8) void { + log("onRead ({})", .{buffer.len}); + this.incoming.len += @as(u32, @truncate(buffer.len)); + bun.assert(this.incoming.len <= this.incoming.cap); + bun.assert(bun.isSliceInBuffer(buffer, this.incoming.allocatedSlice())); + + const data = this.incoming.slice(); + + this.resetTimeout(); + + if (this.wrapper) |*wrapper| { + wrapper.receiveData(data); + } else { + this.handlers.onData(this.handlers.ctx, data); + } + this.incoming.len = 0; +} + +fn onWrite(this: *WindowsNamedPipe, amount: usize, status: bun.io.WriteStatus) void { + log("onWrite {d} {}", .{ amount, status }); + + switch (status) { + .pending => {}, + .drained => { + // unref after sending all data + if (this.writer.source) |source| { + source.pipe.unref(); + } + }, + .end_of_file => { + // we send FIN so we close after this + this.writer.close(); + }, + } +} + +fn onReadError(this: *WindowsNamedPipe, err: bun.sys.E) void { + log("onReadError", .{}); + if (err == .EOF) { + // we received FIN but we dont allow half-closed connections right now + this.handlers.onEnd(this.handlers.ctx); + } else { + this.onError(bun.sys.Error.fromCode(err, .read)); + } + this.writer.close(); +} + +fn onError(this: *WindowsNamedPipe, err: bun.sys.Error) void { + log("onError", .{}); + this.handlers.onError(this.handlers.ctx, err); + this.close(); +} + +fn onOpen(this: *WindowsNamedPipe) void { + log("onOpen", .{}); + this.handlers.onOpen(this.handlers.ctx); +} + +fn onData(this: *WindowsNamedPipe, decoded_data: []const u8) void { + log("onData ({})", .{decoded_data.len}); + this.handlers.onData(this.handlers.ctx, decoded_data); +} + +fn onHandshake(this: *WindowsNamedPipe, handshake_success: bool, ssl_error: uws.us_bun_verify_error_t) void { + log("onHandshake", .{}); + + this.ssl_error = .{ + .error_no = ssl_error.error_no, + .code = if (ssl_error.code == null or ssl_error.error_no == 0) "" else bun.default_allocator.dupeZ(u8, ssl_error.code[0..bun.len(ssl_error.code) :0]) catch bun.outOfMemory(), + .reason = if (ssl_error.reason == null or ssl_error.error_no == 0) "" else bun.default_allocator.dupeZ(u8, ssl_error.reason[0..bun.len(ssl_error.reason) :0]) catch bun.outOfMemory(), + }; + this.handlers.onHandshake(this.handlers.ctx, handshake_success, ssl_error); +} + +fn onClose(this: *WindowsNamedPipe) void { + log("onClose", .{}); + if (!this.flags.is_closed) { + this.flags.is_closed = true; // only call onClose once + this.handlers.onClose(this.handlers.ctx); + this.deinit(); + } +} + +fn callWriteOrEnd(this: *WindowsNamedPipe, data: ?[]const u8, msg_more: bool) void { + if (data) |bytes| { + if (bytes.len > 0) { + // ref because we have pending data + if (this.writer.source) |source| { + source.pipe.ref(); + } + if (this.flags.disconnected) { + // enqueue to be sent after connecting + this.writer.outgoing.write(bytes) catch bun.outOfMemory(); + } else { + // write will enqueue the data if it cannot be sent + _ = this.writer.write(bytes); + } + } + } + + if (!msg_more) { + if (this.wrapper) |*wrapper| { + _ = wrapper.shutdown(false); + } + this.writer.end(); + } +} + +fn internalWrite(this: *WindowsNamedPipe, encoded_data: []const u8) void { + this.resetTimeout(); + + // Possible scenarios: + // Scenario 1: will not write if is not connected yet but will enqueue the data + // Scenario 2: will not write if a exception is thrown (will be handled by onError) + // Scenario 3: will be queued in memory and will be flushed later + // Scenario 4: no write/end function exists (will be handled by onError) + this.callWriteOrEnd(encoded_data, true); +} + +pub fn resumeStream(this: *WindowsNamedPipe) bool { + const stream = this.writer.getStream() orelse { + return false; + }; + const readStartResult = stream.readStart(this, onReadAlloc, onReadError, onRead); + if (readStartResult == .err) { + return false; + } + return true; +} + +pub fn pauseStream(this: *WindowsNamedPipe) bool { + const pipe = this.pipe orelse { + return false; + }; + pipe.readStop(); + return true; +} + +pub fn flush(this: *WindowsNamedPipe) void { + if (this.wrapper) |*wrapper| { + _ = wrapper.flush(); + } + if (!this.flags.disconnected) { + _ = this.writer.flush(); + } +} + +fn onInternalReceiveData(this: *WindowsNamedPipe, data: []const u8) void { + if (this.wrapper) |*wrapper| { + this.resetTimeout(); + wrapper.receiveData(data); + } +} + +pub fn onTimeout(this: *WindowsNamedPipe) EventLoopTimer.Arm { + log("onTimeout", .{}); + + const has_been_cleared = this.event_loop_timer.state == .CANCELLED or this.vm.scriptExecutionStatus() != .running; + + this.event_loop_timer.state = .FIRED; + this.event_loop_timer.heap = .{}; + + if (has_been_cleared) { + return .disarm; + } + + this.handlers.onTimeout(this.handlers.ctx); + + return .disarm; +} + +pub fn from( + pipe: *uv.Pipe, + handlers: WindowsNamedPipe.Handlers, + vm: *JSC.VirtualMachine, +) WindowsNamedPipe { + if (Environment.isPosix) { + @compileError("WindowsNamedPipe is not supported on POSIX systems"); + } + return WindowsNamedPipe{ + .vm = vm, + .pipe = pipe, + .wrapper = null, + .handlers = handlers, + }; +} +fn onConnect(this: *WindowsNamedPipe, status: uv.ReturnCode) void { + if (this.pipe) |pipe| { + _ = pipe.unref(); + } + + if (status.toError(.connect)) |err| { + this.onError(err); + return; + } + + this.flags.disconnected = false; + if (this.start(true)) { + if (this.isTLS()) { + if (this.wrapper) |*wrapper| { + // trigger onOpen and start the handshake + wrapper.start(); + } + } else { + // trigger onOpen + this.onOpen(); + } + } + this.flush(); +} + +pub fn getAcceptedBy(this: *WindowsNamedPipe, server: *uv.Pipe, ssl_ctx: ?*BoringSSL.SSL_CTX) JSC.Maybe(void) { + bun.assert(this.pipe != null); + this.flags.disconnected = true; + + if (ssl_ctx) |tls| { + this.flags.is_ssl = true; + this.wrapper = WrapperType.initWithCTX(tls, false, .{ + .ctx = this, + .onOpen = WindowsNamedPipe.onOpen, + .onHandshake = WindowsNamedPipe.onHandshake, + .onData = WindowsNamedPipe.onData, + .onClose = WindowsNamedPipe.onClose, + .write = WindowsNamedPipe.internalWrite, + }) catch { + return .{ + .err = .{ + .errno = @intFromEnum(bun.sys.E.PIPE), + .syscall = .connect, + }, + }; + }; + // ref because we are accepting will unref when wrapper deinit + _ = BoringSSL.SSL_CTX_up_ref(tls); + } + const initResult = this.pipe.?.init(this.vm.uvLoop(), false); + if (initResult == .err) { + return initResult; + } + + const openResult = server.accept(this.pipe.?); + if (openResult == .err) { + return openResult; + } + + this.flags.disconnected = false; + if (this.start(false)) { + if (this.isTLS()) { + if (this.wrapper) |*wrapper| { + // trigger onOpen and start the handshake + wrapper.start(); + } + } else { + // trigger onOpen + this.onOpen(); + } + } + return .{ .result = {} }; +} +pub fn open(this: *WindowsNamedPipe, fd: bun.FileDescriptor, ssl_options: ?JSC.API.ServerConfig.SSLConfig) JSC.Maybe(void) { + bun.assert(this.pipe != null); + this.flags.disconnected = true; + + if (ssl_options) |tls| { + this.flags.is_ssl = true; + this.wrapper = WrapperType.init(tls, true, .{ + .ctx = this, + .onOpen = WindowsNamedPipe.onOpen, + .onHandshake = WindowsNamedPipe.onHandshake, + .onData = WindowsNamedPipe.onData, + .onClose = WindowsNamedPipe.onClose, + .write = WindowsNamedPipe.internalWrite, + }) catch { + return .{ + .err = .{ + .errno = @intFromEnum(bun.sys.E.PIPE), + .syscall = .connect, + }, + }; + }; + } + const initResult = this.pipe.?.init(this.vm.uvLoop(), false); + if (initResult == .err) { + return initResult; + } + + const openResult = this.pipe.?.open(fd); + if (openResult == .err) { + return openResult; + } + + onConnect(this, uv.ReturnCode.zero); + return .{ .result = {} }; +} + +pub fn connect(this: *WindowsNamedPipe, path: []const u8, ssl_options: ?JSC.API.ServerConfig.SSLConfig) JSC.Maybe(void) { + bun.assert(this.pipe != null); + this.flags.disconnected = true; + // ref because we are connecting + _ = this.pipe.?.ref(); + + if (ssl_options) |tls| { + this.flags.is_ssl = true; + this.wrapper = WrapperType.init(tls, true, .{ + .ctx = this, + .onOpen = WindowsNamedPipe.onOpen, + .onHandshake = WindowsNamedPipe.onHandshake, + .onData = WindowsNamedPipe.onData, + .onClose = WindowsNamedPipe.onClose, + .write = WindowsNamedPipe.internalWrite, + }) catch { + return .{ + .err = .{ + .errno = @intFromEnum(bun.sys.E.PIPE), + .syscall = .connect, + }, + }; + }; + } + const initResult = this.pipe.?.init(this.vm.uvLoop(), false); + if (initResult == .err) { + return initResult; + } + + this.connect_req.data = this; + return this.pipe.?.connect(&this.connect_req, path, this, onConnect); +} +pub fn startTLS(this: *WindowsNamedPipe, ssl_options: JSC.API.ServerConfig.SSLConfig, is_client: bool) !void { + this.flags.is_ssl = true; + if (this.start(is_client)) { + this.wrapper = try WrapperType.init(ssl_options, is_client, .{ + .ctx = this, + .onOpen = WindowsNamedPipe.onOpen, + .onHandshake = WindowsNamedPipe.onHandshake, + .onData = WindowsNamedPipe.onData, + .onClose = WindowsNamedPipe.onClose, + .write = WindowsNamedPipe.internalWrite, + }); + + this.wrapper.?.start(); + } +} + +pub fn start(this: *WindowsNamedPipe, is_client: bool) bool { + this.flags.is_client = is_client; + if (this.pipe == null) { + return false; + } + _ = this.pipe.?.unref(); + this.writer.setParent(this); + const startPipeResult = this.writer.startWithPipe(this.pipe.?); + if (startPipeResult == .err) { + this.onError(startPipeResult.err); + return false; + } + const stream = this.writer.getStream() orelse { + this.onError(bun.sys.Error.fromCode(bun.sys.E.PIPE, .read)); + return false; + }; + + const readStartResult = stream.readStart(this, onReadAlloc, onReadError, onRead); + if (readStartResult == .err) { + this.onError(readStartResult.err); + return false; + } + return true; +} + +pub fn isTLS(this: *WindowsNamedPipe) bool { + return this.flags.is_ssl; +} + +pub fn encodeAndWrite(this: *WindowsNamedPipe, data: []const u8, is_end: bool) i32 { + log("encodeAndWrite (len: {} - is_end: {})", .{ data.len, is_end }); + if (this.wrapper) |*wrapper| { + return @as(i32, @intCast(wrapper.writeData(data) catch 0)); + } else { + this.internalWrite(data); + } + return @intCast(data.len); +} + +pub fn rawWrite(this: *WindowsNamedPipe, encoded_data: []const u8, _: bool) i32 { + this.internalWrite(encoded_data); + return @intCast(encoded_data.len); +} + +pub fn close(this: *WindowsNamedPipe) void { + if (this.wrapper) |*wrapper| { + _ = wrapper.shutdown(false); + } + this.writer.end(); +} + +pub fn shutdown(this: *WindowsNamedPipe) void { + if (this.wrapper) |*wrapper| { + _ = wrapper.shutdown(false); + } +} + +pub fn shutdownRead(this: *WindowsNamedPipe) void { + if (this.wrapper) |*wrapper| { + _ = wrapper.shutdownRead(); + } else { + if (this.writer.getStream()) |stream| { + _ = stream.readStop(); + } + } +} + +pub fn isShutdown(this: *WindowsNamedPipe) bool { + if (this.wrapper) |wrapper| { + return wrapper.isShutdown(); + } + + return this.flags.disconnected or this.writer.is_done; +} + +pub fn isClosed(this: *WindowsNamedPipe) bool { + if (this.wrapper) |wrapper| { + return wrapper.isClosed(); + } + return this.flags.disconnected; +} + +pub fn isEstablished(this: *WindowsNamedPipe) bool { + return !this.isClosed(); +} + +pub fn ssl(this: *WindowsNamedPipe) ?*BoringSSL.SSL { + if (this.wrapper) |wrapper| { + return wrapper.ssl; + } + return null; +} + +pub fn sslError(this: *WindowsNamedPipe) us_bun_verify_error_t { + return .{ + .error_no = this.ssl_error.error_no, + .code = @ptrCast(this.ssl_error.code.ptr), + .reason = @ptrCast(this.ssl_error.reason.ptr), + }; +} + +pub fn resetTimeout(this: *WindowsNamedPipe) void { + this.setTimeoutInMilliseconds(this.current_timeout); +} +pub fn setTimeoutInMilliseconds(this: *WindowsNamedPipe, ms: c_uint) void { + if (this.event_loop_timer.state == .ACTIVE) { + this.vm.timer.remove(&this.event_loop_timer); + } + this.current_timeout = ms; + + // if the interval is 0 means that we stop the timer + if (ms == 0) { + return; + } + + // reschedule the timer + this.event_loop_timer.next = bun.timespec.msFromNow(ms); + this.vm.timer.insert(&this.event_loop_timer); +} +pub fn setTimeout(this: *WindowsNamedPipe, seconds: c_uint) void { + log("setTimeout({d})", .{seconds}); + this.setTimeoutInMilliseconds(seconds * 1000); +} +/// Free internal resources, it can be called multiple times +pub fn deinit(this: *WindowsNamedPipe) void { + log("deinit", .{}); + // clear the timer + this.setTimeout(0); + if (this.writer.getStream()) |stream| { + _ = stream.readStop(); + } + this.writer.deinit(); + if (this.wrapper) |*wrapper| { + wrapper.deinit(); + this.wrapper = null; + } + var ssl_error = this.ssl_error; + ssl_error.deinit(); + this.ssl_error = .{}; +} + +pub const CertError = UpgradedDuplex.CertError; +const WrapperType = SSLWrapper(*WindowsNamedPipe); +const uv = bun.windows.libuv; +const bun = @import("bun"); +const JSC = bun.JSC; +const uws = bun.uws; +const BoringSSL = bun.BoringSSL.c; +const EventLoopTimer = bun.api.Timer.EventLoopTimer; +const us_bun_verify_error_t = uws.us_bun_verify_error_t; +const log = bun.Output.scoped(.WindowsNamedPipe, false); +const SSLWrapper = @import("../../bun.js/api/bun/ssl_wrapper.zig").SSLWrapper; +const Environment = bun.Environment; +const std = @import("std"); +const UpgradedDuplex = uws.UpgradedDuplex; diff --git a/src/deps/uws/socket.zig b/src/deps/uws/socket.zig index d2cc4e4f14..b2f8d7c22b 100644 --- a/src/deps/uws/socket.zig +++ b/src/deps/uws/socket.zig @@ -1,204 +1,1227 @@ -const std = @import("std"); -const bun = @import("bun"); -const uws = @import("../uws.zig"); +pub fn NewSocketHandler(comptime is_ssl: bool) type { + return struct { + const ssl_int: i32 = @intFromBool(is_ssl); -const SocketContext = uws.SocketContext; + socket: InternalSocket, -const debug = bun.Output.scoped(.uws, false); -const max_i32 = std.math.maxInt(i32); + const ThisSocket = @This(); -/// Zig bindings for `us_socket_t` -pub const Socket = opaque { - pub const CloseCode = enum(i32) { - normal = 0, - failure = 1, + pub const detached: NewSocketHandler(is_ssl) = NewSocketHandler(is_ssl){ .socket = .{ .detached = {} } }; + + pub fn setNoDelay(this: ThisSocket, enabled: bool) bool { + return this.socket.setNoDelay(enabled); + } + + pub fn setKeepAlive(this: ThisSocket, enabled: bool, delay: u32) bool { + return this.socket.setKeepAlive(enabled, delay); + } + + pub fn pauseStream(this: ThisSocket) bool { + return this.socket.pauseResume(is_ssl, true); + } + + pub fn resumeStream(this: ThisSocket) bool { + return this.socket.pauseResume(is_ssl, false); + } + + pub fn detach(this: *ThisSocket) void { + this.socket.detach(); + } + + pub fn isDetached(this: ThisSocket) bool { + return this.socket.isDetached(); + } + + pub fn isNamedPipe(this: ThisSocket) bool { + return this.socket.isNamedPipe(); + } + + pub fn getVerifyError(this: ThisSocket) uws.us_bun_verify_error_t { + switch (this.socket) { + .connected => |socket| return socket.getVerifyError(is_ssl), + .upgradedDuplex => |socket| return socket.sslError(), + .pipe => |pipe| if (Environment.isWindows) return pipe.sslError() else return std.mem.zeroes(us_bun_verify_error_t), + .connecting, .detached => return std.mem.zeroes(us_bun_verify_error_t), + } + } + + pub fn isEstablished(this: ThisSocket) bool { + switch (this.socket) { + .connected => |socket| return socket.isEstablished(comptime is_ssl), + .upgradedDuplex => |socket| return socket.isEstablished(), + .pipe => |pipe| if (Environment.isWindows) return pipe.isEstablished() else return false, + .connecting, .detached => return false, + } + } + + pub fn timeout(this: ThisSocket, seconds: c_uint) void { + switch (this.socket) { + .upgradedDuplex => |socket| socket.setTimeout(seconds), + .pipe => |pipe| if (Environment.isWindows) pipe.setTimeout(seconds), + .connected => |socket| socket.setTimeout(is_ssl, seconds), + .connecting => |socket| socket.timeout(is_ssl, seconds), + .detached => {}, + } + } + + pub fn setTimeout(this: ThisSocket, seconds: c_uint) void { + switch (this.socket) { + .connected => |socket| { + if (seconds > 240) { + socket.setTimeout(is_ssl, 0); + socket.setLongTimeout(is_ssl, seconds / 60); + } else { + socket.setTimeout(is_ssl, seconds); + socket.setLongTimeout(is_ssl, 0); + } + }, + .connecting => |socket| { + if (seconds > 240) { + socket.timeout(is_ssl, 0); + socket.longTimeout(is_ssl, seconds / 60); + } else { + socket.timeout(is_ssl, seconds); + socket.longTimeout(is_ssl, 0); + } + }, + .detached => {}, + .upgradedDuplex => |socket| socket.setTimeout(seconds), + .pipe => |pipe| if (Environment.isWindows) pipe.setTimeout(seconds), + } + } + + pub fn setTimeoutMinutes(this: ThisSocket, minutes: c_uint) void { + switch (this.socket) { + .connected => |socket| { + socket.setTimeout(is_ssl, 0); + socket.setLongTimeout(is_ssl, minutes); + }, + .connecting => |socket| { + socket.timeout(is_ssl, 0); + socket.longTimeout(is_ssl, minutes); + }, + .detached => {}, + .upgradedDuplex => |socket| socket.setTimeout(minutes * 60), + .pipe => |pipe| if (Environment.isWindows) pipe.setTimeout(minutes * 60), + } + } + + pub fn startTLS(this: ThisSocket, is_client: bool) void { + if (this.socket.get()) |socket| socket.open(is_ssl, is_client, null); + } + + pub fn ssl(this: ThisSocket) ?*BoringSSL.SSL { + if (comptime is_ssl) { + if (this.getNativeHandle()) |handle| { + return @as(*BoringSSL.SSL, @ptrCast(handle)); + } + return null; + } + return null; + } + + // Note: this assumes that the socket is non-TLS and will be adopted and wrapped with a new TLS context + // context ext will not be copied to the new context, new context will contain us_wrapped_socket_context_t on ext + pub fn wrapTLS( + this: ThisSocket, + options: SocketContext.BunSocketContextOptions, + socket_ext_size: i32, + comptime deref: bool, + comptime ContextType: type, + comptime Fields: anytype, + ) ?NewSocketHandler(true) { + const TLSSocket = NewSocketHandler(true); + const SocketHandler = struct { + const alignment = if (ContextType == anyopaque) + @sizeOf(usize) + else + std.meta.alignment(ContextType); + const deref_ = deref; + const ValueType = if (deref) ContextType else *ContextType; + fn getValue(socket: *us_socket_t) ValueType { + if (comptime ContextType == anyopaque) { + return socket.ext(true); + } + + if (comptime deref_) { + return (TLSSocket.from(socket)).ext(ContextType).?.*; + } + + return (TLSSocket.from(socket)).ext(ContextType); + } + + pub fn on_open(socket: *us_socket_t, is_client: i32, _: [*c]u8, _: i32) callconv(.C) ?*us_socket_t { + if (comptime @hasDecl(Fields, "onCreate")) { + if (is_client == 0) { + Fields.onCreate( + TLSSocket.from(socket), + ); + } + } + Fields.onOpen( + getValue(socket), + TLSSocket.from(socket), + ); + return socket; + } + pub fn on_close(socket: *us_socket_t, code: i32, reason: ?*anyopaque) callconv(.C) ?*us_socket_t { + Fields.onClose( + getValue(socket), + TLSSocket.from(socket), + code, + reason, + ); + return socket; + } + pub fn on_data(socket: *us_socket_t, buf: ?[*]u8, len: i32) callconv(.C) ?*us_socket_t { + Fields.onData( + getValue(socket), + TLSSocket.from(socket), + buf.?[0..@as(usize, @intCast(len))], + ); + return socket; + } + pub fn on_writable(socket: *us_socket_t) callconv(.C) ?*us_socket_t { + Fields.onWritable( + getValue(socket), + TLSSocket.from(socket), + ); + return socket; + } + pub fn on_timeout(socket: *us_socket_t) callconv(.C) ?*us_socket_t { + Fields.onTimeout( + getValue(socket), + TLSSocket.from(socket), + ); + return socket; + } + pub fn on_long_timeout(socket: *us_socket_t) callconv(.C) ?*us_socket_t { + Fields.onLongTimeout( + getValue(socket), + TLSSocket.from(socket), + ); + return socket; + } + pub fn on_connect_error(socket: *us_socket_t, code: i32) callconv(.C) ?*us_socket_t { + Fields.onConnectError( + TLSSocket.from(socket).ext(ContextType).?.*, + TLSSocket.from(socket), + code, + ); + return socket; + } + pub fn on_connect_error_connecting_socket(socket: *ConnectingSocket, code: i32) callconv(.C) ?*ConnectingSocket { + Fields.onConnectError( + @as(*align(alignment) ContextType, @ptrCast(@alignCast(socket.ext(comptime is_ssl)))).*, + TLSSocket.fromConnecting(socket), + code, + ); + return socket; + } + pub fn on_end(socket: *us_socket_t) callconv(.C) ?*us_socket_t { + Fields.onEnd( + getValue(socket), + TLSSocket.from(socket), + ); + return socket; + } + pub fn on_handshake(socket: *us_socket_t, success: i32, verify_error: us_bun_verify_error_t, _: ?*anyopaque) callconv(.C) void { + Fields.onHandshake(getValue(socket), TLSSocket.from(socket), success, verify_error); + } + }; + + const events: c.us_socket_events_t = .{ + .on_open = SocketHandler.on_open, + .on_close = SocketHandler.on_close, + .on_data = SocketHandler.on_data, + .on_writable = SocketHandler.on_writable, + .on_timeout = SocketHandler.on_timeout, + .on_connect_error = SocketHandler.on_connect_error, + .on_connect_error_connecting_socket = SocketHandler.on_connect_error_connecting_socket, + .on_end = SocketHandler.on_end, + .on_handshake = SocketHandler.on_handshake, + .on_long_timeout = SocketHandler.on_long_timeout, + }; + + const this_socket = this.socket.get() orelse return null; + + const socket = c.us_socket_wrap_with_tls(ssl_int, this_socket, options, events, socket_ext_size) orelse return null; + return NewSocketHandler(true).from(socket); + } + + pub fn getNativeHandle(this: ThisSocket) ?*NativeSocketHandleType(is_ssl) { + return @ptrCast(switch (this.socket) { + .connected => |socket| socket.getNativeHandle(is_ssl), + .connecting => |socket| socket.getNativeHandle(is_ssl), + .detached => null, + .upgradedDuplex => |socket| if (is_ssl) @as(*anyopaque, @ptrCast(socket.ssl() orelse return null)) else null, + .pipe => |socket| if (is_ssl and Environment.isWindows) @as(*anyopaque, @ptrCast(socket.ssl() orelse return null)) else null, + } orelse return null); + } + + pub inline fn fd(this: ThisSocket) bun.FileDescriptor { + if (comptime is_ssl) { + @compileError("SSL sockets do not have a file descriptor accessible this way"); + } + const socket = this.socket.get() orelse return bun.invalid_fd; + + // on windows uSockets exposes SOCKET + return if (comptime Environment.isWindows) + .fromNative(@ptrCast(socket.getNativeHandle(is_ssl).?)) + else + .fromNative(@intCast(@intFromPtr(socket.getNativeHandle(is_ssl)))); + } + + pub fn markNeedsMoreForSendfile(this: ThisSocket) void { + if (comptime is_ssl) { + @compileError("SSL sockets do not support sendfile yet"); + } + const socket = this.socket.get() orelse return; + socket.sendFileNeedsMore(); + } + + pub fn ext(this: ThisSocket, comptime ContextType: type) ?*ContextType { + const alignment = if (ContextType == *anyopaque) + @sizeOf(usize) + else + std.meta.alignment(ContextType); + + const ptr = switch (this.socket) { + .connected => |sock| sock.ext(is_ssl), + .connecting => |sock| sock.ext(is_ssl), + .detached => return null, + .upgradedDuplex => return null, + .pipe => return null, + }; + + return @as(*align(alignment) ContextType, @ptrCast(@alignCast(ptr))); + } + + /// This can be null if the socket was closed. + pub fn context(this: ThisSocket) ?*SocketContext { + switch (this.socket) { + .connected => |socket| return socket.context(is_ssl), + .connecting => |socket| return socket.context(is_ssl), + .detached => return null, + .upgradedDuplex => return null, + .pipe => return null, + } + } + + pub fn flush(this: ThisSocket) void { + switch (this.socket) { + .upgradedDuplex => |socket| socket.flush(), + .pipe => |pipe| if (comptime Environment.isWindows) pipe.flush(), + .connected => |socket| socket.flush(is_ssl), + .connecting, .detached => return, + } + } + + pub fn write(this: ThisSocket, data: []const u8, msg_more: bool) i32 { + return switch (this.socket) { + .upgradedDuplex => |socket| socket.encodeAndWrite(data, msg_more), + .pipe => |pipe| if (comptime Environment.isWindows) pipe.encodeAndWrite(data, msg_more) else 0, + .connected => |socket| socket.write(is_ssl, data, msg_more), + .connecting, .detached => 0, + }; + } + + pub fn writeFd(this: ThisSocket, data: []const u8, file_descriptor: bun.FileDescriptor) i32 { + return switch (this.socket) { + .upgradedDuplex, .pipe => this.write(data, false), + .connected => |socket| socket.writeFd(data, file_descriptor), + .connecting, .detached => 0, + }; + } + + pub fn rawWrite(this: ThisSocket, data: []const u8, msg_more: bool) i32 { + return switch (this.socket) { + .connected => |socket| socket.rawWrite(is_ssl, data, msg_more), + .connecting, .detached => 0, + .upgradedDuplex => |socket| socket.rawWrite(data, msg_more), + .pipe => |pipe| if (comptime Environment.isWindows) pipe.rawWrite(data, msg_more) else 0, + }; + } + + pub fn shutdown(this: ThisSocket) void { + switch (this.socket) { + .connected => |socket| socket.shutdown(is_ssl), + .connecting => |socket| { + debug("us_connecting_socket_shutdown({d})", .{@intFromPtr(socket)}); + return socket.shutdown(is_ssl); + }, + .detached => {}, + .upgradedDuplex => |socket| socket.shutdown(), + .pipe => |pipe| if (comptime Environment.isWindows) pipe.shutdown(), + } + } + + pub fn shutdownRead(this: ThisSocket) void { + switch (this.socket) { + .connected => |socket| socket.shutdownRead(is_ssl), + .connecting => |socket| { + debug("us_connecting_socket_shutdown_read({d})", .{@intFromPtr(socket)}); + return socket.shutdownRead(is_ssl); + }, + .upgradedDuplex => |socket| socket.shutdownRead(), + .pipe => |pipe| if (comptime Environment.isWindows) pipe.shutdownRead(), + .detached => {}, + } + } + + pub fn isShutdown(this: ThisSocket) bool { + return switch (this.socket) { + .connected => |socket| socket.isShutdown(is_ssl), + .connecting => |socket| blk: { + debug("us_connecting_socket_is_shut_down({d})", .{@intFromPtr(socket)}); + break :blk socket.isShutdown(is_ssl); + }, + .upgradedDuplex => |socket| socket.isShutdown(), + .pipe => |pipe| return if (Environment.isWindows) pipe.isShutdown() else false, + .detached => true, + }; + } + + pub fn isClosedOrHasError(this: ThisSocket) bool { + if (this.isClosed() or this.isShutdown()) { + return true; + } + + return this.getError() != 0; + } + + pub fn getError(this: ThisSocket) i32 { + switch (this.socket) { + .connected => |socket| { + debug("us_socket_get_error({d})", .{@intFromPtr(socket)}); + return socket.getError(is_ssl); + }, + .connecting => |socket| { + debug("us_connecting_socket_get_error({d})", .{@intFromPtr(socket)}); + return socket.getError(is_ssl); + }, + .detached => return 0, + .upgradedDuplex => |socket| { + return socket.sslError().error_no; + }, + .pipe => |pipe| { + return if (Environment.isWindows) pipe.sslError().error_no else 0; + }, + } + } + + pub fn isClosed(this: ThisSocket) bool { + return this.socket.isClosed(comptime is_ssl); + } + + pub fn close(this: ThisSocket, code: us_socket_t.CloseCode) void { + return this.socket.close(comptime is_ssl, code); + } + + pub fn localPort(this: ThisSocket) i32 { + return switch (this.socket) { + .connected => |socket| socket.localPort(is_ssl), + .pipe, .upgradedDuplex, .connecting, .detached => 0, + }; + } + + pub fn remotePort(this: ThisSocket) i32 { + return switch (this.socket) { + .connected => |socket| socket.remotePort(is_ssl), + .pipe, .upgradedDuplex, .connecting, .detached => 0, + }; + } + + /// `buf` cannot be longer than 2^31 bytes long. + pub fn remoteAddress(this: ThisSocket, buf: []u8) ?[]const u8 { + return switch (this.socket) { + .connected => |sock| sock.remoteAddress(is_ssl, buf) catch |e| { + bun.Output.panic("Failed to get socket's remote address: {s}", .{@errorName(e)}); + }, + .pipe, .upgradedDuplex, .connecting, .detached => null, + }; + } + + /// Get the local address of a socket in binary format. + /// + /// # Arguments + /// - `buf`: A buffer to store the binary address data. + /// + /// # Returns + /// This function returns a slice of the buffer on success, or null on failure. + pub fn localAddress(this: ThisSocket, buf: []u8) ?[]const u8 { + return switch (this.socket) { + .connected => |sock| sock.localAddress(is_ssl, buf) catch |e| { + bun.Output.panic("Failed to get socket's local address: {s}", .{@errorName(e)}); + }, + .pipe, .upgradedDuplex, .connecting, .detached => null, + }; + } + + pub fn connect( + host: []const u8, + port: i32, + socket_ctx: *SocketContext, + comptime Context: type, + ctx: Context, + comptime socket_field_name: []const u8, + allowHalfOpen: bool, + ) ?*Context { + debug("connect({s}, {d})", .{ host, port }); + + var stack_fallback = std.heap.stackFallback(1024, bun.default_allocator); + var allocator = stack_fallback.get(); + + // remove brackets from IPv6 addresses, as getaddrinfo doesn't understand them + const clean_host = if (host.len > 1 and host[0] == '[' and host[host.len - 1] == ']') + host[1 .. host.len - 1] + else + host; + + const host_ = allocator.dupeZ(u8, clean_host) catch bun.outOfMemory(); + defer allocator.free(host); + + var did_dns_resolve: i32 = 0; + const socket = socket_ctx.connect(is_ssl, host_, port, if (allowHalfOpen) uws.LIBUS_SOCKET_ALLOW_HALF_OPEN else 0, @sizeOf(Context), &did_dns_resolve) orelse return null; + const socket_ = if (did_dns_resolve == 1) + ThisSocket{ + .socket = .{ .connected = @ptrCast(socket) }, + } + else + ThisSocket{ + .socket = .{ .connecting = @ptrCast(socket) }, + }; + + var holder = socket_.ext(Context); + holder.* = ctx; + @field(holder, socket_field_name) = socket_; + return holder; + } + + pub fn connectPtr( + host: []const u8, + port: i32, + socket_ctx: *SocketContext, + comptime Context: type, + ctx: *Context, + comptime socket_field_name: []const u8, + allowHalfOpen: bool, + ) !*Context { + const this_socket = try connectAnon(host, port, socket_ctx, ctx, allowHalfOpen); + @field(ctx, socket_field_name) = this_socket; + return ctx; + } + + pub fn fromDuplex( + duplex: *UpgradedDuplex, + ) ThisSocket { + return ThisSocket{ .socket = .{ .upgradedDuplex = duplex } }; + } + + pub fn fromNamedPipe( + pipe: *WindowsNamedPipe, + ) ThisSocket { + if (Environment.isWindows) { + return ThisSocket{ .socket = .{ .pipe = pipe } }; + } + @compileError("WindowsNamedPipe is only available on Windows"); + } + + pub fn fromFd( + ctx: *SocketContext, + handle: bun.FileDescriptor, + comptime This: type, + this: *This, + comptime socket_field_name: ?[]const u8, + is_ipc: bool, + ) ?ThisSocket { + const socket_ = ThisSocket{ + .socket = .{ + .connected = us_socket_t.fromFd( + ctx, + @sizeOf(*anyopaque), + handle.native(), + @intFromBool(is_ipc), + ) orelse return null, + }, + }; + + if (socket_.ext(*anyopaque)) |holder| { + holder.* = this; + } + + if (comptime socket_field_name) |field| { + @field(this, field) = socket_; + } + + return socket_; + } + + pub fn connectUnixPtr( + path: []const u8, + socket_ctx: *SocketContext, + comptime Context: type, + ctx: *Context, + comptime socket_field_name: []const u8, + ) !*Context { + const this_socket = try connectUnixAnon(path, socket_ctx, ctx); + @field(ctx, socket_field_name) = this_socket; + return ctx; + } + + pub fn connectUnixAnon( + path: []const u8, + socket_ctx: *SocketContext, + ctx: *anyopaque, + allowHalfOpen: bool, + ) !ThisSocket { + debug("connect(unix:{s})", .{path}); + var stack_fallback = std.heap.stackFallback(1024, bun.default_allocator); + var allocator = stack_fallback.get(); + const path_ = allocator.dupeZ(u8, path) catch bun.outOfMemory(); + defer allocator.free(path_); + + const socket = socket_ctx.connectUnix(is_ssl, path_, if (allowHalfOpen) uws.LIBUS_SOCKET_ALLOW_HALF_OPEN else 0, 8) orelse + return error.FailedToOpenSocket; + + const socket_ = ThisSocket{ .socket = .{ .connected = socket } }; + if (socket_.ext(*anyopaque)) |holder| { + holder.* = ctx; + } + return socket_; + } + + pub fn connectAnon( + raw_host: []const u8, + port: i32, + socket_ctx: *SocketContext, + ptr: *anyopaque, + allowHalfOpen: bool, + ) !ThisSocket { + debug("connect({s}, {d})", .{ raw_host, port }); + var stack_fallback = std.heap.stackFallback(1024, bun.default_allocator); + var allocator = stack_fallback.get(); + + // remove brackets from IPv6 addresses, as getaddrinfo doesn't understand them + const clean_host = if (raw_host.len > 1 and raw_host[0] == '[' and raw_host[raw_host.len - 1] == ']') + raw_host[1 .. raw_host.len - 1] + else + raw_host; + + const host = allocator.dupeZ(u8, clean_host) catch bun.outOfMemory(); + defer allocator.free(host); + + var did_dns_resolve: i32 = 0; + const socket_ptr = socket_ctx.connect( + is_ssl, + host.ptr, + port, + if (allowHalfOpen) uws.LIBUS_SOCKET_ALLOW_HALF_OPEN else 0, + @sizeOf(*anyopaque), + &did_dns_resolve, + ) orelse return error.FailedToOpenSocket; + const socket = if (did_dns_resolve == 1) + ThisSocket{ + .socket = .{ .connected = @ptrCast(socket_ptr) }, + } + else + ThisSocket{ + .socket = .{ .connecting = @ptrCast(socket_ptr) }, + }; + if (socket.ext(*anyopaque)) |holder| { + holder.* = ptr; + } + return socket; + } + + pub fn unsafeConfigure( + ctx: *SocketContext, + comptime ssl_type: bool, + comptime deref: bool, + comptime ContextType: type, + comptime Fields: anytype, + ) void { + const SocketHandlerType = NewSocketHandler(ssl_type); + const Type = comptime if (@TypeOf(Fields) != type) @TypeOf(Fields) else Fields; + + const SocketHandler = struct { + const alignment = if (ContextType == anyopaque) + @sizeOf(usize) + else + std.meta.alignment(ContextType); + const deref_ = deref; + const ValueType = if (deref) ContextType else *ContextType; + fn getValue(socket: *us_socket_t) ValueType { + if (comptime ContextType == anyopaque) { + return socket.ext(is_ssl); + } + + if (comptime deref_) { + return (SocketHandlerType.from(socket)).ext(ContextType).?.*; + } + + return (SocketHandlerType.from(socket)).ext(ContextType); + } + + pub fn on_open(socket: *us_socket_t, is_client: i32, _: [*c]u8, _: i32) callconv(.C) ?*us_socket_t { + if (comptime @hasDecl(Fields, "onCreate")) { + if (is_client == 0) { + Fields.onCreate( + SocketHandlerType.from(socket), + ); + } + } + Fields.onOpen( + getValue(socket), + SocketHandlerType.from(socket), + ); + return socket; + } + pub fn on_close(socket: *us_socket_t, code: i32, reason: ?*anyopaque) callconv(.C) ?*us_socket_t { + Fields.onClose( + getValue(socket), + SocketHandlerType.from(socket), + code, + reason, + ); + return socket; + } + pub fn on_data(socket: *us_socket_t, buf: ?[*]u8, len: i32) callconv(.C) ?*us_socket_t { + Fields.onData( + getValue(socket), + SocketHandlerType.from(socket), + buf.?[0..@as(usize, @intCast(len))], + ); + return socket; + } + pub fn on_writable(socket: *us_socket_t) callconv(.C) ?*us_socket_t { + Fields.onWritable( + getValue(socket), + SocketHandlerType.from(socket), + ); + return socket; + } + pub fn on_timeout(socket: *us_socket_t) callconv(.C) ?*us_socket_t { + Fields.onTimeout( + getValue(socket), + SocketHandlerType.from(socket), + ); + return socket; + } + pub fn on_connect_error_connecting_socket(socket: *ConnectingSocket, code: i32) callconv(.C) ?*ConnectingSocket { + const val = if (comptime ContextType == anyopaque) + socket.ext(comptime is_ssl) + else if (comptime deref_) + SocketHandlerType.fromConnecting(socket).ext(ContextType).?.* + else + SocketHandlerType.fromConnecting(socket).ext(ContextType); + Fields.onConnectError( + val, + SocketHandlerType.fromConnecting(socket), + code, + ); + return socket; + } + pub fn on_connect_error(socket: *us_socket_t, code: i32) callconv(.C) ?*us_socket_t { + const val = if (comptime ContextType == anyopaque) + socket.ext(is_ssl) + else if (comptime deref_) + SocketHandlerType.from(socket).ext(ContextType).?.* + else + SocketHandlerType.from(socket).ext(ContextType); + Fields.onConnectError( + val, + SocketHandlerType.from(socket), + code, + ); + return socket; + } + pub fn on_end(socket: *us_socket_t) callconv(.C) ?*us_socket_t { + Fields.onEnd( + getValue(socket), + SocketHandlerType.from(socket), + ); + return socket; + } + pub fn on_handshake(socket: *us_socket_t, success: i32, verify_error: us_bun_verify_error_t, _: ?*anyopaque) callconv(.C) void { + Fields.onHandshake(getValue(socket), SocketHandlerType.from(socket), success, verify_error); + } + }; + + if (@typeInfo(@TypeOf(Type.onOpen)) != .null) + ctx.onOpen(is_ssl, SocketHandler.on_open); + if (@typeInfo(@TypeOf(Type.onClose)) != .null) + ctx.onClose(is_ssl, SocketHandler.on_close); + if (@typeInfo(@TypeOf(Type.onData)) != .null) + ctx.onData(is_ssl, SocketHandler.on_data); + if (@typeInfo(@TypeOf(Type.onFd)) != .null) + ctx.onFd(is_ssl, SocketHandler.on_fd); + if (@typeInfo(@TypeOf(Type.onWritable)) != .null) + ctx.onWritable(is_ssl, SocketHandler.on_writable); + if (@typeInfo(@TypeOf(Type.onTimeout)) != .null) + ctx.onTimeout(is_ssl, SocketHandler.on_timeout); + if (@typeInfo(@TypeOf(Type.onConnectError)) != .null) { + ctx.onSocketConnectError(is_ssl, SocketHandler.on_connect_error); + ctx.onConnectError(is_ssl, SocketHandler.on_connect_error_connecting_socket); + } + if (@typeInfo(@TypeOf(Type.onEnd)) != .null) + ctx.onEnd(is_ssl, SocketHandler.on_end); + if (@typeInfo(@TypeOf(Type.onHandshake)) != .null) + ctx.onHandshake(is_ssl, SocketHandler.on_handshake); + } + + pub fn configure( + ctx: *SocketContext, + comptime deref: bool, + comptime ContextType: type, + comptime Fields: anytype, + ) void { + const Type = comptime if (@TypeOf(Fields) != type) @TypeOf(Fields) else Fields; + + const SocketHandler = struct { + const alignment = if (ContextType == anyopaque) + @sizeOf(usize) + else + std.meta.alignment(ContextType); + const deref_ = deref; + const ValueType = if (deref) ContextType else *ContextType; + fn getValue(socket: *us_socket_t) ValueType { + if (comptime ContextType == anyopaque) { + return socket.ext(is_ssl); + } + + if (comptime deref_) { + return (ThisSocket.from(socket)).ext(ContextType).?.*; + } + + return (ThisSocket.from(socket)).ext(ContextType); + } + + pub fn on_open(socket: *us_socket_t, is_client: i32, _: [*c]u8, _: i32) callconv(.C) ?*us_socket_t { + if (comptime @hasDecl(Fields, "onCreate")) { + if (is_client == 0) { + Fields.onCreate( + ThisSocket.from(socket), + ); + } + } + Fields.onOpen( + getValue(socket), + ThisSocket.from(socket), + ); + return socket; + } + pub fn on_close(socket: *us_socket_t, code: i32, reason: ?*anyopaque) callconv(.C) ?*us_socket_t { + Fields.onClose( + getValue(socket), + ThisSocket.from(socket), + code, + reason, + ); + return socket; + } + pub fn on_data(socket: *us_socket_t, buf: ?[*]u8, len: i32) callconv(.C) ?*us_socket_t { + Fields.onData( + getValue(socket), + ThisSocket.from(socket), + buf.?[0..@as(usize, @intCast(len))], + ); + return socket; + } + pub fn on_fd(socket: *us_socket_t, file_descriptor: c_int) callconv(.C) ?*us_socket_t { + Fields.onFd( + getValue(socket), + ThisSocket.from(socket), + file_descriptor, + ); + return socket; + } + pub fn on_writable(socket: *us_socket_t) callconv(.C) ?*us_socket_t { + Fields.onWritable( + getValue(socket), + ThisSocket.from(socket), + ); + return socket; + } + pub fn on_timeout(socket: *us_socket_t) callconv(.C) ?*us_socket_t { + Fields.onTimeout( + getValue(socket), + ThisSocket.from(socket), + ); + return socket; + } + pub fn on_long_timeout(socket: *us_socket_t) callconv(.C) ?*us_socket_t { + Fields.onLongTimeout( + getValue(socket), + ThisSocket.from(socket), + ); + return socket; + } + pub fn on_connect_error_connecting_socket(socket: *ConnectingSocket, code: i32) callconv(.C) ?*ConnectingSocket { + const val = if (comptime ContextType == anyopaque) + socket.ext(comptime is_ssl) + else if (comptime deref_) + ThisSocket.fromConnecting(socket).ext(ContextType).?.* + else + ThisSocket.fromConnecting(socket).ext(ContextType); + Fields.onConnectError( + val, + ThisSocket.fromConnecting(socket), + code, + ); + return socket; + } + pub fn on_connect_error(socket: *us_socket_t, code: i32) callconv(.C) ?*us_socket_t { + const val = if (comptime ContextType == anyopaque) + socket.ext(is_ssl) + else if (comptime deref_) + ThisSocket.from(socket).ext(ContextType).?.* + else + ThisSocket.from(socket).ext(ContextType); + + // We close immediately in this case + // uSockets doesn't know if this is a TLS socket or not. + // So we need to close it like a TCP socket. + NewSocketHandler(false).from(socket).close(.failure); + + Fields.onConnectError( + val, + ThisSocket.from(socket), + code, + ); + return socket; + } + pub fn on_end(socket: *us_socket_t) callconv(.C) ?*us_socket_t { + Fields.onEnd( + getValue(socket), + ThisSocket.from(socket), + ); + return socket; + } + pub fn on_handshake(socket: *us_socket_t, success: i32, verify_error: us_bun_verify_error_t, _: ?*anyopaque) callconv(.C) void { + Fields.onHandshake(getValue(socket), ThisSocket.from(socket), success, verify_error); + } + }; + + if (comptime @hasDecl(Type, "onOpen") and @typeInfo(@TypeOf(Type.onOpen)) != .null) + ctx.onOpen(is_ssl, SocketHandler.on_open); + if (comptime @hasDecl(Type, "onClose") and @typeInfo(@TypeOf(Type.onClose)) != .null) + ctx.onClose(is_ssl, SocketHandler.on_close); + if (comptime @hasDecl(Type, "onData") and @typeInfo(@TypeOf(Type.onData)) != .null) + ctx.onData(is_ssl, SocketHandler.on_data); + if (comptime @hasDecl(Type, "onFd") and @typeInfo(@TypeOf(Type.onFd)) != .null) + ctx.onFd(is_ssl, SocketHandler.on_fd); + if (comptime @hasDecl(Type, "onWritable") and @typeInfo(@TypeOf(Type.onWritable)) != .null) + ctx.onWritable(is_ssl, SocketHandler.on_writable); + if (comptime @hasDecl(Type, "onTimeout") and @typeInfo(@TypeOf(Type.onTimeout)) != .null) + ctx.onTimeout(is_ssl, SocketHandler.on_timeout); + if (comptime @hasDecl(Type, "onConnectError") and @typeInfo(@TypeOf(Type.onConnectError)) != .null) { + ctx.onSocketConnectError(is_ssl, SocketHandler.on_connect_error); + ctx.onConnectError(is_ssl, SocketHandler.on_connect_error_connecting_socket); + } + if (comptime @hasDecl(Type, "onEnd") and @typeInfo(@TypeOf(Type.onEnd)) != .null) + ctx.onEnd(is_ssl, SocketHandler.on_end); + if (comptime @hasDecl(Type, "onHandshake") and @typeInfo(@TypeOf(Type.onHandshake)) != .null) + ctx.onHandshake(is_ssl, SocketHandler.on_handshake); + if (comptime @hasDecl(Type, "onLongTimeout") and @typeInfo(@TypeOf(Type.onLongTimeout)) != .null) + ctx.onLongTimeout(is_ssl, SocketHandler.on_long_timeout); + } + + pub fn from(socket: *us_socket_t) ThisSocket { + return ThisSocket{ .socket = .{ .connected = socket } }; + } + + pub fn fromConnecting(connecting: *ConnectingSocket) ThisSocket { + return ThisSocket{ .socket = .{ .connecting = connecting } }; + } + + pub fn fromAny(socket: InternalSocket) ThisSocket { + return ThisSocket{ .socket = socket }; + } + + pub fn adoptPtr( + socket: *us_socket_t, + socket_ctx: *SocketContext, + comptime Context: type, + comptime socket_field_name: []const u8, + ctx: *Context, + ) bool { + // ext_size of -1 means we want to keep the current ext size + // in particular, we don't want to allocate a new socket + const new_socket = socket_ctx.adoptSocket(comptime is_ssl, socket, -1) orelse return false; + bun.assert(new_socket == socket); + var adopted = ThisSocket.from(new_socket); + if (adopted.ext(*anyopaque)) |holder| { + holder.* = ctx; + } + @field(ctx, socket_field_name) = adopted; + return true; + } }; +} +pub const SocketTCP = NewSocketHandler(false); +pub const SocketTLS = NewSocketHandler(true); - pub fn open(this: *Socket, comptime is_ssl: bool, is_client: bool, ip_addr: ?[]const u8) void { - debug("us_socket_open({d}, is_client: {})", .{ @intFromPtr(this), is_client }); - const ssl = @intFromBool(is_ssl); +pub const InternalSocket = union(enum) { + connected: *us_socket_t, + connecting: *ConnectingSocket, + detached: void, + upgradedDuplex: *uws.UpgradedDuplex, + pipe: if (Environment.isWindows) *uws.WindowsNamedPipe else void, - if (ip_addr) |ip| { - bun.assert(ip.len < max_i32); - _ = us_socket_open(ssl, this, @intFromBool(is_client), ip.ptr, @intCast(ip.len)); - } else { - _ = us_socket_open(ssl, this, @intFromBool(is_client), null, 0); + pub fn pauseResume(this: InternalSocket, ssl: bool, pause: bool) bool { + switch (this) { + .detached => return true, + .connected => |socket| { + if (pause) socket.pause(ssl) else socket.@"resume"(ssl); + return true; + }, + .connecting => |_| { + // always return false for connecting sockets + return false; + }, + .upgradedDuplex => |_| { + // TODO: pause and resume upgraded duplex + return false; + }, + .pipe => |pipe| { + if (Environment.isWindows) { + if (pause) { + return pipe.pauseStream(); + } + return pipe.resumeStream(); + } + return false; + }, + } + } + pub fn isDetached(this: InternalSocket) bool { + return this == .detached; + } + pub fn isNamedPipe(this: InternalSocket) bool { + return this == .pipe; + } + pub fn detach(this: *InternalSocket) void { + this.* = .detached; + } + pub fn setNoDelay(this: InternalSocket, enabled: bool) bool { + switch (this) { + .pipe, .upgradedDuplex, .connecting, .detached => return false, + .connected => |socket| { + // only supported by connected sockets + socket.setNodelay(enabled); + return true; + }, + } + } + pub fn setKeepAlive(this: InternalSocket, enabled: bool, delay: u32) bool { + switch (this) { + .pipe, .upgradedDuplex, .connecting, .detached => return false, + .connected => |socket| { + // only supported by connected sockets and can fail + return socket.setKeepalive(enabled, delay) == 0; + }, + } + } + pub fn close(this: InternalSocket, comptime is_ssl: bool, code: us_socket_t.CloseCode) void { + switch (this) { + .detached => {}, + .connected => |socket| { + socket.close(is_ssl, code); + }, + .connecting => |socket| { + socket.close(is_ssl); + }, + .upgradedDuplex => |socket| { + socket.close(); + }, + .pipe => |pipe| { + if (Environment.isWindows) pipe.close(); + }, } } - pub fn pause(this: *Socket, ssl: bool) void { - debug("us_socket_pause({d})", .{@intFromPtr(this)}); - us_socket_pause(@intFromBool(ssl), this); + pub fn isClosed(this: InternalSocket, comptime is_ssl: bool) bool { + return switch (this) { + .connected => |socket| socket.isClosed(is_ssl), + .connecting => |socket| socket.isClosed(is_ssl), + .detached => true, + .upgradedDuplex => |socket| socket.isClosed(), + .pipe => |pipe| if (Environment.isWindows) pipe.isClosed() else true, + }; } - pub fn @"resume"(this: *Socket, ssl: bool) void { - debug("us_socket_resume({d})", .{@intFromPtr(this)}); - us_socket_resume(@intFromBool(ssl), this); + pub fn get(this: @This()) ?*us_socket_t { + return switch (this) { + .connected => this.connected, + .connecting => null, + .detached => null, + .upgradedDuplex => null, + .pipe => null, + }; } - pub fn close(this: *Socket, ssl: bool, code: CloseCode) void { - debug("us_socket_close({d}, {s})", .{ @intFromPtr(this), @tagName(code) }); - _ = us_socket_close(@intFromBool(ssl), this, code, null); + pub fn eq(this: @This(), other: @This()) bool { + return switch (this) { + .connected => switch (other) { + .connected => this.connected == other.connected, + .upgradedDuplex, .connecting, .detached, .pipe => false, + }, + .connecting => switch (other) { + .upgradedDuplex, .connected, .detached, .pipe => false, + .connecting => this.connecting == other.connecting, + }, + .detached => switch (other) { + .detached => true, + .upgradedDuplex, .connected, .connecting, .pipe => false, + }, + .upgradedDuplex => switch (other) { + .upgradedDuplex => this.upgradedDuplex == other.upgradedDuplex, + .connected, .connecting, .detached, .pipe => false, + }, + .pipe => switch (other) { + .pipe => if (Environment.isWindows) other.pipe == other.pipe else false, + .connected, .connecting, .detached, .upgradedDuplex => false, + }, + }; } - - pub fn shutdown(this: *Socket, ssl: bool) void { - debug("us_socket_shutdown({d})", .{@intFromPtr(this)}); - us_socket_shutdown(@intFromBool(ssl), this); - } - - pub fn shutdownRead(this: *Socket, ssl: bool) void { - us_socket_shutdown_read(@intFromBool(ssl), this); - } - - pub fn isClosed(this: *Socket, ssl: bool) bool { - return us_socket_is_closed(@intFromBool(ssl), this) > 0; - } - - pub fn isShutDown(this: *Socket, ssl: bool) bool { - return us_socket_is_shut_down(@intFromBool(ssl), this) > 0; - } - - pub fn localPort(this: *Socket, ssl: bool) i32 { - return us_socket_local_port(@intFromBool(ssl), this); - } - - pub fn remotePort(this: *Socket, ssl: bool) i32 { - return us_socket_remote_port(@intFromBool(ssl), this); - } - - /// Returned slice is a view into `buf`. - pub fn localAddress(this: *Socket, ssl: bool, buf: []u8) ![]const u8 { - var length: i32 = @intCast(buf.len); - - us_socket_local_address(@intFromBool(ssl), this, buf.ptr, &length); - if (length < 0) { - const errno = bun.sys.getErrno(length); - bun.debugAssert(errno != .SUCCESS); - return bun.errnoToZigErr(errno); - } - bun.unsafeAssert(buf.len >= length); - - return buf[0..@intCast(length)]; - } - - /// Returned slice is a view into `buf`. On error, `errno` should be set - pub fn remoteAddress(this: *Socket, ssl: bool, buf: []u8) ![]const u8 { - var length: i32 = @intCast(buf.len); - - us_socket_remote_address(@intFromBool(ssl), this, buf.ptr, &length); - if (length < 0) { - const errno = bun.sys.getErrno(length); - bun.debugAssert(errno != .SUCCESS); - return bun.errnoToZigErr(errno); - } - bun.unsafeAssert(buf.len >= length); - - return buf[0..@intCast(length)]; - } - - pub fn setTimeout(this: *Socket, ssl: bool, seconds: u32) void { - us_socket_timeout(@intFromBool(ssl), this, @intCast(seconds)); - } - - pub fn setLongTimeout(this: *Socket, ssl: bool, minutes: u32) void { - us_socket_long_timeout(@intFromBool(ssl), this, @intCast(minutes)); - } - - pub fn setNodelay(this: *Socket, enabled: bool) void { - us_socket_nodelay(this, @intFromBool(enabled)); - } - - /// Returns error code. `0` on success. error codes depend on platform an - /// configured event loop. - pub fn setKeepalive(this: *Socket, enabled: bool, delay: u32) i32 { - return us_socket_keepalive(this, @intFromBool(enabled), @intCast(delay)); - } - - pub fn getNativeHandle(this: *Socket, ssl: bool) ?*anyopaque { - return us_socket_get_native_handle(@intFromBool(ssl), this); - } - - pub fn ext(this: *Socket, ssl: bool) *anyopaque { - @setRuntimeSafety(true); - return us_socket_ext(@intFromBool(ssl), this).?; - } - - pub fn context(this: *Socket, ssl: bool) *SocketContext { - @setRuntimeSafety(true); - return us_socket_context(@intFromBool(ssl), this).?; - } - - pub fn write(this: *Socket, ssl: bool, data: []const u8, msg_more: bool) i32 { - const rc = us_socket_write(@intFromBool(ssl), this, data.ptr, @intCast(data.len), @intFromBool(msg_more)); - debug("us_socket_write({d}, {d}) = {d}", .{ @intFromPtr(this), data.len, rc }); - return rc; - } - - pub fn writeFd(this: *Socket, data: []const u8, file_descriptor: bun.FD) i32 { - if (bun.Environment.isWindows) @compileError("TODO: implement writeFd on Windows"); - const rc = us_socket_ipc_write_fd(this, data.ptr, @intCast(data.len), file_descriptor.native()); - debug("us_socket_ipc_write_fd({d}, {d}, {d}) = {d}", .{ @intFromPtr(this), data.len, file_descriptor.native(), rc }); - return rc; - } - - pub fn write2(this: *Socket, ssl: bool, first: []const u8, second: []const u8) i32 { - const rc = us_socket_write2(@intFromBool(ssl), this, first.ptr, first.len, second.ptr, second.len); - debug("us_socket_write2({d}, {d}, {d}) = {d}", .{ @intFromPtr(this), first.len, second.len, rc }); - return rc; - } - - pub fn rawWrite(this: *Socket, ssl: bool, data: []const u8, msg_more: bool) i32 { - debug("us_socket_raw_write({d}, {d})", .{ @intFromPtr(this), data.len }); - return us_socket_raw_write(@intFromBool(ssl), this, data.ptr, @intCast(data.len), @intFromBool(msg_more)); - } - - pub fn flush(this: *Socket, ssl: bool) void { - us_socket_flush(@intFromBool(ssl), this); - } - - pub fn sendFileNeedsMore(this: *Socket) void { - us_socket_sendfile_needs_more(this); - } - - pub fn getFd(this: *Socket) bun.FD { - return .fromNative(us_socket_get_fd(this)); - } - - extern fn us_socket_get_native_handle(ssl: i32, s: ?*Socket) ?*anyopaque; - - extern fn us_socket_local_port(ssl: i32, s: ?*Socket) i32; - extern fn us_socket_remote_port(ssl: i32, s: ?*Socket) i32; - extern fn us_socket_remote_address(ssl: i32, s: ?*Socket, buf: [*c]u8, length: [*c]i32) void; - extern fn us_socket_local_address(ssl: i32, s: ?*Socket, buf: [*c]u8, length: [*c]i32) void; - extern fn us_socket_timeout(ssl: i32, s: ?*Socket, seconds: c_uint) void; - extern fn us_socket_long_timeout(ssl: i32, s: ?*Socket, minutes: c_uint) void; - extern fn us_socket_nodelay(s: ?*Socket, enable: c_int) void; - extern fn us_socket_keepalive(s: ?*Socket, enable: c_int, delay: c_uint) c_int; - - extern fn us_socket_ext(ssl: i32, s: ?*Socket) ?*anyopaque; // nullish to be safe - extern fn us_socket_context(ssl: i32, s: ?*Socket) ?*SocketContext; - - extern fn us_socket_write(ssl: i32, s: ?*Socket, data: [*c]const u8, length: i32, msg_more: i32) i32; - extern fn us_socket_ipc_write_fd(s: ?*Socket, data: [*c]const u8, length: i32, fd: i32) i32; - extern "c" fn us_socket_write2(ssl: i32, *Socket, header: ?[*]const u8, len: usize, payload: ?[*]const u8, usize) i32; - extern fn us_socket_raw_write(ssl: i32, s: ?*Socket, data: [*c]const u8, length: i32, msg_more: i32) i32; - extern fn us_socket_flush(ssl: i32, s: ?*Socket) void; - - // if a TLS socket calls this, it will start SSL instance and call open event will also do TLS handshake if required - // will have no effect if the socket is closed or is not TLS - extern fn us_socket_open(ssl: i32, s: ?*Socket, is_client: i32, ip: [*c]const u8, ip_length: i32) ?*Socket; - extern fn us_socket_pause(ssl: i32, s: ?*Socket) void; - extern fn us_socket_resume(ssl: i32, s: ?*Socket) void; - extern fn us_socket_close(ssl: i32, s: ?*Socket, code: CloseCode, reason: ?*anyopaque) ?*Socket; - extern fn us_socket_shutdown(ssl: i32, s: ?*Socket) void; - extern fn us_socket_is_closed(ssl: i32, s: ?*Socket) i32; - extern fn us_socket_shutdown_read(ssl: i32, s: ?*Socket) void; - extern fn us_socket_is_shut_down(ssl: i32, s: ?*Socket) i32; - - extern fn us_socket_sendfile_needs_more(socket: *Socket) void; - extern fn us_socket_get_fd(s: ?*Socket) LIBUS_SOCKET_DESCRIPTOR; - const LIBUS_SOCKET_DESCRIPTOR = switch (bun.Environment.isWindows) { - true => *anyopaque, - false => i32, - }; }; + +/// TODO: rename to ConnectedSocket +pub const AnySocket = union(enum) { + SocketTCP: SocketTCP, + SocketTLS: SocketTLS, + + pub fn setTimeout(this: AnySocket, seconds: c_uint) void { + switch (this) { + .SocketTCP => this.SocketTCP.setTimeout(seconds), + .SocketTLS => this.SocketTLS.setTimeout(seconds), + } + } + + pub fn shutdown(this: AnySocket) void { + switch (this) { + .SocketTCP => |sock| sock.shutdown(), + .SocketTLS => |sock| sock.shutdown(), + } + } + + pub fn shutdownRead(this: AnySocket) void { + switch (this) { + .SocketTCP => |sock| sock.shutdownRead(), + .SocketTLS => |sock| sock.shutdownRead(), + } + } + + pub fn isShutdown(this: AnySocket) bool { + return switch (this) { + .SocketTCP => this.SocketTCP.isShutdown(), + .SocketTLS => this.SocketTLS.isShutdown(), + }; + } + pub fn isClosed(this: AnySocket) bool { + return switch (this) { + inline else => |s| s.isClosed(), + }; + } + pub fn close(this: AnySocket) void { + switch (this) { + inline else => |s| s.close(.normal), + } + } + + pub fn terminate(this: AnySocket) void { + switch (this) { + inline else => |s| s.close(.failure), + } + } + + pub fn write(this: AnySocket, data: []const u8, msg_more: bool) i32 { + return switch (this) { + .SocketTCP => |sock| sock.write(data, msg_more), + .SocketTLS => |sock| sock.write(data, msg_more), + }; + } + + pub fn getNativeHandle(this: AnySocket) ?*anyopaque { + return switch (this.socket()) { + .connected => |sock| sock.getNativeHandle(this.isSSL()), + else => null, + }; + } + + pub fn localPort(this: AnySocket) i32 { + switch (this) { + .SocketTCP => |sock| sock.localPort(), + .SocketTLS => |sock| sock.localPort(), + } + } + + pub fn isSSL(this: AnySocket) bool { + return switch (this) { + .SocketTCP => false, + .SocketTLS => true, + }; + } + + pub fn socket(this: AnySocket) InternalSocket { + return switch (this) { + .SocketTCP => this.SocketTCP.socket, + .SocketTLS => this.SocketTLS.socket, + }; + } + + pub fn ext(this: AnySocket, comptime ContextType: type) ?*ContextType { + const ptr = this.socket().ext(this.isSSL()) orelse return null; + + return @ptrCast(@alignCast(ptr)); + } + + pub fn context(this: AnySocket) *SocketContext { + @setRuntimeSafety(true); + return switch (this) { + .SocketTCP => |sock| sock.context(), + .SocketTLS => |sock| sock.context(), + }.?; + } +}; + +fn NativeSocketHandleType(comptime ssl: bool) type { + if (ssl) { + return BoringSSL.SSL; + } else { + return anyopaque; + } +} + +const us_socket_t = uws.us_socket_t; + +const c = struct { + pub const us_socket_events_t = extern struct { + on_open: ?*const fn (*us_socket_t, i32, [*c]u8, i32) callconv(.C) ?*us_socket_t = null, + on_data: ?*const fn (*us_socket_t, [*c]u8, i32) callconv(.C) ?*us_socket_t = null, + on_writable: ?*const fn (*us_socket_t) callconv(.C) ?*us_socket_t = null, + on_close: ?*const fn (*us_socket_t, i32, ?*anyopaque) callconv(.C) ?*us_socket_t = null, + + on_timeout: ?*const fn (*us_socket_t) callconv(.C) ?*us_socket_t = null, + on_long_timeout: ?*const fn (*us_socket_t) callconv(.C) ?*us_socket_t = null, + on_end: ?*const fn (*us_socket_t) callconv(.C) ?*us_socket_t = null, + on_connect_error: ?*const fn (*us_socket_t, i32) callconv(.C) ?*us_socket_t = null, + on_connect_error_connecting_socket: ?*const fn (*ConnectingSocket, i32) callconv(.C) ?*ConnectingSocket = null, + on_handshake: ?*const fn (*us_socket_t, i32, uws.us_bun_verify_error_t, ?*anyopaque) callconv(.C) void = null, + }; + pub extern fn us_socket_wrap_with_tls(ssl: i32, s: *uws.us_socket_t, options: uws.SocketContext.BunSocketContextOptions, events: c.us_socket_events_t, socket_ext_size: i32) ?*uws.us_socket_t; +}; + +const bun = @import("bun"); +const uws = bun.uws; +const ConnectingSocket = uws.ConnectingSocket; +const Environment = bun.Environment; +const SocketContext = uws.SocketContext; +const debug = bun.Output.scoped(.uws, false); +const std = @import("std"); +const us_bun_verify_error_t = uws.us_bun_verify_error_t; +const BunSocketContextOptions = uws.SocketContext.BunSocketContextOptions; +const WindowsNamedPipe = uws.WindowsNamedPipe; +const UpgradedDuplex = uws.UpgradedDuplex; +const BoringSSL = bun.BoringSSL.c; diff --git a/src/deps/uws/udp.zig b/src/deps/uws/udp.zig new file mode 100644 index 0000000000..c89eac5aa6 --- /dev/null +++ b/src/deps/uws/udp.zig @@ -0,0 +1,111 @@ +pub const Socket = opaque { + pub fn create(loop: *Loop, data_cb: *const fn (*udp.Socket, *PacketBuffer, c_int) callconv(.C) void, drain_cb: *const fn (*udp.Socket) callconv(.C) void, close_cb: *const fn (*udp.Socket) callconv(.C) void, host: [*c]const u8, port: c_ushort, options: c_int, err: ?*c_int, user_data: ?*anyopaque) ?*udp.Socket { + return us_create_udp_socket(loop, data_cb, drain_cb, close_cb, host, port, options, err, user_data); + } + + pub fn send(this: *udp.Socket, payloads: []const [*]const u8, lengths: []const usize, addresses: []const ?*const anyopaque) c_int { + bun.assert(payloads.len == lengths.len and payloads.len == addresses.len); + return us_udp_socket_send(this, payloads.ptr, lengths.ptr, addresses.ptr, @intCast(payloads.len)); + } + + pub fn user(this: *udp.Socket) ?*anyopaque { + return us_udp_socket_user(this); + } + + pub fn bind(this: *udp.Socket, hostname: [*c]const u8, port: c_uint) c_int { + return us_udp_socket_bind(this, hostname, port); + } + + /// Get the bound port in host byte order + pub fn boundPort(this: *udp.Socket) c_int { + return us_udp_socket_bound_port(this); + } + + pub fn boundIp(this: *udp.Socket, buf: [*c]u8, length: *i32) void { + return us_udp_socket_bound_ip(this, buf, length); + } + + pub fn remoteIp(this: *udp.Socket, buf: [*c]u8, length: *i32) void { + return us_udp_socket_remote_ip(this, buf, length); + } + + pub fn close(this: *udp.Socket) void { + return us_udp_socket_close(this); + } + + pub fn connect(this: *udp.Socket, hostname: [*c]const u8, port: c_uint) c_int { + return us_udp_socket_connect(this, hostname, port); + } + + pub fn disconnect(this: *udp.Socket) c_int { + return us_udp_socket_disconnect(this); + } + + pub fn setBroadcast(this: *udp.Socket, enabled: bool) c_int { + return us_udp_socket_set_broadcast(this, @intCast(@intFromBool(enabled))); + } + + pub fn setUnicastTTL(this: *udp.Socket, ttl: i32) c_int { + return us_udp_socket_set_ttl_unicast(this, @intCast(ttl)); + } + + pub fn setMulticastTTL(this: *udp.Socket, ttl: i32) c_int { + return us_udp_socket_set_ttl_multicast(this, @intCast(ttl)); + } + + pub fn setMulticastLoopback(this: *udp.Socket, enabled: bool) c_int { + return us_udp_socket_set_multicast_loopback(this, @intCast(@intFromBool(enabled))); + } + + pub fn setMulticastInterface(this: *udp.Socket, iface: *const std.posix.sockaddr.storage) c_int { + return us_udp_socket_set_multicast_interface(this, iface); + } + + pub fn setMembership(this: *udp.Socket, address: *const std.posix.sockaddr.storage, iface: ?*const std.posix.sockaddr.storage, drop: bool) c_int { + return us_udp_socket_set_membership(this, address, iface, @intFromBool(drop)); + } + + pub fn setSourceSpecificMembership(this: *udp.Socket, source: *const std.posix.sockaddr.storage, group: *const std.posix.sockaddr.storage, iface: ?*const std.posix.sockaddr.storage, drop: bool) c_int { + return us_udp_socket_set_source_specific_membership(this, source, group, iface, @intFromBool(drop)); + } + + extern fn us_create_udp_socket(loop: ?*Loop, data_cb: *const fn (*udp.Socket, *PacketBuffer, c_int) callconv(.C) void, drain_cb: *const fn (*udp.Socket) callconv(.C) void, close_cb: *const fn (*udp.Socket) callconv(.C) void, host: [*c]const u8, port: c_ushort, options: c_int, err: ?*c_int, user_data: ?*anyopaque) ?*udp.Socket; + extern fn us_udp_socket_connect(socket: *udp.Socket, hostname: [*c]const u8, port: c_uint) c_int; + extern fn us_udp_socket_disconnect(socket: *udp.Socket) c_int; + extern fn us_udp_socket_send(socket: *udp.Socket, [*c]const [*c]const u8, [*c]const usize, [*c]const ?*const anyopaque, c_int) c_int; + extern fn us_udp_socket_user(socket: *udp.Socket) ?*anyopaque; + extern fn us_udp_socket_bind(socket: *udp.Socket, hostname: [*c]const u8, port: c_uint) c_int; + extern fn us_udp_socket_bound_port(socket: *udp.Socket) c_int; + extern fn us_udp_socket_bound_ip(socket: *udp.Socket, buf: [*c]u8, length: [*c]i32) void; + extern fn us_udp_socket_remote_ip(socket: *udp.Socket, buf: [*c]u8, length: [*c]i32) void; + extern fn us_udp_socket_close(socket: *udp.Socket) void; + extern fn us_udp_socket_set_broadcast(socket: *udp.Socket, enabled: c_int) c_int; + extern fn us_udp_socket_set_ttl_unicast(socket: *udp.Socket, ttl: c_int) c_int; + extern fn us_udp_socket_set_ttl_multicast(socket: *udp.Socket, ttl: c_int) c_int; + extern fn us_udp_socket_set_multicast_loopback(socket: *udp.Socket, enabled: c_int) c_int; + extern fn us_udp_socket_set_multicast_interface(socket: *udp.Socket, iface: *const std.posix.sockaddr.storage) c_int; + extern fn us_udp_socket_set_membership(socket: *udp.Socket, address: *const std.posix.sockaddr.storage, iface: ?*const std.posix.sockaddr.storage, drop: c_int) c_int; + extern fn us_udp_socket_set_source_specific_membership(socket: *udp.Socket, source: *const std.posix.sockaddr.storage, group: *const std.posix.sockaddr.storage, iface: ?*const std.posix.sockaddr.storage, drop: c_int) c_int; +}; + +pub const PacketBuffer = opaque { + pub fn getPeer(this: *PacketBuffer, index: c_int) *std.posix.sockaddr.storage { + return us_udp_packet_buffer_peer(this, index); + } + + pub fn getPayload(this: *PacketBuffer, index: c_int) []u8 { + const payload = us_udp_packet_buffer_payload(this, index); + const len = us_udp_packet_buffer_payload_length(this, index); + return payload[0..@as(usize, @intCast(len))]; + } + + extern fn us_udp_packet_buffer_peer(buf: ?*PacketBuffer, index: c_int) *std.posix.sockaddr.storage; + extern fn us_udp_packet_buffer_payload(buf: ?*PacketBuffer, index: c_int) [*]u8; + extern fn us_udp_packet_buffer_payload_length(buf: ?*PacketBuffer, index: c_int) c_int; +}; + +const udp = @This(); +const Loop = uws.Loop; +const bun = @import("bun"); +const uws = bun.uws; +const std = @import("std"); diff --git a/src/deps/uws/us_socket_t.zig b/src/deps/uws/us_socket_t.zig new file mode 100644 index 0000000000..2c8d3fa328 --- /dev/null +++ b/src/deps/uws/us_socket_t.zig @@ -0,0 +1,235 @@ +const std = @import("std"); +const bun = @import("bun"); +const uws = @import("../uws.zig"); + +const SocketContext = uws.SocketContext; + +const debug = bun.Output.scoped(.uws, false); +const max_i32 = std.math.maxInt(i32); + +/// Zig bindings for `us_socket_t` +/// +/// This is lower-level, you generally want to use uws.SocketTCP or +/// uws.SocketTLS instead so that you can support named pipes, upgraded duplexes, +/// asynchronous DNS, etc. +pub const us_socket_t = opaque { + pub const CloseCode = enum(i32) { + normal = 0, + failure = 1, + }; + + pub fn open(this: *us_socket_t, comptime is_ssl: bool, is_client: bool, ip_addr: ?[]const u8) void { + debug("us_socket_open({d}, is_client: {})", .{ @intFromPtr(this), is_client }); + const ssl = @intFromBool(is_ssl); + + if (ip_addr) |ip| { + bun.assert(ip.len < max_i32); + _ = c.us_socket_open(ssl, this, @intFromBool(is_client), ip.ptr, @intCast(ip.len)); + } else { + _ = c.us_socket_open(ssl, this, @intFromBool(is_client), null, 0); + } + } + + pub fn pause(this: *us_socket_t, ssl: bool) void { + debug("us_socket_pause({d})", .{@intFromPtr(this)}); + c.us_socket_pause(@intFromBool(ssl), this); + } + + pub fn @"resume"(this: *us_socket_t, ssl: bool) void { + debug("us_socket_resume({d})", .{@intFromPtr(this)}); + c.us_socket_resume(@intFromBool(ssl), this); + } + + pub fn close(this: *us_socket_t, ssl: bool, code: CloseCode) void { + debug("us_socket_close({d}, {s})", .{ @intFromPtr(this), @tagName(code) }); + _ = c.us_socket_close(@intFromBool(ssl), this, code, null); + } + + pub fn shutdown(this: *us_socket_t, ssl: bool) void { + debug("us_socket_shutdown({d})", .{@intFromPtr(this)}); + c.us_socket_shutdown(@intFromBool(ssl), this); + } + + pub fn shutdownRead(this: *us_socket_t, ssl: bool) void { + c.us_socket_shutdown_read(@intFromBool(ssl), this); + } + + pub fn isClosed(this: *us_socket_t, ssl: bool) bool { + return c.us_socket_is_closed(@intFromBool(ssl), this) > 0; + } + + pub fn isShutdown(this: *us_socket_t, ssl: bool) bool { + return c.us_socket_is_shut_down(@intFromBool(ssl), this) > 0; + } + + pub fn localPort(this: *us_socket_t, ssl: bool) i32 { + return c.us_socket_local_port(@intFromBool(ssl), this); + } + + pub fn remotePort(this: *us_socket_t, ssl: bool) i32 { + return c.us_socket_remote_port(@intFromBool(ssl), this); + } + + /// Returned slice is a view into `buf`. + pub fn localAddress(this: *us_socket_t, ssl: bool, buf: []u8) ![]const u8 { + var length: i32 = @intCast(buf.len); + + c.us_socket_local_address(@intFromBool(ssl), this, buf.ptr, &length); + if (length < 0) { + const errno = bun.sys.getErrno(length); + bun.debugAssert(errno != .SUCCESS); + return bun.errnoToZigErr(errno); + } + bun.unsafeAssert(buf.len >= length); + + return buf[0..@intCast(length)]; + } + + /// Returned slice is a view into `buf`. On error, `errno` should be set + pub fn remoteAddress(this: *us_socket_t, ssl: bool, buf: []u8) ![]const u8 { + var length: i32 = @intCast(buf.len); + + c.us_socket_remote_address(@intFromBool(ssl), this, buf.ptr, &length); + if (length < 0) { + const errno = bun.sys.getErrno(length); + bun.debugAssert(errno != .SUCCESS); + return bun.errnoToZigErr(errno); + } + bun.unsafeAssert(buf.len >= length); + + return buf[0..@intCast(length)]; + } + + pub fn setTimeout(this: *us_socket_t, ssl: bool, seconds: u32) void { + c.us_socket_timeout(@intFromBool(ssl), this, @intCast(seconds)); + } + + pub fn setLongTimeout(this: *us_socket_t, ssl: bool, minutes: u32) void { + c.us_socket_long_timeout(@intFromBool(ssl), this, @intCast(minutes)); + } + + pub fn setNodelay(this: *us_socket_t, enabled: bool) void { + c.us_socket_nodelay(this, @intFromBool(enabled)); + } + + /// Returns error code. `0` on success. error codes depend on platform an + /// configured event loop. + pub fn setKeepalive(this: *us_socket_t, enabled: bool, delay: u32) i32 { + return c.us_socket_keepalive(this, @intFromBool(enabled), @intCast(delay)); + } + + pub fn getNativeHandle(this: *us_socket_t, ssl: bool) ?*anyopaque { + return c.us_socket_get_native_handle(@intFromBool(ssl), this); + } + + pub fn ext(this: *us_socket_t, ssl: bool) *anyopaque { + @setRuntimeSafety(true); + return c.us_socket_ext(@intFromBool(ssl), this).?; + } + + pub fn context(this: *us_socket_t, ssl: bool) *SocketContext { + @setRuntimeSafety(true); + return c.us_socket_context(@intFromBool(ssl), this).?; + } + + pub fn write(this: *us_socket_t, ssl: bool, data: []const u8, msg_more: bool) i32 { + const rc = c.us_socket_write(@intFromBool(ssl), this, data.ptr, @intCast(data.len), @intFromBool(msg_more)); + debug("us_socket_write({d}, {d}) = {d}", .{ @intFromPtr(this), data.len, rc }); + return rc; + } + + pub fn writeFd(this: *us_socket_t, data: []const u8, file_descriptor: bun.FD) i32 { + if (bun.Environment.isWindows) @compileError("TODO: implement writeFd on Windows"); + const rc = c.us_socket_ipc_write_fd(this, data.ptr, @intCast(data.len), file_descriptor.native()); + debug("us_socket_ipc_write_fd({d}, {d}, {d}) = {d}", .{ @intFromPtr(this), data.len, file_descriptor.native(), rc }); + return rc; + } + + pub fn write2(this: *us_socket_t, ssl: bool, first: []const u8, second: []const u8) i32 { + const rc = c.us_socket_write2(@intFromBool(ssl), this, first.ptr, first.len, second.ptr, second.len); + debug("us_socket_write2({d}, {d}, {d}) = {d}", .{ @intFromPtr(this), first.len, second.len, rc }); + return rc; + } + + pub fn rawWrite(this: *us_socket_t, ssl: bool, data: []const u8, msg_more: bool) i32 { + debug("us_socket_raw_write({d}, {d})", .{ @intFromPtr(this), data.len }); + return c.us_socket_raw_write(@intFromBool(ssl), this, data.ptr, @intCast(data.len), @intFromBool(msg_more)); + } + + pub fn flush(this: *us_socket_t, ssl: bool) void { + c.us_socket_flush(@intFromBool(ssl), this); + } + + pub fn sendFileNeedsMore(this: *us_socket_t) void { + c.us_socket_sendfile_needs_more(this); + } + + pub fn getFd(this: *us_socket_t) bun.FD { + return .fromNative(c.us_socket_get_fd(this)); + } + + pub fn getVerifyError(this: *us_socket_t, ssl: bool) uws.us_bun_verify_error_t { + return c.us_socket_verify_error(@intFromBool(ssl), this); + } + + pub fn upgrade(this: *us_socket_t, new_context: *SocketContext, sni: ?[*:0]const u8) ?*us_socket_t { + return c.us_socket_upgrade_to_tls(this, new_context, sni); + } + + pub fn fromFd(ctx: *SocketContext, ext_size: c_int, fd: uws.LIBUS_SOCKET_DESCRIPTOR, is_ipc: c_int) ?*us_socket_t { + return c.us_socket_from_fd(ctx, ext_size, fd, is_ipc); + } + + pub fn getError(this: *us_socket_t, ssl: bool) i32 { + return c.us_socket_get_error(@intFromBool(ssl), this); + } + + pub fn isEstablished(this: *us_socket_t, ssl: bool) bool { + return c.us_socket_is_established(@intFromBool(ssl), this) > 0; + } +}; + +pub const c = struct { + pub extern fn us_socket_get_native_handle(ssl: i32, s: ?*us_socket_t) ?*anyopaque; + + pub extern fn us_socket_local_port(ssl: i32, s: ?*us_socket_t) i32; + pub extern fn us_socket_remote_port(ssl: i32, s: ?*us_socket_t) i32; + pub extern fn us_socket_remote_address(ssl: i32, s: ?*us_socket_t, buf: [*c]u8, length: [*c]i32) void; + pub extern fn us_socket_local_address(ssl: i32, s: ?*us_socket_t, buf: [*c]u8, length: [*c]i32) void; + pub extern fn us_socket_timeout(ssl: i32, s: ?*us_socket_t, seconds: c_uint) void; + pub extern fn us_socket_long_timeout(ssl: i32, s: ?*us_socket_t, minutes: c_uint) void; + pub extern fn us_socket_nodelay(s: ?*us_socket_t, enable: c_int) void; + pub extern fn us_socket_keepalive(s: ?*us_socket_t, enable: c_int, delay: c_uint) c_int; + + pub extern fn us_socket_ext(ssl: i32, s: ?*us_socket_t) ?*anyopaque; // nullish to be safe + pub extern fn us_socket_context(ssl: i32, s: ?*us_socket_t) ?*SocketContext; + + pub extern fn us_socket_write(ssl: i32, s: ?*us_socket_t, data: [*c]const u8, length: i32, msg_more: i32) i32; + pub extern fn us_socket_ipc_write_fd(s: ?*us_socket_t, data: [*c]const u8, length: i32, fd: i32) i32; + pub extern fn us_socket_write2(ssl: i32, *us_socket_t, header: ?[*]const u8, len: usize, payload: ?[*]const u8, usize) i32; + pub extern fn us_socket_raw_write(ssl: i32, s: ?*us_socket_t, data: [*c]const u8, length: i32, msg_more: i32) i32; + pub extern fn us_socket_flush(ssl: i32, s: ?*us_socket_t) void; + + // if a TLS socket calls this, it will start SSL instance and call open event will also do TLS handshake if required + // will have no effect if the socket is closed or is not TLS + pub extern fn us_socket_open(ssl: i32, s: ?*us_socket_t, is_client: i32, ip: [*c]const u8, ip_length: i32) ?*us_socket_t; + pub extern fn us_socket_pause(ssl: i32, s: ?*us_socket_t) void; + pub extern fn us_socket_resume(ssl: i32, s: ?*us_socket_t) void; + pub extern fn us_socket_close(ssl: i32, s: ?*us_socket_t, code: us_socket_t.CloseCode, reason: ?*anyopaque) ?*us_socket_t; + pub extern fn us_socket_shutdown(ssl: i32, s: ?*us_socket_t) void; + pub extern fn us_socket_is_closed(ssl: i32, s: ?*us_socket_t) i32; + pub extern fn us_socket_shutdown_read(ssl: i32, s: ?*us_socket_t) void; + pub extern fn us_socket_is_shut_down(ssl: i32, s: ?*us_socket_t) i32; + pub extern fn us_socket_sendfile_needs_more(socket: *us_socket_t) void; + pub extern fn us_socket_get_fd(s: ?*us_socket_t) uws.LIBUS_SOCKET_DESCRIPTOR; + pub extern fn us_socket_verify_error(ssl: i32, context: *us_socket_t) uws.us_bun_verify_error_t; + pub extern fn us_socket_upgrade_to_tls(s: *us_socket_t, new_context: *SocketContext, sni: ?[*:0]const u8) ?*us_socket_t; + pub extern fn us_socket_from_fd( + ctx: *SocketContext, + ext_size: c_int, + fd: uws.LIBUS_SOCKET_DESCRIPTOR, + is_ipc: c_int, + ) ?*us_socket_t; + pub extern fn us_socket_get_error(ssl: i32, s: *uws.us_socket_t) c_int; + pub extern fn us_socket_is_established(ssl: i32, s: *uws.us_socket_t) i32; +}; diff --git a/src/http.zig b/src/http.zig index 540e4c7e2e..2086eb370c 100644 --- a/src/http.zig +++ b/src/http.zig @@ -641,7 +641,6 @@ fn NewHTTPContext(comptime ssl: bool) type { pub fn deinit(this: *@This()) void { this.us_socket_context.deinit(ssl); - uws.us_socket_context_free(@as(c_int, @intFromBool(ssl)), this.us_socket_context); bun.default_allocator.destroy(this); } @@ -655,13 +654,13 @@ fn NewHTTPContext(comptime ssl: bool) type { try this.initWithOpts(&opts); } - fn initWithOpts(this: *@This(), opts: *const uws.us_bun_socket_context_options_t) InitError!void { + fn initWithOpts(this: *@This(), opts: *const uws.SocketContext.BunSocketContextOptions) InitError!void { if (!comptime ssl) { @compileError("ssl only"); } var err: uws.create_bun_socket_error_t = .none; - const socket = uws.us_create_bun_ssl_socket_context(http_thread.loop.loop, @sizeOf(usize), opts.*, &err); + const socket = uws.SocketContext.createSSLContext(http_thread.loop.loop, @sizeOf(usize), opts.*, &err); if (socket == null) { return switch (err) { .load_ca_file => error.LoadCAFile, @@ -685,7 +684,7 @@ fn NewHTTPContext(comptime ssl: bool) type { if (!comptime ssl) { @compileError("ssl only"); } - var opts: uws.us_bun_socket_context_options_t = .{ + var opts: uws.SocketContext.BunSocketContextOptions = .{ .ca = if (init_opts.ca.len > 0) @ptrCast(init_opts.ca) else null, .ca_count = @intCast(init_opts.ca.len), .ca_file_name = if (init_opts.abs_ca_file_name.len > 0) init_opts.abs_ca_file_name else null, @@ -697,19 +696,18 @@ fn NewHTTPContext(comptime ssl: bool) type { pub fn init(this: *@This()) void { if (comptime ssl) { - const opts: uws.us_bun_socket_context_options_t = .{ + const opts: uws.SocketContext.BunSocketContextOptions = .{ // we request the cert so we load root certs and can verify it .request_cert = 1, // we manually abort the connection if the hostname doesn't match .reject_unauthorized = 0, }; var err: uws.create_bun_socket_error_t = .none; - this.us_socket_context = uws.us_create_bun_ssl_socket_context(http_thread.loop.loop, @sizeOf(usize), opts, &err).?; + this.us_socket_context = uws.SocketContext.createSSLContext(http_thread.loop.loop, @sizeOf(usize), opts, &err).?; this.sslCtx().setup(); } else { - const opts: uws.us_socket_context_options_t = .{}; - this.us_socket_context = uws.us_create_socket_context(ssl_int, http_thread.loop.loop, @sizeOf(usize), opts).?; + this.us_socket_context = uws.SocketContext.createNoSSLContext(http_thread.loop.loop, @sizeOf(usize)).?; } HTTPSocket.configure( diff --git a/src/http/websocket_client.zig b/src/http/websocket_client.zig index fdc6f541c7..87c99ee22b 100644 --- a/src/http/websocket_client.zig +++ b/src/http/websocket_client.zig @@ -929,7 +929,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { buffered_data: [*]u8, buffered_data_len: usize, ) callconv(.C) ?*anyopaque { - const tcp = @as(*uws.Socket, @ptrCast(input_socket)); + const tcp = @as(*uws.us_socket_t, @ptrCast(input_socket)); const ctx = @as(*uws.SocketContext, @ptrCast(socket_ctx)); var ws = bun.new(WebSocket, .{ .ref_count = .init(), diff --git a/src/sql/postgres.zig b/src/sql/postgres.zig index bb5c0ddb11..dbbd07a74c 100644 --- a/src/sql/postgres.zig +++ b/src/sql/postgres.zig @@ -1431,7 +1431,7 @@ pub const PostgresSQLConnection = struct { pub fn setupTLS(this: *PostgresSQLConnection) void { debug("setupTLS", .{}); - const new_socket = uws.us_socket_upgrade_to_tls(this.socket.SocketTCP.socket.connected, this.tls_ctx.?, this.tls_config.server_name) orelse { + const new_socket = this.socket.SocketTCP.socket.connected.upgrade(this.tls_ctx.?, this.tls_config.server_name) orelse { this.fail("Failed to upgrade to TLS", error.TLSUpgradeFailed); return; }; @@ -1843,7 +1843,7 @@ pub const PostgresSQLConnection = struct { // We create it right here so we can throw errors early. const context_options = tls_config.asUSockets(); var err: uws.create_bun_socket_error_t = .none; - tls_ctx = uws.us_create_bun_ssl_socket_context(vm.uwsLoop(), @sizeOf(*PostgresSQLConnection), context_options, &err) orelse { + tls_ctx = uws.SocketContext.createSSLContext(vm.uwsLoop(), @sizeOf(*PostgresSQLConnection), context_options, &err) orelse { if (err != .none) { return globalObject.throw("failed to create TLS context", .{}); } else { @@ -1951,7 +1951,7 @@ pub const PostgresSQLConnection = struct { defer hostname.deinit(); const ctx = vm.rareData().postgresql_context.tcp orelse brk: { - const ctx_ = uws.us_create_bun_nossl_socket_context(vm.uwsLoop(), @sizeOf(*PostgresSQLConnection)).?; + const ctx_ = uws.SocketContext.createNoSSLContext(vm.uwsLoop(), @sizeOf(*PostgresSQLConnection)).?; uws.NewSocketHandler(false).configure(ctx_, true, *PostgresSQLConnection, SocketHandler(false)); vm.rareData().postgresql_context.tcp = ctx_; break :brk ctx_; diff --git a/src/valkey/js_valkey.zig b/src/valkey/js_valkey.zig index c05f02c0ee..6f0a70e0cf 100644 --- a/src/valkey/js_valkey.zig +++ b/src/valkey/js_valkey.zig @@ -532,7 +532,7 @@ pub const JSValkeyClient = struct { .none => .{ vm.rareData().valkey_context.tcp orelse brk_ctx: { // TCP socket - const ctx_ = uws.us_create_bun_nossl_socket_context(vm.uwsLoop(), @sizeOf(*JSValkeyClient)).?; + const ctx_ = uws.SocketContext.createNoSSLContext(vm.uwsLoop(), @sizeOf(*JSValkeyClient)).?; uws.NewSocketHandler(false).configure(ctx_, true, *JSValkeyClient, SocketHandler(false)); vm.rareData().valkey_context.tcp = ctx_; break :brk_ctx ctx_; @@ -543,7 +543,7 @@ pub const JSValkeyClient = struct { vm.rareData().valkey_context.tls orelse brk_ctx: { // TLS socket, default config var err: uws.create_bun_socket_error_t = .none; - const ctx_ = uws.us_create_bun_ssl_socket_context(vm.uwsLoop(), @sizeOf(*JSValkeyClient), uws.us_bun_socket_context_options_t{}, &err).?; + const ctx_ = uws.SocketContext.createSSLContext(vm.uwsLoop(), @sizeOf(*JSValkeyClient), uws.SocketContext.BunSocketContextOptions{}, &err).?; uws.NewSocketHandler(true).configure(ctx_, true, *JSValkeyClient, SocketHandler(true)); vm.rareData().valkey_context.tls = ctx_; break :brk_ctx ctx_; @@ -554,7 +554,7 @@ pub const JSValkeyClient = struct { // TLS socket, custom config var err: uws.create_bun_socket_error_t = .none; const options = custom.asUSockets(); - const ctx_ = uws.us_create_bun_ssl_socket_context(vm.uwsLoop(), @sizeOf(*JSValkeyClient), options, &err).?; + const ctx_ = uws.SocketContext.createSSLContext(vm.uwsLoop(), @sizeOf(*JSValkeyClient), options, &err).?; uws.NewSocketHandler(true).configure(ctx_, true, *JSValkeyClient, SocketHandler(true)); break :brk_ctx .{ ctx_, true }; },