From b287022e4d78789e50874c94e13511e3f4d2f451 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 10:53:56 -0700 Subject: [PATCH 01/33] port node internal bindings to rust --- ext/node/lib.rs | 9 +- ext/node/ops/internal_binding.rs | 259 ++++++++++++++++++ ext/node/ops/mod.rs | 1 + ext/node/ops/pipe_wrap.rs | 101 ++++++- ext/node/ops/tcp_wrap.rs | 63 ++++- .../internal/cluster/round_robin_handle.ts | 15 - .../internal_binding/_libuv_winerror.ts | 10 +- .../polyfills/internal_binding/_listen.ts | 27 -- ext/node/polyfills/internal_binding/_node.ts | 13 +- ext/node/polyfills/internal_binding/ares.ts | 79 +----- .../polyfills/internal_binding/handle_wrap.ts | 8 +- .../polyfills/internal_binding/inspector.js | 12 +- .../polyfills/internal_binding/pipe_wrap.ts | 86 +----- .../polyfills/internal_binding/tcp_wrap.ts | 50 +--- .../polyfills/internal_binding/tty_wrap.ts | 11 +- ext/node/polyfills/internal_binding/types.ts | 85 +----- ext/node/polyfills/net.ts | 11 - 17 files changed, 435 insertions(+), 405 deletions(-) create mode 100644 ext/node/ops/internal_binding.rs delete mode 100644 ext/node/polyfills/internal_binding/_listen.ts diff --git a/ext/node/lib.rs b/ext/node/lib.rs index b9ada4b1689f31..b63fee523599cc 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -320,6 +320,14 @@ deno_core::extension!(deno_node, ops::idna::op_node_idna_punycode_to_unicode, ops::idna::op_node_idna_punycode_decode, ops::idna::op_node_idna_punycode_encode, + ops::internal_binding::op_node_internal_binding_ares, + ops::internal_binding::op_node_internal_binding_encodings, + ops::internal_binding::op_node_internal_binding_handle_wrap, + ops::internal_binding::op_node_internal_binding_inspector, + ops::internal_binding::op_node_internal_binding_libuv_winerror, + ops::internal_binding::op_node_internal_binding_tty_wrap, + ops::internal_binding::op_node_internal_binding_types, + ops::internal_binding::op_node_ares_strerror, ops::zlib::op_zlib_crc32, ops::zlib::op_zlib_crc32_string, ops::handle_wrap::op_node_new_async_id, @@ -614,7 +622,6 @@ deno_core::extension!(deno_node, "_process/process.ts", "_util/_util_callbackify.js", "_zlib_binding.mjs", - "internal_binding/_listen.ts", "internal_binding/_node.ts", "internal_binding/_utils.ts", "internal_binding/ares.ts", diff --git a/ext/node/ops/internal_binding.rs b/ext/node/ops/internal_binding.rs new file mode 100644 index 00000000000000..06c6078ef46f74 --- /dev/null +++ b/ext/node/ops/internal_binding.rs @@ -0,0 +1,259 @@ +// Copyright 2018-2026 the Deno authors. MIT license. + +use deno_core::op2; +use deno_core::v8; + +fn set_i32( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: i32, +) { + let key = v8::String::new(scope, name).unwrap(); + let value = v8::Integer::new(scope, value); + obj.set(scope, key.into(), value.into()); +} + +fn set_str( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: &str, +) { + let key = v8::String::new(scope, name).unwrap(); + let value = v8::String::new(scope, value).unwrap(); + obj.set(scope, key.into(), value.into()); +} + +fn set_bool( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: bool, +) { + let key = v8::String::new(scope, name).unwrap(); + let value = v8::Boolean::new(scope, value); + obj.set(scope, key.into(), value.into()); +} + +fn core_object<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let context = scope.get_current_context(); + let global = context.global(scope); + let deno_key = v8::String::new(scope, "Deno").unwrap(); + let core_key = v8::String::new(scope, "core").unwrap(); + let deno = global.get(scope, deno_key.into()).unwrap(); + let deno = v8::Local::::try_from(deno).unwrap(); + let core = deno.get(scope, core_key.into()).unwrap(); + v8::Local::::try_from(core).unwrap() +} + +fn core_ops<'s>(scope: &mut v8::PinScope<'s, '_>) -> v8::Local<'s, v8::Object> { + let core = core_object(scope); + let ops_key = v8::String::new(scope, "ops").unwrap(); + let ops = core.get(scope, ops_key.into()).unwrap(); + v8::Local::::try_from(ops).unwrap() +} + +fn get_op<'s>( + scope: &mut v8::PinScope<'s, '_>, + name: &str, +) -> v8::Local<'s, v8::Value> { + let ops = core_ops(scope); + let key = v8::String::new(scope, name).unwrap(); + ops.get(scope, key.into()).unwrap() +} + +fn set_op_alias( + scope: &mut v8::PinScope, + obj: v8::Local, + export_name: &str, + op_name: &str, +) { + let op = get_op(scope, op_name); + let key = v8::String::new(scope, export_name).unwrap(); + obj.set(scope, key.into(), op); +} + +fn set_core_alias( + scope: &mut v8::PinScope, + obj: v8::Local, + export_name: &str, +) { + let core = core_object(scope); + let key = v8::String::new(scope, export_name).unwrap(); + let value = core.get(scope, key.into()).unwrap(); + obj.set(scope, key.into(), value); +} + +#[op2] +pub fn op_node_internal_binding_encodings<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + for (name, value) in [ + ("ASCII", 0), + ("UTF8", 1), + ("BASE64", 2), + ("UCS2", 3), + ("BINARY", 4), + ("HEX", 5), + ("BUFFER", 6), + ("BASE64URL", 7), + ("LATIN1", 4), + ] { + set_i32(scope, obj, name, value); + } + for (value, name) in [ + ("0", "ASCII"), + ("1", "UTF8"), + ("2", "BASE64"), + ("3", "UCS2"), + ("4", "LATIN1"), + ("5", "HEX"), + ("6", "BUFFER"), + ("7", "BASE64URL"), + ] { + set_str(scope, obj, value, name); + } + obj +} + +#[op2] +#[string] +pub fn op_node_ares_strerror(#[smi] code: i32) -> &'static str { + const ERROR_TEXT: &[&str] = &[ + "Successful completion", + "DNS server returned answer with no data", + "DNS server claims query was misformatted", + "DNS server returned general failure", + "Domain name not found", + "DNS server does not implement requested operation", + "DNS server refused query", + "Misformatted DNS query", + "Misformatted domain name", + "Unsupported address family", + "Misformatted DNS reply", + "Could not contact DNS servers", + "Timeout while contacting DNS servers", + "End of file", + "Error reading file", + "Out of memory", + "Channel is being destroyed", + "Misformatted string", + "Illegal flags specified", + "Given hostname is not numeric", + "Illegal hints flags specified", + "c-ares library initialization not yet performed", + "Error loading iphlpapi.dll", + "Could not find GetNetworkParams function", + "DNS query cancelled", + ]; + ERROR_TEXT.get(code as usize).copied().unwrap_or("unknown") +} + +#[op2] +pub fn op_node_internal_binding_ares<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + for (name, value) in [ + ("ARES_AI_CANONNAME", 1 << 0), + ("ARES_AI_NUMERICHOST", 1 << 1), + ("ARES_AI_PASSIVE", 1 << 2), + ("ARES_AI_NUMERICSERV", 1 << 3), + ("AI_V4MAPPED", 1 << 4), + ("AI_ALL", 1 << 5), + ("AI_ADDRCONFIG", 1 << 6), + ("ARES_AI_NOSORT", 1 << 7), + ("ARES_AI_ENVHOSTS", 1 << 8), + ] { + set_i32(scope, obj, name, value); + } + + set_op_alias(scope, obj, "ares_strerror", "op_node_ares_strerror"); + obj +} + +#[op2] +pub fn op_node_internal_binding_inspector<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + set_op_alias(scope, obj, "isEnabled", "op_inspector_enabled"); + obj +} + +#[op2] +pub fn op_node_internal_binding_handle_wrap<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + set_op_alias(scope, obj, "HandleWrap", "HandleWrap"); + obj +} + +#[op2] +pub fn op_node_internal_binding_tty_wrap<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + let tty = get_op(scope, "TTY"); + let tty_fn = v8::Local::::try_from(tty).unwrap(); + let prototype_key = v8::String::new(scope, "prototype").unwrap(); + let prototype = tty_fn.get(scope, prototype_key.into()).unwrap(); + let prototype = v8::Local::::try_from(prototype).unwrap(); + set_bool(scope, prototype, "isStreamBase", true); + let key = v8::String::new(scope, "TTY").unwrap(); + obj.set(scope, key.into(), tty_fn.into()); + obj +} + +#[op2] +pub fn op_node_internal_binding_libuv_winerror<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + set_op_alias(scope, obj, "uvTranslateSysError", "op_node_sys_to_uv_error"); + obj +} + +#[op2] +pub fn op_node_internal_binding_types<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + for name in [ + "isAnyArrayBuffer", + "isArgumentsObject", + "isArrayBuffer", + "isAsyncFunction", + "isBigIntObject", + "isBooleanObject", + "isBoxedPrimitive", + "isDataView", + "isDate", + "isGeneratorFunction", + "isGeneratorObject", + "isMap", + "isMapIterator", + "isModuleNamespaceObject", + "isNativeError", + "isNumberObject", + "isPromise", + "isProxy", + "isRegExp", + "isSet", + "isSetIterator", + "isSharedArrayBuffer", + "isStringObject", + "isSymbolObject", + "isTypedArray", + "isWeakMap", + "isWeakSet", + ] { + set_core_alias(scope, obj, name); + } + obj +} diff --git a/ext/node/ops/mod.rs b/ext/node/ops/mod.rs index 39a52af081689b..3d829d2c95763c 100644 --- a/ext/node/ops/mod.rs +++ b/ext/node/ops/mod.rs @@ -10,6 +10,7 @@ pub mod handle_wrap; pub mod http2; pub mod idna; pub mod inspector; +pub mod internal_binding; pub mod ipc; pub mod llhttp; pub mod module; diff --git a/ext/node/ops/pipe_wrap.rs b/ext/node/ops/pipe_wrap.rs index fe87c502aa6eab..63f8987f69508e 100644 --- a/ext/node/ops/pipe_wrap.rs +++ b/ext/node/ops/pipe_wrap.rs @@ -88,10 +88,36 @@ macro_rules! with_js_handle { }}; } -/// Connection callback for `uv_pipe_listen`. Fires -/// `this.onconnection(status)` on the server handle's JS object. The JS -/// `setupListenWrap` shim intercepts this to allocate a client PipeWrap -/// and call `accept` before forwarding to the user's onconnection. +fn new_pipe_client<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> Option> { + let pipe = { + let op_state = deno_core::JsRuntime::op_state_from(scope); + let mut op_state = op_state.borrow_mut(); + PipeWrap::new(PipeType::Socket, &mut op_state) + }; + let obj = deno_core::cppgc::make_cppgc_object(scope, pipe); + let global = v8::Global::new(scope, obj); + let pipe = + deno_core::cppgc::try_unwrap_cppgc_object::(scope, obj.into())?; + let pipe = unsafe { pipe.as_ref() }; + pipe.base.set_js_handle(global, scope); + Some(obj) +} + +fn ceil_pow_of_two(n: i32) -> i32 { + if n <= 1 { + return 1; + } + (n as u32) + .checked_next_power_of_two() + .and_then(|value| i32::try_from(value).ok()) + .unwrap_or(i32::MAX) +} + +/// Connection callback for `uv_pipe_listen`. Accepts a pending connection into +/// a native PipeWrap client and fires `this.onconnection(status, client)` on +/// the server handle's JS object. /// /// # Safety /// Must only be called by libuv as a `uv_connection_cb`. `server` must be @@ -107,9 +133,48 @@ unsafe extern "C" fn server_connection_cb(server: *mut UvStream, status: i32) { if let Some(onconnection) = this.get(scope, key.into()) && let Ok(func) = v8::Local::::try_from(onconnection) { + if status != 0 { + let status_val: v8::Local = + v8::Integer::new(scope, status).into(); + let undefined: v8::Local = v8::undefined(scope).into(); + func.call(scope, this.into(), &[status_val, undefined]); + return; + } + + let Some(client_obj) = new_pipe_client(scope) else { + let status_val: v8::Local = + v8::Integer::new(scope, uv_compat::UV_EINVAL).into(); + let undefined: v8::Local = v8::undefined(scope).into(); + func.call(scope, this.into(), &[status_val, undefined]); + return; + }; + + let accept_status = match deno_core::cppgc::try_unwrap_cppgc_object::< + PipeWrap, + >(scope, client_obj.into()) + { + Some(client) => { + let client = unsafe { client.as_ref() }; + let client_pipe = client.pipe_ptr(); + if client_pipe.is_null() { + uv_compat::UV_EBADF + } else { + unsafe { + uv_compat::uv_pipe_accept(server as *mut UvPipe, client_pipe) + } + } + } + None => uv_compat::UV_EINVAL, + }; + let status_val: v8::Local = - v8::Integer::new(scope, status).into(); - func.call(scope, this.into(), &[status_val]); + v8::Integer::new(scope, accept_status).into(); + let client_val: v8::Local = if accept_status == 0 { + client_obj.into() + } else { + v8::undefined(scope).into() + }; + func.call(scope, this.into(), &[status_val, client_val]); } }); } @@ -374,6 +439,8 @@ impl PipeWrap { Some("node:net.Server.listen()"), )?; } + let backlog = ceil_pow_of_two(backlog.saturating_add(1)); + // SAFETY: pipe is valid (null-checked above). Ok(unsafe { uv_compat::uv_pipe_listen(pipe, backlog, Some(server_connection_cb)) @@ -445,10 +512,19 @@ impl PipeWrap { } } - /// Change permissions on the bound pipe path. Takes already-translated - /// POSIX mode bits; the JS wrapper translates UV_READABLE/UV_WRITABLE. + /// Change permissions on the bound pipe path. Takes Node's UV_READABLE / + /// UV_WRITABLE flags and translates them to POSIX mode bits natively. #[fast] fn fchmod(&self, #[smi] mode: i32) -> i32 { + const UV_READABLE: i32 = 1; + const UV_WRITABLE: i32 = 2; + if mode != UV_READABLE + && mode != UV_WRITABLE + && mode != (UV_READABLE | UV_WRITABLE) + { + return uv_compat::UV_EINVAL; + } + #[cfg(unix)] { let pipe = self.pipe_ptr(); @@ -466,8 +542,15 @@ impl PipeWrap { Ok(p) => p, Err(_) => return uv_compat::UV_EINVAL, }; + let mut desired_mode: libc::mode_t = 0; + if mode & UV_READABLE != 0 { + desired_mode |= libc::S_IRUSR | libc::S_IRGRP | libc::S_IROTH; + } + if mode & UV_WRITABLE != 0 { + desired_mode |= libc::S_IWUSR | libc::S_IWGRP | libc::S_IWOTH; + } // SAFETY: c_path is a valid null-terminated C string. - if unsafe { libc::chmod(c_path.as_ptr(), mode as libc::mode_t) } != 0 { + if unsafe { libc::chmod(c_path.as_ptr(), desired_mode) } != 0 { return -1; } 0 diff --git a/ext/node/ops/tcp_wrap.rs b/ext/node/ops/tcp_wrap.rs index 92c9d3d575edbe..fbd36eaaac938b 100644 --- a/ext/node/ops/tcp_wrap.rs +++ b/ext/node/ops/tcp_wrap.rs @@ -90,8 +90,26 @@ macro_rules! with_js_handle { }}; } -/// Connection callback for `uv_listen`. Fires `this.onconnection(status)` on -/// the server handle's JS object. +fn new_tcp_client<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> Option> { + let tcp = { + let op_state = deno_core::JsRuntime::op_state_from(scope); + let mut op_state = op_state.borrow_mut(); + TCPWrap::new(SocketType::Socket, &mut op_state) + }; + let obj = deno_core::cppgc::make_cppgc_object(scope, tcp); + let global = v8::Global::new(scope, obj); + let tcp = + deno_core::cppgc::try_unwrap_cppgc_object::(scope, obj.into())?; + let tcp = unsafe { tcp.as_ref() }; + tcp.base.set_js_handle(global, scope); + Some(obj) +} + +/// Connection callback for `uv_listen`. Accepts a pending connection into a +/// native TCPWrap client and fires `this.onconnection(status, client)` on the +/// server handle's JS object. /// /// # Safety /// Must only be called by libuv as a `uv_connection_cb`. `server` must be a @@ -110,9 +128,46 @@ pub(crate) unsafe extern "C" fn server_connection_cb( if let Some(onconnection) = this.get(scope, key.into()) && let Ok(func) = v8::Local::::try_from(onconnection) { + if status != 0 { + let status_val: v8::Local = + v8::Integer::new(scope, status).into(); + let undefined: v8::Local = v8::undefined(scope).into(); + func.call(scope, this.into(), &[status_val, undefined]); + return; + } + + let Some(client_obj) = new_tcp_client(scope) else { + let status_val: v8::Local = + v8::Integer::new(scope, uv_compat::UV_EINVAL).into(); + let undefined: v8::Local = v8::undefined(scope).into(); + func.call(scope, this.into(), &[status_val, undefined]); + return; + }; + + let accept_status = match deno_core::cppgc::try_unwrap_cppgc_object::< + TCPWrap, + >(scope, client_obj.into()) + { + Some(client) => { + let client = unsafe { client.as_ref() }; + let client_tcp = client.tcp_ptr(); + if client_tcp.is_null() { + uv_compat::UV_EBADF + } else { + unsafe { uv_compat::uv_accept(server, client_tcp as *mut UvStream) } + } + } + None => uv_compat::UV_EINVAL, + }; + let status_val: v8::Local = - v8::Integer::new(scope, status).into(); - func.call(scope, this.into(), &[status_val]); + v8::Integer::new(scope, accept_status).into(); + let client_val: v8::Local = if accept_status == 0 { + client_obj.into() + } else { + v8::undefined(scope).into() + }; + func.call(scope, this.into(), &[status_val, client_val]); } }); } diff --git a/ext/node/polyfills/internal/cluster/round_robin_handle.ts b/ext/node/polyfills/internal/cluster/round_robin_handle.ts index c7b3d86815e020..fa7404df59b5e2 100644 --- a/ext/node/polyfills/internal/cluster/round_robin_handle.ts +++ b/ext/node/polyfills/internal/cluster/round_robin_handle.ts @@ -21,13 +21,7 @@ const { } = core.loadExtScript("ext:deno_node/internal/cluster/linkedlist.ts"); const { constants: TCPConstants, - setupListenWrap: setupTCPListenWrap, - TCP, } = core.loadExtScript("ext:deno_node/internal_binding/tcp_wrap.ts"); -const { - Pipe, - setupListenWrap: setupPipeListenWrap, -} = core.loadExtScript("ext:deno_node/internal_binding/pipe_wrap.ts"); const { ArrayIsArray, Boolean, SafeMap } = primordials; @@ -67,17 +61,8 @@ function RoundRobinHandle( } this.server.once("listening", () => { this.handle = this.server._handle; - // Replace the listen-wrap that net.Server set up: it captures the - // onconnection at wrap time, so just assigning a new onconnection - // (as Node does) would skip our accept-wrap step in Deno's TCP/Pipe - // bindings. Re-run setupListenWrap with the new user callback. this.handle.onconnection = (err: number, handle: any) => this.distribute(err, handle); - if (this.handle instanceof TCP) { - setupTCPListenWrap(this.handle); - } else if (this.handle instanceof Pipe) { - setupPipeListenWrap(this.handle); - } this.server._handle = null; this.server = null; }); diff --git a/ext/node/polyfills/internal_binding/_libuv_winerror.ts b/ext/node/polyfills/internal_binding/_libuv_winerror.ts index 3e7886d95dc310..80ded222659e64 100644 --- a/ext/node/polyfills/internal_binding/_libuv_winerror.ts +++ b/ext/node/polyfills/internal_binding/_libuv_winerror.ts @@ -1,12 +1,6 @@ // Copyright 2018-2026 the Deno authors. MIT license. (function () { -const { core } = __bootstrap; -const { op_node_sys_to_uv_error } = core.ops; - -function uvTranslateSysError(sysErrno: number): string { - return op_node_sys_to_uv_error(sysErrno); -} - -return { uvTranslateSysError }; +const { op_node_internal_binding_libuv_winerror } = __bootstrap.core.ops; +return op_node_internal_binding_libuv_winerror(); })(); diff --git a/ext/node/polyfills/internal_binding/_listen.ts b/ext/node/polyfills/internal_binding/_listen.ts deleted file mode 100644 index dac4fd193bbf06..00000000000000 --- a/ext/node/polyfills/internal_binding/_listen.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2018-2026 the Deno authors. MIT license. -(function () { -const { primordials } = __bootstrap; -const { MathClz32 } = primordials; - -/** - * @param n Number to act on. - * @return The number rounded up to the nearest power of 2. - */ -function ceilPowOf2(n: number) { - const roundPowOf2 = 1 << (31 - MathClz32(n)); - - return roundPowOf2 < n ? roundPowOf2 * 2 : roundPowOf2; -} - -/** Initial backoff delay of 5ms following a temporary accept failure. */ -const INITIAL_ACCEPT_BACKOFF_DELAY = 5; - -/** Max backoff delay of 1s following a temporary accept failure. */ -const MAX_ACCEPT_BACKOFF_DELAY = 1000; - -return { - ceilPowOf2, - INITIAL_ACCEPT_BACKOFF_DELAY, - MAX_ACCEPT_BACKOFF_DELAY, -}; -})(); diff --git a/ext/node/polyfills/internal_binding/_node.ts b/ext/node/polyfills/internal_binding/_node.ts index 95fac78305989b..f6da2107b0471f 100644 --- a/ext/node/polyfills/internal_binding/_node.ts +++ b/ext/node/polyfills/internal_binding/_node.ts @@ -6,17 +6,8 @@ * https://github.com/nodejs/node/blob/3b72788afb7365e10ae1e97c71d1f60ee29f09f2/src/node.h#L728-L738 */ (function () { -enum Encodings { - ASCII, // 0 - UTF8, // 1 - BASE64, // 2 - UCS2, // 3 - BINARY, // 4 - HEX, // 5 - BUFFER, // 6 - BASE64URL, // 7 - LATIN1 = 4, // 4 = BINARY -} +const { op_node_internal_binding_encodings } = __bootstrap.core.ops; +const Encodings = op_node_internal_binding_encodings(); return { Encodings, diff --git a/ext/node/polyfills/internal_binding/ares.ts b/ext/node/polyfills/internal_binding/ares.ts index 2ffbdf99723da5..77e49f5ce61c42 100644 --- a/ext/node/polyfills/internal_binding/ares.ts +++ b/ext/node/polyfills/internal_binding/ares.ts @@ -1,81 +1,6 @@ // Copyright 2018-2026 the Deno authors. MIT license. -/* Copyright 1998 by the Massachusetts Institute of Technology. - * - * Permission to use, copy, modify, and distribute this - * software and its documentation for any purpose and without - * fee is hereby granted, provided that the above copyright - * notice appear in all copies and that both that copyright - * notice and this permission notice appear in supporting - * documentation, and that the name of M.I.T. not be used in - * advertising or publicity pertaining to distribution of the - * software without specific, written prior permission. - * M.I.T. makes no representations about the suitability of - * this software for any purpose. It is provided "as is" - * without express or implied warranty. - */ -// REF: https://github.com/nodejs/node/blob/master/deps/cares/include/ares.h#L190 (function () { -const ARES_AI_CANONNAME = 1 << 0; -const ARES_AI_NUMERICHOST = 1 << 1; -const ARES_AI_PASSIVE = 1 << 2; -const ARES_AI_NUMERICSERV = 1 << 3; -const AI_V4MAPPED = 1 << 4; -const AI_ALL = 1 << 5; -const AI_ADDRCONFIG = 1 << 6; -const ARES_AI_NOSORT = 1 << 7; -const ARES_AI_ENVHOSTS = 1 << 8; - -// REF: https://github.com/nodejs/node/blob/master/deps/cares/src/lib/ares_strerror.c - -// deno-lint-ignore camelcase -function ares_strerror(code: number) { - /* Return a string literal from a table. */ - const errorText = [ - "Successful completion", - "DNS server returned answer with no data", - "DNS server claims query was misformatted", - "DNS server returned general failure", - "Domain name not found", - "DNS server does not implement requested operation", - "DNS server refused query", - "Misformatted DNS query", - "Misformatted domain name", - "Unsupported address family", - "Misformatted DNS reply", - "Could not contact DNS servers", - "Timeout while contacting DNS servers", - "End of file", - "Error reading file", - "Out of memory", - "Channel is being destroyed", - "Misformatted string", - "Illegal flags specified", - "Given hostname is not numeric", - "Illegal hints flags specified", - "c-ares library initialization not yet performed", - "Error loading iphlpapi.dll", - "Could not find GetNetworkParams function", - "DNS query cancelled", - ]; - - if (code >= 0 && code < errorText.length) { - return errorText[code]; - } else { - return "unknown"; - } -} - -return { - ares_strerror, - ARES_AI_CANONNAME, - ARES_AI_NUMERICHOST, - ARES_AI_PASSIVE, - ARES_AI_NUMERICSERV, - AI_V4MAPPED, - AI_ALL, - AI_ADDRCONFIG, - ARES_AI_NOSORT, - ARES_AI_ENVHOSTS, -}; +const { op_node_internal_binding_ares } = __bootstrap.core.ops; +return op_node_internal_binding_ares(); })(); diff --git a/ext/node/polyfills/internal_binding/handle_wrap.ts b/ext/node/polyfills/internal_binding/handle_wrap.ts index 7c4d99e422891f..84bd6de1f95c5e 100644 --- a/ext/node/polyfills/internal_binding/handle_wrap.ts +++ b/ext/node/polyfills/internal_binding/handle_wrap.ts @@ -24,10 +24,6 @@ // - https://github.com/nodejs/node/blob/master/src/handle_wrap.cc // - https://github.com/nodejs/node/blob/master/src/handle_wrap.h (function () { -const { core } = __bootstrap; -const { HandleWrap } = core.ops; - -return { - HandleWrap, -}; +const { op_node_internal_binding_handle_wrap } = __bootstrap.core.ops; +return op_node_internal_binding_handle_wrap(); })(); diff --git a/ext/node/polyfills/internal_binding/inspector.js b/ext/node/polyfills/internal_binding/inspector.js index 92012dc6ee3f80..25384217eebcbb 100644 --- a/ext/node/polyfills/internal_binding/inspector.js +++ b/ext/node/polyfills/internal_binding/inspector.js @@ -1,14 +1,6 @@ // Copyright 2018-2026 the Deno authors. MIT license. (function () { -const { core } = globalThis.__bootstrap; -const { op_inspector_enabled } = core.ops; - -function isEnabled() { - return op_inspector_enabled(); -} - -return { - isEnabled, -}; +const { op_node_internal_binding_inspector } = globalThis.__bootstrap.core.ops; +return op_node_internal_binding_inspector(); })(); diff --git a/ext/node/polyfills/internal_binding/pipe_wrap.ts b/ext/node/polyfills/internal_binding/pipe_wrap.ts index 16217d1d06d064..6eec6ec686a739 100644 --- a/ext/node/polyfills/internal_binding/pipe_wrap.ts +++ b/ext/node/polyfills/internal_binding/pipe_wrap.ts @@ -30,22 +30,11 @@ // setPendingInstances) are on PipeWrap itself. (function () { -const { core, primordials } = __bootstrap; +const { core } = __bootstrap; const { op_node_create_pipe, PipeWrap } = core.ops; const { AsyncWrap, providerType } = core.loadExtScript( "ext:deno_node/internal_binding/async_wrap.ts", ); -const { ceilPowOf2 } = core.loadExtScript( - "ext:deno_node/internal_binding/_listen.ts", -); -const { codeMap } = core.loadExtScript( - "ext:deno_node/internal_binding/uv.ts", -); -const { fs } = core.loadExtScript( - "ext:deno_node/internal_binding/constants.ts", -); - -const { FunctionPrototypeCall, MapPrototypeGet } = primordials; // Mark PipeWrap as a StreamBase handle, matching Node's StreamBase::AddMethods. PipeWrap.prototype.isStreamBase = true; @@ -80,77 +69,6 @@ class PipeConnectWrap extends AsyncWrap { } } -// Translate UV_READABLE/UV_WRITABLE flags to POSIX mode bits before calling -// the native fchmod op (which takes raw chmod bits). -const nativeFchmod = PipeWrap.prototype.fchmod; -PipeWrap.prototype.fchmod = function (mode: number): number { - if ( - mode !== constants.UV_READABLE && - mode !== constants.UV_WRITABLE && - mode !== (constants.UV_WRITABLE | constants.UV_READABLE) - ) { - return MapPrototypeGet(codeMap, "EINVAL"); - } - - let desiredMode = 0; - if (mode & constants.UV_READABLE) { - desiredMode |= fs.S_IRUSR | fs.S_IRGRP | fs.S_IROTH; - } - if (mode & constants.UV_WRITABLE) { - desiredMode |= fs.S_IWUSR | fs.S_IWGRP | fs.S_IWOTH; - } - - return FunctionPrototypeCall(nativeFchmod, this, desiredMode); -}; - -// Round up the backlog to the next power of two (matching the previous -// implementation). TCP uses the raw backlog; pipes historically rounded. -const nativeListen = PipeWrap.prototype.listen; -PipeWrap.prototype.listen = function (backlog: number): number { - return FunctionPrototypeCall(nativeListen, this, ceilPowOf2(backlog + 1)); -}; - -/** - * Wrap the native PipeWrap.listen() to handle connection acceptance. - * The Rust server_connection_cb fires onconnection(status), and this - * wrapper creates client handles and calls uv_accept before forwarding - * to the user's onconnection(status, clientHandle). - */ -function setupListenWrap(serverHandle: InstanceType) { - const userOnConnection = serverHandle.onconnection; - serverHandle.onconnection = function (status: number) { - if (status !== 0) { - if (userOnConnection) { - FunctionPrototypeCall( - userOnConnection, - serverHandle, - status, - undefined, - ); - } - return; - } - - const clientHandle = new PipeWrap(socketType.SOCKET); - const acceptErr = serverHandle.accept(clientHandle); - if (acceptErr !== 0) { - if (userOnConnection) { - FunctionPrototypeCall( - userOnConnection, - serverHandle, - acceptErr, - undefined, - ); - } - return; - } - - if (userOnConnection) { - FunctionPrototypeCall(userOnConnection, serverHandle, 0, clientHandle); - } - }; -} - // Re-export the Rust PipeWrap as Pipe. /** Create an anonymous pipe pair. Returns [readFd, writeFd]. */ @@ -162,13 +80,11 @@ const _defaultExport = { Pipe: PipeWrap, PipeConnectWrap, constants, - setupListenWrap, createPipe, }; return { Pipe: PipeWrap, - setupListenWrap, createPipe, PipeConnectWrap, socketType, diff --git a/ext/node/polyfills/internal_binding/tcp_wrap.ts b/ext/node/polyfills/internal_binding/tcp_wrap.ts index c317d27cdc7fb8..6256b8ab2c0149 100644 --- a/ext/node/polyfills/internal_binding/tcp_wrap.ts +++ b/ext/node/polyfills/internal_binding/tcp_wrap.ts @@ -32,12 +32,11 @@ // accept) and for re-exporting types. (function () { -const { core, primordials } = __bootstrap; +const { core } = __bootstrap; const { TCPWrap } = core.ops; const { AsyncWrap, providerType } = core.loadExtScript( "ext:deno_node/internal_binding/async_wrap.ts", ); -const { FunctionPrototypeCall } = primordials; // Mark TCPWrap as a StreamBase handle, matching Node's StreamBase::AddMethods. // This allows parser.consume(socket._handle) to detect it as consumable. @@ -74,63 +73,16 @@ enum constants { UV_TCP_REUSEPORT = 4, } -/** - * Wrap the native TCPWrap.listen() to handle connection acceptance. - * The Rust server_connection_cb fires onconnection(status), and this - * wrapper creates client handles and calls uv_accept before forwarding - * to the user's onconnection(status, clientHandle). - * - * TODO: Move this logic into Rust by making the connection callback - * allocate a CppGC TCPWrap directly, removing the need for this JS shim. - */ -function setupListenWrap(serverHandle: InstanceType) { - const userOnConnection = serverHandle.onconnection; - serverHandle.onconnection = function (status: number) { - if (status !== 0) { - if (userOnConnection) { - FunctionPrototypeCall( - userOnConnection, - serverHandle, - status, - undefined, - ); - } - return; - } - - // Create a new client handle and accept the connection - const clientHandle = new TCPWrap(socketType.SOCKET); - const acceptErr = serverHandle.accept(clientHandle); - if (acceptErr !== 0) { - if (userOnConnection) { - FunctionPrototypeCall( - userOnConnection, - serverHandle, - acceptErr, - undefined, - ); - } - return; - } - - if (userOnConnection) { - FunctionPrototypeCall(userOnConnection, serverHandle, 0, clientHandle); - } - }; -} - // Re-export the Rust TCPWrap as TCP. const _defaultExport = { TCPConnectWrap, constants, TCP: TCPWrap, - setupListenWrap, }; return { TCP: TCPWrap, - setupListenWrap, TCPConnectWrap, socketType, constants, diff --git a/ext/node/polyfills/internal_binding/tty_wrap.ts b/ext/node/polyfills/internal_binding/tty_wrap.ts index 1e656de06b3a4d..d79017fad950f8 100644 --- a/ext/node/polyfills/internal_binding/tty_wrap.ts +++ b/ext/node/polyfills/internal_binding/tty_wrap.ts @@ -1,12 +1,5 @@ // Copyright 2018-2026 the Deno authors. MIT license. (function () { -const { core } = __bootstrap; -const { TTY } = core.ops; - -// Mark TTY as a StreamBase handle, matching Node's StreamBase::AddMethods. -TTY.prototype.isStreamBase = true; - -return { - TTY, -}; +const { op_node_internal_binding_tty_wrap } = __bootstrap.core.ops; +return op_node_internal_binding_tty_wrap(); })(); diff --git a/ext/node/polyfills/internal_binding/types.ts b/ext/node/polyfills/internal_binding/types.ts index 88f306f3d3cbab..9afd80473dd735 100644 --- a/ext/node/polyfills/internal_binding/types.ts +++ b/ext/node/polyfills/internal_binding/types.ts @@ -1,87 +1,6 @@ // Copyright 2018-2026 the Deno authors. MIT license. -// -// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. (function () { -const { core } = __bootstrap; - -const { - // isExternal, - isAnyArrayBuffer, - isArgumentsObject, - isArrayBuffer, - isAsyncFunction, - isBigIntObject, - isBooleanObject, - isBoxedPrimitive, - isDataView, - isDate, - isGeneratorFunction, - isGeneratorObject, - isMap, - isMapIterator, - isModuleNamespaceObject, - isNativeError, - isNumberObject, - isPromise, - isProxy, - isRegExp, - isSet, - isSetIterator, - isSharedArrayBuffer, - isStringObject, - isSymbolObject, - isTypedArray, - isWeakMap, - isWeakSet, -} = core; - -return { - isAnyArrayBuffer, - isArgumentsObject, - isArrayBuffer, - isAsyncFunction, - isBigIntObject, - isBooleanObject, - isBoxedPrimitive, - isDataView, - isDate, - isGeneratorFunction, - isGeneratorObject, - isMap, - isMapIterator, - isModuleNamespaceObject, - isNativeError, - isNumberObject, - isPromise, - isProxy, - isRegExp, - isSet, - isSetIterator, - isSharedArrayBuffer, - isStringObject, - isSymbolObject, - isTypedArray, - isWeakMap, - isWeakSet, -}; +const { op_node_internal_binding_types } = __bootstrap.core.ops; +return op_node_internal_binding_types(); })(); diff --git a/ext/node/polyfills/net.ts b/ext/node/polyfills/net.ts index a93e8e2e979508..1b8ae13f652b22 100644 --- a/ext/node/polyfills/net.ts +++ b/ext/node/polyfills/net.ts @@ -95,7 +95,6 @@ const { Buffer } = core.loadExtScript("ext:deno_node/internal/buffer.mjs"); type LookupOneOptions = any; const { constants: TCPConstants, - setupListenWrap, TCP, TCPConnectWrap, } = core.loadExtScript("ext:deno_node/internal_binding/tcp_wrap.ts"); @@ -103,7 +102,6 @@ const { constants: PipeConstants, Pipe, PipeConnectWrap, - setupListenWrap: setupPipeListenWrap, } = core.loadExtScript("ext:deno_node/internal_binding/pipe_wrap.ts"); const { ShutdownWrap } = core.loadExtScript( "ext:deno_node/internal_binding/stream_wrap.ts", @@ -2662,15 +2660,6 @@ function _setupListenHandle( this._handle.onconnection = _onconnection; this._handle[ownerSymbol] = this; - // For TCP and Pipe handles, wrap the onconnection callback to create - // client handles and call uv_accept before forwarding to - // _onconnection(status, clientHandle). - if (ObjectPrototypeIsPrototypeOf(TCP.prototype, this._handle)) { - setupListenWrap(this._handle); - } else if (ObjectPrototypeIsPrototypeOf(Pipe.prototype, this._handle)) { - setupPipeListenWrap(this._handle); - } - // Use a backlog of 512 entries. We pass 511 to the listen() call because // the kernel does: backlogsize = roundup_pow_of_two(backlogsize + 1); // which will thus give us a backlog of 512 entries. From c7f9e4fc414882495855d670a61db02022be3695 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 11:22:21 -0700 Subject: [PATCH 02/33] port more node internal bindings to rust --- ext/node/lib.rs | 15 + ext/node/ops/constant.rs | 18 +- ext/node/ops/internal_binding_constants.rs | 88 + ext/node/ops/internal_binding_crypto.rs | 285 +++ ext/node/ops/internal_binding_uv.rs | 2243 +++++++++++++++++ ext/node/ops/mod.rs | 3 + ext/node/ops/winerror.rs | 15 +- .../internal_binding/_timingSafeEqual.ts | 119 +- .../polyfills/internal_binding/constants.ts | 896 +------ ext/node/polyfills/internal_binding/crypto.ts | 18 +- ext/node/polyfills/internal_binding/uv.ts | 609 +---- 11 files changed, 2660 insertions(+), 1649 deletions(-) create mode 100644 ext/node/ops/internal_binding_constants.rs create mode 100644 ext/node/ops/internal_binding_crypto.rs create mode 100644 ext/node/ops/internal_binding_uv.rs diff --git a/ext/node/lib.rs b/ext/node/lib.rs index b63fee523599cc..cacbdfb13ec07c 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -328,6 +328,18 @@ deno_core::extension!(deno_node, ops::internal_binding::op_node_internal_binding_tty_wrap, ops::internal_binding::op_node_internal_binding_types, ops::internal_binding::op_node_ares_strerror, + ops::internal_binding_constants::op_node_internal_binding_constants, + ops::internal_binding_crypto::op_node_crypto_get_fips, + ops::internal_binding_crypto::op_node_crypto_set_fips, + ops::internal_binding_crypto::op_node_crypto_timing_safe_equal, + ops::internal_binding_crypto::op_node_internal_binding_crypto, + ops::internal_binding_crypto::op_node_internal_binding_timing_safe_equal, + ops::internal_binding_uv::op_node_internal_binding_uv, + ops::internal_binding_uv::op_node_uv_errname, + ops::internal_binding_uv::op_node_uv_get_code_map, + ops::internal_binding_uv::op_node_uv_get_error_map, + ops::internal_binding_uv::op_node_uv_get_error_message, + ops::internal_binding_uv::op_node_uv_map_sys_errno_to_uv_errno, ops::zlib::op_zlib_crc32, ops::zlib::op_zlib_crc32_string, ops::handle_wrap::op_node_new_async_id, @@ -883,6 +895,9 @@ deno_core::extension!(deno_node, ]; ext.external_references.to_mut().extend(external_references); + ext.external_references.to_mut().extend( + ops::internal_binding_crypto::external_references(), + ); }, ); diff --git a/ext/node/ops/constant.rs b/ext/node/ops/constant.rs index 8a60d180af1417..727724c11b9354 100644 --- a/ext/node/ops/constant.rs +++ b/ext/node/ops/constant.rs @@ -243,18 +243,14 @@ fn common_unix_fs_constants() -> FsConstants { } #[cfg(target_os = "macos")] -#[op2] -#[serde] -pub fn op_node_fs_constants() -> FsConstants { +pub fn node_fs_constants() -> FsConstants { let mut constants = common_unix_fs_constants(); constants.o_symlink = Some(libc::O_SYMLINK); constants } #[cfg(any(target_os = "android", target_os = "linux"))] -#[op2] -#[serde] -pub fn op_node_fs_constants() -> FsConstants { +pub fn node_fs_constants() -> FsConstants { let mut constants = common_unix_fs_constants(); constants.o_noatime = Some(libc::O_NOATIME); constants.o_direct = Some(libc::O_DIRECT); @@ -262,9 +258,7 @@ pub fn op_node_fs_constants() -> FsConstants { } #[cfg(windows)] -#[op2] -#[serde] -pub fn op_node_fs_constants() -> FsConstants { +pub fn node_fs_constants() -> FsConstants { let mut constants = FsConstants::default(); // https://github.com/nodejs/node/blob/4dafa7747f7d2804aed3f3400d04f1ec6af24160/deps/uv/include/uv/win.h#L65-L68 // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_file_stat_lx_information @@ -293,3 +287,9 @@ pub fn op_node_fs_constants() -> FsConstants { constants.s_ififo = Some(S_IFIFO); constants } + +#[op2] +#[serde] +pub fn op_node_fs_constants() -> FsConstants { + node_fs_constants() +} diff --git a/ext/node/ops/internal_binding_constants.rs b/ext/node/ops/internal_binding_constants.rs new file mode 100644 index 00000000000000..86bae65f8882fa --- /dev/null +++ b/ext/node/ops/internal_binding_constants.rs @@ -0,0 +1,88 @@ +// Copyright 2018-2026 the Deno authors. MIT license. + +use deno_core::op2; +use deno_core::serde_v8; +use deno_core::v8; + +const OS_DARWIN_JSON: &str = r#"{"UV_UDP_IPV6ONLY":2,"UV_UDP_REUSEADDR":4,"dlopen":{"RTLD_LAZY":1,"RTLD_NOW":2,"RTLD_GLOBAL":8,"RTLD_LOCAL":4},"errno":{"E2BIG":7,"EACCES":13,"EADDRINUSE":48,"EADDRNOTAVAIL":49,"EAFNOSUPPORT":47,"EAGAIN":35,"EALREADY":37,"EBADF":9,"EBADMSG":94,"EBUSY":16,"ECANCELED":89,"ECHILD":10,"ECONNABORTED":53,"ECONNREFUSED":61,"ECONNRESET":54,"EDEADLK":11,"EDESTADDRREQ":39,"EDOM":33,"EDQUOT":69,"EEXIST":17,"EFAULT":14,"EFBIG":27,"EHOSTUNREACH":65,"EIDRM":90,"EILSEQ":92,"EINPROGRESS":36,"EINTR":4,"EINVAL":22,"EIO":5,"EISCONN":56,"EISDIR":21,"ELOOP":62,"EMFILE":24,"EMLINK":31,"EMSGSIZE":40,"EMULTIHOP":95,"ENAMETOOLONG":63,"ENETDOWN":50,"ENETRESET":52,"ENETUNREACH":51,"ENFILE":23,"ENOBUFS":55,"ENODATA":96,"ENODEV":19,"ENOENT":2,"ENOEXEC":8,"ENOLCK":77,"ENOLINK":97,"ENOMEM":12,"ENOMSG":91,"ENOPROTOOPT":42,"ENOSPC":28,"ENOSR":98,"ENOSTR":99,"ENOSYS":78,"ENOTCONN":57,"ENOTDIR":20,"ENOTEMPTY":66,"ENOTSOCK":38,"ENOTSUP":45,"ENOTTY":25,"ENXIO":6,"EOPNOTSUPP":102,"EOVERFLOW":84,"EPERM":1,"EPIPE":32,"EPROTO":100,"EPROTONOSUPPORT":43,"EPROTOTYPE":41,"ERANGE":34,"EROFS":30,"ESPIPE":29,"ESRCH":3,"ESTALE":70,"ETIME":101,"ETIMEDOUT":60,"ETXTBSY":26,"EWOULDBLOCK":35,"EXDEV":18},"signals":{"SIGHUP":1,"SIGINT":2,"SIGQUIT":3,"SIGILL":4,"SIGTRAP":5,"SIGABRT":6,"SIGIOT":6,"SIGBUS":10,"SIGFPE":8,"SIGKILL":9,"SIGUSR1":30,"SIGSEGV":11,"SIGUSR2":31,"SIGPIPE":13,"SIGALRM":14,"SIGTERM":15,"SIGCHLD":20,"SIGCONT":19,"SIGSTOP":17,"SIGTSTP":18,"SIGTTIN":21,"SIGTTOU":22,"SIGURG":16,"SIGXCPU":24,"SIGXFSZ":25,"SIGVTALRM":26,"SIGPROF":27,"SIGWINCH":28,"SIGIO":23,"SIGINFO":29,"SIGSYS":12},"priority":{"PRIORITY_LOW":19,"PRIORITY_BELOW_NORMAL":10,"PRIORITY_NORMAL":0,"PRIORITY_ABOVE_NORMAL":-7,"PRIORITY_HIGH":-14,"PRIORITY_HIGHEST":-20}}"#; +const OS_LINUX_JSON: &str = r#"{"UV_UDP_IPV6ONLY":2,"UV_UDP_REUSEADDR":4,"dlopen":{"RTLD_LAZY":1,"RTLD_NOW":2,"RTLD_GLOBAL":256,"RTLD_LOCAL":0,"RTLD_DEEPBIND":8},"errno":{"E2BIG":7,"EACCES":13,"EADDRINUSE":98,"EADDRNOTAVAIL":99,"EAFNOSUPPORT":97,"EAGAIN":11,"EALREADY":114,"EBADF":9,"EBADMSG":74,"EBUSY":16,"ECANCELED":125,"ECHILD":10,"ECONNABORTED":103,"ECONNREFUSED":111,"ECONNRESET":104,"EDEADLK":35,"EDESTADDRREQ":89,"EDOM":33,"EDQUOT":122,"EEXIST":17,"EFAULT":14,"EFBIG":27,"EHOSTUNREACH":113,"EIDRM":43,"EILSEQ":84,"EINPROGRESS":115,"EINTR":4,"EINVAL":22,"EIO":5,"EISCONN":106,"EISDIR":21,"ELOOP":40,"EMFILE":24,"EMLINK":31,"EMSGSIZE":90,"EMULTIHOP":72,"ENAMETOOLONG":36,"ENETDOWN":100,"ENETRESET":102,"ENETUNREACH":101,"ENFILE":23,"ENOBUFS":105,"ENODATA":61,"ENODEV":19,"ENOENT":2,"ENOEXEC":8,"ENOLCK":37,"ENOLINK":67,"ENOMEM":12,"ENOMSG":42,"ENOPROTOOPT":92,"ENOSPC":28,"ENOSR":63,"ENOSTR":60,"ENOSYS":38,"ENOTCONN":107,"ENOTDIR":20,"ENOTEMPTY":39,"ENOTSOCK":88,"ENOTSUP":95,"ENOTTY":25,"ENXIO":6,"EOPNOTSUPP":95,"EOVERFLOW":75,"EPERM":1,"EPIPE":32,"EPROTO":71,"EPROTONOSUPPORT":93,"EPROTOTYPE":91,"ERANGE":34,"EROFS":30,"ESPIPE":29,"ESRCH":3,"ESTALE":116,"ETIME":62,"ETIMEDOUT":110,"ETXTBSY":26,"EWOULDBLOCK":11,"EXDEV":18},"signals":{"SIGHUP":1,"SIGINT":2,"SIGQUIT":3,"SIGILL":4,"SIGTRAP":5,"SIGABRT":6,"SIGIOT":6,"SIGBUS":7,"SIGFPE":8,"SIGKILL":9,"SIGUSR1":10,"SIGSEGV":11,"SIGUSR2":12,"SIGPIPE":13,"SIGALRM":14,"SIGTERM":15,"SIGCHLD":17,"SIGSTKFLT":16,"SIGCONT":18,"SIGSTOP":19,"SIGTSTP":20,"SIGTTIN":21,"SIGTTOU":22,"SIGURG":23,"SIGXCPU":24,"SIGXFSZ":25,"SIGVTALRM":26,"SIGPROF":27,"SIGWINCH":28,"SIGIO":29,"SIGPOLL":29,"SIGPWR":30,"SIGSYS":31,"SIGUNUSED":31},"priority":{"PRIORITY_LOW":19,"PRIORITY_BELOW_NORMAL":10,"PRIORITY_NORMAL":0,"PRIORITY_ABOVE_NORMAL":-7,"PRIORITY_HIGH":-14,"PRIORITY_HIGHEST":-20}}"#; +const OS_WINDOWS_JSON: &str = r#"{"UV_UDP_IPV6ONLY":2,"UV_UDP_REUSEADDR":4,"dlopen":{},"errno":{"E2BIG":7,"EACCES":13,"EADDRINUSE":100,"EADDRNOTAVAIL":101,"EAFNOSUPPORT":102,"EAGAIN":11,"EALREADY":103,"EBADF":9,"EBADMSG":104,"EBUSY":16,"ECANCELED":105,"ECHILD":10,"ECONNABORTED":106,"ECONNREFUSED":107,"ECONNRESET":108,"EDEADLK":36,"EDESTADDRREQ":109,"EDOM":33,"EEXIST":17,"EFAULT":14,"EFBIG":27,"EHOSTUNREACH":110,"EIDRM":111,"EILSEQ":42,"EINPROGRESS":112,"EINTR":4,"EINVAL":22,"EIO":5,"EISCONN":113,"EISDIR":21,"ELOOP":114,"EMFILE":24,"EMLINK":31,"EMSGSIZE":115,"ENAMETOOLONG":38,"ENETDOWN":116,"ENETRESET":117,"ENETUNREACH":118,"ENFILE":23,"ENOBUFS":119,"ENODATA":120,"ENODEV":19,"ENOENT":2,"ENOEXEC":8,"ENOLCK":39,"ENOLINK":121,"ENOMEM":12,"ENOMSG":122,"ENOPROTOOPT":123,"ENOSPC":28,"ENOSR":124,"ENOSTR":125,"ENOSYS":40,"ENOTCONN":126,"ENOTDIR":20,"ENOTEMPTY":41,"ENOTSOCK":128,"ENOTSUP":129,"ENOTTY":25,"ENXIO":6,"EOPNOTSUPP":130,"EOVERFLOW":132,"EPERM":1,"EPIPE":32,"EPROTO":134,"EPROTONOSUPPORT":135,"EPROTOTYPE":136,"ERANGE":34,"EROFS":30,"ESPIPE":29,"ESRCH":3,"ETIME":137,"ETIMEDOUT":138,"ETXTBSY":139,"EWOULDBLOCK":140,"EXDEV":18,"WSAEINTR":10004,"WSAEBADF":10009,"WSAEACCES":10013,"WSAEFAULT":10014,"WSAEINVAL":10022,"WSAEMFILE":10024,"WSAEWOULDBLOCK":10035,"WSAEINPROGRESS":10036,"WSAEALREADY":10037,"WSAENOTSOCK":10038,"WSAEDESTADDRREQ":10039,"WSAEMSGSIZE":10040,"WSAEPROTOTYPE":10041,"WSAENOPROTOOPT":10042,"WSAEPROTONOSUPPORT":10043,"WSAESOCKTNOSUPPORT":10044,"WSAEOPNOTSUPP":10045,"WSAEPFNOSUPPORT":10046,"WSAEAFNOSUPPORT":10047,"WSAEADDRINUSE":10048,"WSAEADDRNOTAVAIL":10049,"WSAENETDOWN":10050,"WSAENETUNREACH":10051,"WSAENETRESET":10052,"WSAECONNABORTED":10053,"WSAECONNRESET":10054,"WSAENOBUFS":10055,"WSAEISCONN":10056,"WSAENOTCONN":10057,"WSAESHUTDOWN":10058,"WSAETOOMANYREFS":10059,"WSAETIMEDOUT":10060,"WSAECONNREFUSED":10061,"WSAELOOP":10062,"WSAENAMETOOLONG":10063,"WSAEHOSTDOWN":10064,"WSAEHOSTUNREACH":10065,"WSAENOTEMPTY":10066,"WSAEPROCLIM":10067,"WSAEUSERS":10068,"WSAEDQUOT":10069,"WSAESTALE":10070,"WSAEREMOTE":10071,"WSASYSNOTREADY":10091,"WSAVERNOTSUPPORTED":10092,"WSANOTINITIALISED":10093,"WSAEDISCON":10101,"WSAENOMORE":10102,"WSAECANCELLED":10103,"WSAEINVALIDPROCTABLE":10104,"WSAEINVALIDPROVIDER":10105,"WSAEPROVIDERFAILEDINIT":10106,"WSASYSCALLFAILURE":10107,"WSASERVICE_NOT_FOUND":10108,"WSATYPE_NOT_FOUND":10109,"WSA_E_NO_MORE":10110,"WSA_E_CANCELLED":10111,"WSAEREFUSED":10112},"signals":{"SIGHUP":1,"SIGINT":2,"SIGILL":4,"SIGABRT":22,"SIGFPE":8,"SIGKILL":9,"SIGSEGV":11,"SIGTERM":15,"SIGBREAK":21,"SIGWINCH":28},"priority":{"PRIORITY_LOW":19,"PRIORITY_BELOW_NORMAL":10,"PRIORITY_NORMAL":0,"PRIORITY_ABOVE_NORMAL":-7,"PRIORITY_HIGH":-14,"PRIORITY_HIGHEST":-20}}"#; +const CRYPTO_JSON: &str = r#"{"OPENSSL_VERSION_NUMBER":269488319,"SSL_OP_ALL":2147485780,"SSL_OP_ALLOW_NO_DHE_KEX":1024,"SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION":262144,"SSL_OP_CIPHER_SERVER_PREFERENCE":4194304,"SSL_OP_CISCO_ANYCONNECT":32768,"SSL_OP_COOKIE_EXCHANGE":8192,"SSL_OP_CRYPTOPRO_TLSEXT_BUG":2147483648,"SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS":2048,"SSL_OP_EPHEMERAL_RSA":0,"SSL_OP_LEGACY_SERVER_CONNECT":4,"SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER":0,"SSL_OP_MICROSOFT_SESS_ID_BUG":0,"SSL_OP_MSIE_SSLV2_RSA_PADDING":0,"SSL_OP_NETSCAPE_CA_DN_BUG":0,"SSL_OP_NETSCAPE_CHALLENGE_BUG":0,"SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG":0,"SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG":0,"SSL_OP_NO_COMPRESSION":131072,"SSL_OP_NO_ENCRYPT_THEN_MAC":524288,"SSL_OP_NO_QUERY_MTU":4096,"SSL_OP_NO_RENEGOTIATION":1073741824,"SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION":65536,"SSL_OP_NO_SSLv2":0,"SSL_OP_NO_SSLv3":33554432,"SSL_OP_NO_TICKET":16384,"SSL_OP_NO_TLSv1":67108864,"SSL_OP_NO_TLSv1_1":268435456,"SSL_OP_NO_TLSv1_2":134217728,"SSL_OP_NO_TLSv1_3":536870912,"SSL_OP_PKCS1_CHECK_1":0,"SSL_OP_PKCS1_CHECK_2":0,"SSL_OP_PRIORITIZE_CHACHA":2097152,"SSL_OP_SINGLE_DH_USE":0,"SSL_OP_SINGLE_ECDH_USE":0,"SSL_OP_SSLEAY_080_CLIENT_DH_BUG":0,"SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG":0,"SSL_OP_TLS_BLOCK_PADDING_BUG":0,"SSL_OP_TLS_D5_BUG":0,"SSL_OP_TLS_ROLLBACK_BUG":8388608,"ENGINE_METHOD_RSA":1,"ENGINE_METHOD_DSA":2,"ENGINE_METHOD_DH":4,"ENGINE_METHOD_RAND":8,"ENGINE_METHOD_EC":2048,"ENGINE_METHOD_CIPHERS":64,"ENGINE_METHOD_DIGESTS":128,"ENGINE_METHOD_PKEY_METHS":512,"ENGINE_METHOD_PKEY_ASN1_METHS":1024,"ENGINE_METHOD_ALL":65535,"ENGINE_METHOD_NONE":0,"DH_CHECK_P_NOT_SAFE_PRIME":2,"DH_CHECK_P_NOT_PRIME":1,"DH_UNABLE_TO_CHECK_GENERATOR":4,"DH_NOT_SUITABLE_GENERATOR":8,"ALPN_ENABLED":1,"RSA_PKCS1_PADDING":1,"RSA_SSLV23_PADDING":2,"RSA_NO_PADDING":3,"RSA_PKCS1_OAEP_PADDING":4,"RSA_X931_PADDING":5,"RSA_PKCS1_PSS_PADDING":6,"RSA_PSS_SALTLEN_DIGEST":-1,"RSA_PSS_SALTLEN_MAX_SIGN":-2,"RSA_PSS_SALTLEN_AUTO":-2,"defaultCoreCipherList":"TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA","TLS1_VERSION":769,"TLS1_1_VERSION":770,"TLS1_2_VERSION":771,"TLS1_3_VERSION":772,"POINT_CONVERSION_COMPRESSED":2,"POINT_CONVERSION_UNCOMPRESSED":4,"POINT_CONVERSION_HYBRID":6}"#; +const ZLIB_JSON: &str = r#"{"Z_NO_FLUSH":0,"Z_PARTIAL_FLUSH":1,"Z_SYNC_FLUSH":2,"Z_FULL_FLUSH":3,"Z_FINISH":4,"Z_BLOCK":5,"Z_OK":0,"Z_STREAM_END":1,"Z_NEED_DICT":2,"Z_ERRNO":-1,"Z_STREAM_ERROR":-2,"Z_DATA_ERROR":-3,"Z_MEM_ERROR":-4,"Z_BUF_ERROR":-5,"Z_VERSION_ERROR":-6,"Z_NO_COMPRESSION":0,"Z_BEST_SPEED":1,"Z_BEST_COMPRESSION":9,"Z_DEFAULT_COMPRESSION":-1,"Z_FILTERED":1,"Z_HUFFMAN_ONLY":2,"Z_RLE":3,"Z_FIXED":4,"Z_DEFAULT_STRATEGY":0,"ZLIB_VERNUM":4784,"DEFLATE":1,"INFLATE":2,"GZIP":3,"GUNZIP":4,"DEFLATERAW":5,"INFLATERAW":6,"UNZIP":7,"BROTLI_DECODE":8,"BROTLI_ENCODE":9,"ZSTD_COMPRESS":10,"ZSTD_DECOMPRESS":11,"Z_MIN_WINDOWBITS":8,"Z_MAX_WINDOWBITS":15,"Z_DEFAULT_WINDOWBITS":15,"Z_MIN_CHUNK":64,"Z_MAX_CHUNK":null,"Z_DEFAULT_CHUNK":16384,"Z_MIN_MEMLEVEL":1,"Z_MAX_MEMLEVEL":9,"Z_DEFAULT_MEMLEVEL":8,"Z_MIN_LEVEL":-1,"Z_MAX_LEVEL":9,"Z_DEFAULT_LEVEL":-1,"BROTLI_OPERATION_PROCESS":0,"BROTLI_OPERATION_FLUSH":1,"BROTLI_OPERATION_FINISH":2,"BROTLI_OPERATION_EMIT_METADATA":3,"BROTLI_PARAM_MODE":0,"BROTLI_MODE_GENERIC":0,"BROTLI_MODE_TEXT":1,"BROTLI_MODE_FONT":2,"BROTLI_DEFAULT_MODE":0,"BROTLI_PARAM_QUALITY":1,"BROTLI_MIN_QUALITY":0,"BROTLI_MAX_QUALITY":11,"BROTLI_DEFAULT_QUALITY":11,"BROTLI_PARAM_LGWIN":2,"BROTLI_MIN_WINDOW_BITS":10,"BROTLI_MAX_WINDOW_BITS":24,"BROTLI_LARGE_MAX_WINDOW_BITS":30,"BROTLI_DEFAULT_WINDOW":22,"BROTLI_PARAM_LGBLOCK":3,"BROTLI_MIN_INPUT_BLOCK_BITS":16,"BROTLI_MAX_INPUT_BLOCK_BITS":24,"BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING":4,"BROTLI_PARAM_SIZE_HINT":5,"BROTLI_PARAM_LARGE_WINDOW":6,"BROTLI_PARAM_NPOSTFIX":7,"BROTLI_PARAM_NDIRECT":8,"BROTLI_DECODER_RESULT_ERROR":0,"BROTLI_DECODER_RESULT_SUCCESS":1,"BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT":2,"BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT":3,"BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION":0,"BROTLI_DECODER_PARAM_LARGE_WINDOW":1,"BROTLI_DECODER_NO_ERROR":0,"BROTLI_DECODER_SUCCESS":1,"BROTLI_DECODER_NEEDS_MORE_INPUT":2,"BROTLI_DECODER_NEEDS_MORE_OUTPUT":3,"BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE":-1,"BROTLI_DECODER_ERROR_FORMAT_RESERVED":-2,"BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE":-3,"BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET":-4,"BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME":-5,"BROTLI_DECODER_ERROR_FORMAT_CL_SPACE":-6,"BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE":-7,"BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT":-8,"BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1":-9,"BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2":-10,"BROTLI_DECODER_ERROR_FORMAT_TRANSFORM":-11,"BROTLI_DECODER_ERROR_FORMAT_DICTIONARY":-12,"BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS":-13,"BROTLI_DECODER_ERROR_FORMAT_PADDING_1":-14,"BROTLI_DECODER_ERROR_FORMAT_PADDING_2":-15,"BROTLI_DECODER_ERROR_FORMAT_DISTANCE":-16,"BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET":-19,"BROTLI_DECODER_ERROR_INVALID_ARGUMENTS":-20,"BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES":-21,"BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS":-22,"BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP":-25,"BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1":-26,"BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2":-27,"BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES":-30,"BROTLI_DECODER_ERROR_UNREACHABLE":-31,"ZSTD_e_continue":0,"ZSTD_e_flush":1,"ZSTD_e_end":2,"ZSTD_c_compressionLevel":100,"ZSTD_c_windowLog":101,"ZSTD_c_hashLog":102,"ZSTD_c_chainLog":103,"ZSTD_c_searchLog":104,"ZSTD_c_minMatch":105,"ZSTD_c_targetLength":106,"ZSTD_c_strategy":107,"ZSTD_c_enableLongDistanceMatching":160,"ZSTD_c_ldmHashLog":161,"ZSTD_c_ldmMinMatch":162,"ZSTD_c_ldmBucketSizeLog":163,"ZSTD_c_ldmHashRateLog":164,"ZSTD_c_contentSizeFlag":200,"ZSTD_c_checksumFlag":201,"ZSTD_c_dictIDFlag":202,"ZSTD_c_nbWorkers":240,"ZSTD_c_jobSize":241,"ZSTD_c_overlapLog":242,"ZSTD_d_windowLogMax":100,"ZSTD_fast":1,"ZSTD_dfast":2,"ZSTD_greedy":3,"ZSTD_lazy":4,"ZSTD_lazy2":5,"ZSTD_btlazy2":6,"ZSTD_btopt":7,"ZSTD_btultra":8,"ZSTD_btultra2":9}"#; +const TRACE_JSON: &str = r#"{"TRACE_EVENT_PHASE_BEGIN":66,"TRACE_EVENT_PHASE_END":69,"TRACE_EVENT_PHASE_COMPLETE":88,"TRACE_EVENT_PHASE_INSTANT":73,"TRACE_EVENT_PHASE_ASYNC_BEGIN":83,"TRACE_EVENT_PHASE_ASYNC_STEP_INTO":84,"TRACE_EVENT_PHASE_ASYNC_STEP_PAST":112,"TRACE_EVENT_PHASE_ASYNC_END":70,"TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN":98,"TRACE_EVENT_PHASE_NESTABLE_ASYNC_END":101,"TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT":110,"TRACE_EVENT_PHASE_FLOW_BEGIN":115,"TRACE_EVENT_PHASE_FLOW_STEP":116,"TRACE_EVENT_PHASE_FLOW_END":102,"TRACE_EVENT_PHASE_METADATA":77,"TRACE_EVENT_PHASE_COUNTER":67,"TRACE_EVENT_PHASE_SAMPLE":80,"TRACE_EVENT_PHASE_CREATE_OBJECT":78,"TRACE_EVENT_PHASE_SNAPSHOT_OBJECT":79,"TRACE_EVENT_PHASE_DELETE_OBJECT":68,"TRACE_EVENT_PHASE_MEMORY_DUMP":118,"TRACE_EVENT_PHASE_MARK":82,"TRACE_EVENT_PHASE_CLOCK_SYNC":99,"TRACE_EVENT_PHASE_ENTER_CONTEXT":40,"TRACE_EVENT_PHASE_LEAVE_CONTEXT":41,"TRACE_EVENT_PHASE_LINK_IDS":61}"#; + +fn parse_json_obj<'s>( + scope: &mut v8::PinScope<'s, '_>, + json: &str, +) -> v8::Local<'s, v8::Object> { + let source = v8::String::new(scope, json).unwrap(); + let value = v8::json::parse(scope, source).unwrap(); + v8::Local::::try_from(value).unwrap() +} + +fn set_value( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: v8::Local, +) { + let key = v8::String::new(scope, name).unwrap(); + obj.set(scope, key.into(), value); +} + +fn freeze_property_object( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, +) { + let key = v8::String::new(scope, name).unwrap(); + let value = obj.get(scope, key.into()).unwrap(); + let value = v8::Local::::try_from(value).unwrap(); + value + .set_integrity_level(scope, v8::IntegrityLevel::Frozen) + .unwrap(); +} + +fn set_infinity( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, +) { + let key = v8::String::new(scope, name).unwrap(); + let value = v8::Number::new(scope, f64::INFINITY); + obj.set(scope, key.into(), value.into()); +} + +#[op2] +pub fn op_node_internal_binding_constants<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + + let os_json = if cfg!(target_os = "macos") { + OS_DARWIN_JSON + } else if cfg!(any(target_os = "linux", target_os = "android")) { + OS_LINUX_JSON + } else { + OS_WINDOWS_JSON + }; + let os = parse_json_obj(scope, os_json); + freeze_property_object(scope, os, "signals"); + set_value(scope, obj, "os", os.into()); + + let fs = + serde_v8::to_v8(scope, crate::ops::constant::node_fs_constants()).unwrap(); + set_value(scope, obj, "fs", fs); + + let crypto = parse_json_obj(scope, CRYPTO_JSON); + set_value(scope, obj, "crypto", crypto.into()); + + let zlib = parse_json_obj(scope, ZLIB_JSON); + set_infinity(scope, zlib, "Z_MAX_CHUNK"); + set_value(scope, obj, "zlib", zlib.into()); + + let trace = parse_json_obj(scope, TRACE_JSON); + set_value(scope, obj, "trace", trace.into()); + + obj +} diff --git a/ext/node/ops/internal_binding_crypto.rs b/ext/node/ops/internal_binding_crypto.rs new file mode 100644 index 00000000000000..eefbc69dcb50b0 --- /dev/null +++ b/ext/node/ops/internal_binding_crypto.rs @@ -0,0 +1,285 @@ +// Copyright 2018-2026 the Deno authors. MIT license. + +use deno_core::op2; +use deno_core::v8; +use deno_core::v8::ExternalReference; +use deno_core::v8::MapFnTo; + +#[derive(Debug, thiserror::Error, deno_error::JsError)] +enum CryptoBindingError { + #[error("{0}")] + #[class(type)] + InvalidArgType(String), + #[error("Input buffers must have the same byte length")] + #[class(range)] + #[property("code" = "ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH")] + TimingSafeEqualLength, + #[error("FIPS mode is not supported in Deno.")] + #[class(generic)] + FipsUnsupported, +} + +fn set_value( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: v8::Local, +) { + let key = v8::String::new(scope, name).unwrap(); + obj.set(scope, key.into(), value); +} + +fn set_function( + scope: &mut v8::PinScope, + obj: v8::Local, + export_name: &str, + function: v8::Local, +) { + let name = v8::String::new(scope, export_name).unwrap(); + function.set_name(name); + set_value(scope, obj, export_name, function.into()); +} + +fn received_type( + scope: &mut v8::PinScope, + value: v8::Local, +) -> String { + if value.is_null() { + return "Received null".to_string(); + } + if value.is_undefined() { + return "Received undefined".to_string(); + } + if value.is_string() { + let value = value.to_string(scope).unwrap().to_rust_string_lossy(scope); + return format!("Received type string ('{}')", value.replace('\'', "\\'")); + } + if value.is_number() { + let value = value.to_string(scope).unwrap().to_rust_string_lossy(scope); + return format!("Received type number ({value})"); + } + if value.is_boolean() { + let value = if value.is_true() { "true" } else { "false" }; + return format!("Received type boolean ({value})"); + } + if value.is_big_int() { + let value = value.to_string(scope).unwrap().to_rust_string_lossy(scope); + return format!("Received type bigint ({value})"); + } + if value.is_symbol() { + return "Received type symbol".to_string(); + } + if value.is_function() { + return "Received function".to_string(); + } + "Received an instance of Object".to_string() +} + +fn invalid_buffer_arg( + scope: &mut v8::PinScope, + name: &str, + value: v8::Local, +) -> CryptoBindingError { + CryptoBindingError::InvalidArgType(format!( + "The \"{name}\" argument must be an instance of Buffer, ArrayBuffer, TypedArray, or DataView. {}", + received_type(scope, value) + )) +} + +fn array_buffer_bytes( + value: v8::Local, +) -> Result, CryptoBindingError> { + let length = value.byte_length(); + let mut bytes = vec![0; length]; + if length == 0 { + return Ok(bytes); + } + if let Some(data) = value.data() { + // SAFETY: V8 owns the ArrayBuffer backing store and the byte length came + // from the same ArrayBuffer. The copy is bounded by that byte length. + let slice = + unsafe { std::slice::from_raw_parts(data.as_ptr().cast::(), length) }; + bytes.copy_from_slice(slice); + } + Ok(bytes) +} + +fn shared_array_buffer_bytes( + value: v8::Local, +) -> Result, CryptoBindingError> { + let length = value.byte_length(); + let mut bytes = vec![0; length]; + if length == 0 { + return Ok(bytes); + } + let backing_store = value.get_backing_store(); + if let Some(data) = backing_store.data() { + // SAFETY: V8 owns the SharedArrayBuffer backing store and the byte length + // came from the same SharedArrayBuffer. + let slice = + unsafe { std::slice::from_raw_parts(data.as_ptr().cast::(), length) }; + bytes.copy_from_slice(slice); + } + Ok(bytes) +} + +fn array_buffer_view_bytes(value: v8::Local) -> Vec { + let mut bytes = vec![0; value.byte_length()]; + let copied = value.copy_contents(&mut bytes); + debug_assert_eq!(copied, bytes.len()); + bytes +} + +fn buffer_source_bytes( + scope: &mut v8::PinScope, + value: v8::Local, + name: &str, +) -> Result, CryptoBindingError> { + if value.is_array_buffer_view() { + let value = v8::Local::::try_from(value).unwrap(); + return Ok(array_buffer_view_bytes(value)); + } + if value.is_array_buffer() { + let value = v8::Local::::try_from(value).unwrap(); + return array_buffer_bytes(value); + } + if value.is_shared_array_buffer() { + let value = v8::Local::::try_from(value).unwrap(); + return shared_array_buffer_bytes(value); + } + Err(invalid_buffer_arg(scope, name, value)) +} + +fn timing_safe_equal_impl( + scope: &mut v8::PinScope<'_, '_>, + buf1: v8::Local, + buf2: v8::Local, +) -> Result { + let buf1 = buffer_source_bytes(scope, buf1, "buf1")?; + let buf2 = buffer_source_bytes(scope, buf2, "buf2")?; + if buf1.len() != buf2.len() { + return Err(CryptoBindingError::TimingSafeEqualLength); + } + let mut out = 0; + for i in 0..buf1.len() { + out |= buf1[i] ^ buf2[i]; + } + Ok(out == 0) +} + +fn throw_crypto_error(scope: &mut v8::PinScope, error: CryptoBindingError) { + let message = v8::String::new(scope, &error.to_string()).unwrap(); + let exception = match error { + CryptoBindingError::InvalidArgType(_) => { + v8::Exception::type_error(scope, message) + } + CryptoBindingError::TimingSafeEqualLength => { + v8::Exception::range_error(scope, message) + } + CryptoBindingError::FipsUnsupported => v8::Exception::error(scope, message), + }; + scope.throw_exception(exception); +} + +fn timing_safe_equal_callback( + scope: &mut v8::PinScope, + args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + let buf1 = args.get(0); + let buf2 = args.get(1); + match timing_safe_equal_impl(scope, buf1, buf2) { + Ok(equal) => rv.set_bool(equal), + Err(error) => throw_crypto_error(scope, error), + } +} + +fn get_fips_callback( + _scope: &mut v8::PinScope, + _args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + rv.set_bool(false); +} + +fn set_fips_callback( + scope: &mut v8::PinScope, + _args: v8::FunctionCallbackArguments, + _rv: v8::ReturnValue, +) { + throw_crypto_error(scope, CryptoBindingError::FipsUnsupported); +} + +pub(crate) fn external_references() -> [ExternalReference; 3] { + [ + ExternalReference { + function: timing_safe_equal_callback.map_fn_to(), + }, + ExternalReference { + function: get_fips_callback.map_fn_to(), + }, + ExternalReference { + function: set_fips_callback.map_fn_to(), + }, + ] +} + +#[op2(fast)] +pub fn op_node_crypto_timing_safe_equal( + scope: &mut v8::PinScope<'_, '_>, + buf1: v8::Local, + buf2: v8::Local, +) -> Result { + timing_safe_equal_impl(scope, buf1, buf2) +} + +#[op2(fast)] +pub fn op_node_crypto_get_fips() -> bool { + false +} + +#[op2(fast)] +pub fn op_node_crypto_set_fips() -> Result<(), CryptoBindingError> { + Err(CryptoBindingError::FipsUnsupported) +} + +#[op2] +pub fn op_node_internal_binding_crypto<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + let timing_safe_equal = + v8::FunctionTemplate::new(scope, timing_safe_equal_callback) + .get_function(scope) + .unwrap(); + set_function(scope, obj, "timingSafeEqual", timing_safe_equal); + let get_fips = v8::FunctionTemplate::new(scope, get_fips_callback) + .get_function(scope) + .unwrap(); + set_function(scope, obj, "getFipsCrypto", get_fips); + let set_fips = v8::FunctionTemplate::new(scope, set_fips_callback) + .get_function(scope) + .unwrap(); + set_function(scope, obj, "setFipsCrypto", set_fips); + obj +} + +#[op2] +pub fn op_node_internal_binding_timing_safe_equal<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + let timing_safe_equal = + v8::FunctionTemplate::new(scope, timing_safe_equal_callback) + .get_function(scope) + .unwrap(); + set_function(scope, obj, "timingSafeEqual", timing_safe_equal); + let default_obj = v8::Object::new(scope); + let timing_safe_equal = + v8::FunctionTemplate::new(scope, timing_safe_equal_callback) + .get_function(scope) + .unwrap(); + set_function(scope, default_obj, "timingSafeEqual", timing_safe_equal); + set_value(scope, obj, "default", default_obj.into()); + obj +} diff --git a/ext/node/ops/internal_binding_uv.rs b/ext/node/ops/internal_binding_uv.rs new file mode 100644 index 00000000000000..2326bda8d95165 --- /dev/null +++ b/ext/node/ops/internal_binding_uv.rs @@ -0,0 +1,2243 @@ +// Copyright 2018-2026 the Deno authors. MIT license. + +use deno_core::op2; +use deno_core::v8; + +use crate::ops::winerror::node_sys_to_uv_error; + +struct UvError { + code: i32, + name: &'static str, + message: &'static str, +} + +const CODE_TO_ERROR_WINDOWS: &[UvError] = &[ + UvError { + code: -4093, + name: "E2BIG", + message: "argument list too long", + }, + UvError { + code: -4092, + name: "EACCES", + message: "permission denied", + }, + UvError { + code: -4091, + name: "EADDRINUSE", + message: "address already in use", + }, + UvError { + code: -4090, + name: "EADDRNOTAVAIL", + message: "address not available", + }, + UvError { + code: -4089, + name: "EAFNOSUPPORT", + message: "address family not supported", + }, + UvError { + code: -4088, + name: "EAGAIN", + message: "resource temporarily unavailable", + }, + UvError { + code: -3000, + name: "EAI_ADDRFAMILY", + message: "address family not supported", + }, + UvError { + code: -3001, + name: "EAI_AGAIN", + message: "temporary failure", + }, + UvError { + code: -3002, + name: "EAI_BADFLAGS", + message: "bad ai_flags value", + }, + UvError { + code: -3013, + name: "EAI_BADHINTS", + message: "invalid value for hints", + }, + UvError { + code: -3003, + name: "EAI_CANCELED", + message: "request canceled", + }, + UvError { + code: -3004, + name: "EAI_FAIL", + message: "permanent failure", + }, + UvError { + code: -3005, + name: "EAI_FAMILY", + message: "ai_family not supported", + }, + UvError { + code: -3006, + name: "EAI_MEMORY", + message: "out of memory", + }, + UvError { + code: -3007, + name: "EAI_NODATA", + message: "no address", + }, + UvError { + code: -3008, + name: "EAI_NONAME", + message: "unknown node or service", + }, + UvError { + code: -3009, + name: "EAI_OVERFLOW", + message: "argument buffer overflow", + }, + UvError { + code: -3014, + name: "EAI_PROTOCOL", + message: "resolved protocol is unknown", + }, + UvError { + code: -3010, + name: "EAI_SERVICE", + message: "service not available for socket type", + }, + UvError { + code: -3011, + name: "EAI_SOCKTYPE", + message: "socket type not supported", + }, + UvError { + code: -4084, + name: "EALREADY", + message: "connection already in progress", + }, + UvError { + code: -4083, + name: "EBADF", + message: "bad file descriptor", + }, + UvError { + code: -4082, + name: "EBUSY", + message: "resource busy or locked", + }, + UvError { + code: -4081, + name: "ECANCELED", + message: "operation canceled", + }, + UvError { + code: -4080, + name: "ECHARSET", + message: "invalid Unicode character", + }, + UvError { + code: -4079, + name: "ECONNABORTED", + message: "software caused connection abort", + }, + UvError { + code: -4078, + name: "ECONNREFUSED", + message: "connection refused", + }, + UvError { + code: -4077, + name: "ECONNRESET", + message: "connection reset by peer", + }, + UvError { + code: -4076, + name: "EDESTADDRREQ", + message: "destination address required", + }, + UvError { + code: -4075, + name: "EEXIST", + message: "file already exists", + }, + UvError { + code: -4074, + name: "EFAULT", + message: "bad address in system call argument", + }, + UvError { + code: -4036, + name: "EFBIG", + message: "file too large", + }, + UvError { + code: -4073, + name: "EHOSTUNREACH", + message: "host is unreachable", + }, + UvError { + code: -4072, + name: "EINTR", + message: "interrupted system call", + }, + UvError { + code: -4071, + name: "EINVAL", + message: "invalid argument", + }, + UvError { + code: -4070, + name: "EIO", + message: "i/o error", + }, + UvError { + code: -4069, + name: "EISCONN", + message: "socket is already connected", + }, + UvError { + code: -4068, + name: "EISDIR", + message: "illegal operation on a directory", + }, + UvError { + code: -4067, + name: "ELOOP", + message: "too many symbolic links encountered", + }, + UvError { + code: -4066, + name: "EMFILE", + message: "too many open files", + }, + UvError { + code: -4065, + name: "EMSGSIZE", + message: "message too long", + }, + UvError { + code: -4064, + name: "ENAMETOOLONG", + message: "name too long", + }, + UvError { + code: -4063, + name: "ENETDOWN", + message: "network is down", + }, + UvError { + code: -4062, + name: "ENETUNREACH", + message: "network is unreachable", + }, + UvError { + code: -4061, + name: "ENFILE", + message: "file table overflow", + }, + UvError { + code: -4060, + name: "ENOBUFS", + message: "no buffer space available", + }, + UvError { + code: -4059, + name: "ENODEV", + message: "no such device", + }, + UvError { + code: -4058, + name: "ENOENT", + message: "no such file or directory", + }, + UvError { + code: -4057, + name: "ENOMEM", + message: "not enough memory", + }, + UvError { + code: -4056, + name: "ENONET", + message: "machine is not on the network", + }, + UvError { + code: -4035, + name: "ENOPROTOOPT", + message: "protocol not available", + }, + UvError { + code: -4055, + name: "ENOSPC", + message: "no space left on device", + }, + UvError { + code: -4054, + name: "ENOSYS", + message: "function not implemented", + }, + UvError { + code: -4053, + name: "ENOTCONN", + message: "socket is not connected", + }, + UvError { + code: -4052, + name: "ENOTDIR", + message: "not a directory", + }, + UvError { + code: -4051, + name: "ENOTEMPTY", + message: "directory not empty", + }, + UvError { + code: -4050, + name: "ENOTSOCK", + message: "socket operation on non-socket", + }, + UvError { + code: -4049, + name: "ENOTSUP", + message: "operation not supported on socket", + }, + UvError { + code: -4048, + name: "EPERM", + message: "operation not permitted", + }, + UvError { + code: -4047, + name: "EPIPE", + message: "broken pipe", + }, + UvError { + code: -4046, + name: "EPROTO", + message: "protocol error", + }, + UvError { + code: -4045, + name: "EPROTONOSUPPORT", + message: "protocol not supported", + }, + UvError { + code: -4044, + name: "EPROTOTYPE", + message: "protocol wrong type for socket", + }, + UvError { + code: -4034, + name: "ERANGE", + message: "result too large", + }, + UvError { + code: -4043, + name: "EROFS", + message: "read-only file system", + }, + UvError { + code: -4042, + name: "ESHUTDOWN", + message: "cannot send after transport endpoint shutdown", + }, + UvError { + code: -4041, + name: "ESPIPE", + message: "invalid seek", + }, + UvError { + code: -4040, + name: "ESRCH", + message: "no such process", + }, + UvError { + code: -4039, + name: "ETIMEDOUT", + message: "connection timed out", + }, + UvError { + code: -4038, + name: "ETXTBSY", + message: "text file is busy", + }, + UvError { + code: -4037, + name: "EXDEV", + message: "cross-device link not permitted", + }, + UvError { + code: -4094, + name: "UNKNOWN", + message: "unknown error", + }, + UvError { + code: -4095, + name: "EOF", + message: "end of file", + }, + UvError { + code: -4033, + name: "ENXIO", + message: "no such device or address", + }, + UvError { + code: -4032, + name: "EMLINK", + message: "too many links", + }, + UvError { + code: -4031, + name: "EHOSTDOWN", + message: "host is down", + }, + UvError { + code: -4030, + name: "EREMOTEIO", + message: "remote I/O error", + }, + UvError { + code: -4029, + name: "ENOTTY", + message: "inappropriate ioctl for device", + }, + UvError { + code: -4028, + name: "EFTYPE", + message: "inappropriate file type or format", + }, + UvError { + code: -4027, + name: "EILSEQ", + message: "illegal byte sequence", + }, +]; +const CODE_TO_ERROR_DARWIN: &[UvError] = &[ + UvError { + code: -7, + name: "E2BIG", + message: "argument list too long", + }, + UvError { + code: -13, + name: "EACCES", + message: "permission denied", + }, + UvError { + code: -48, + name: "EADDRINUSE", + message: "address already in use", + }, + UvError { + code: -49, + name: "EADDRNOTAVAIL", + message: "address not available", + }, + UvError { + code: -47, + name: "EAFNOSUPPORT", + message: "address family not supported", + }, + UvError { + code: -35, + name: "EAGAIN", + message: "resource temporarily unavailable", + }, + UvError { + code: -3000, + name: "EAI_ADDRFAMILY", + message: "address family not supported", + }, + UvError { + code: -3001, + name: "EAI_AGAIN", + message: "temporary failure", + }, + UvError { + code: -3002, + name: "EAI_BADFLAGS", + message: "bad ai_flags value", + }, + UvError { + code: -3013, + name: "EAI_BADHINTS", + message: "invalid value for hints", + }, + UvError { + code: -3003, + name: "EAI_CANCELED", + message: "request canceled", + }, + UvError { + code: -3004, + name: "EAI_FAIL", + message: "permanent failure", + }, + UvError { + code: -3005, + name: "EAI_FAMILY", + message: "ai_family not supported", + }, + UvError { + code: -3006, + name: "EAI_MEMORY", + message: "out of memory", + }, + UvError { + code: -3007, + name: "EAI_NODATA", + message: "no address", + }, + UvError { + code: -3008, + name: "EAI_NONAME", + message: "unknown node or service", + }, + UvError { + code: -3009, + name: "EAI_OVERFLOW", + message: "argument buffer overflow", + }, + UvError { + code: -3014, + name: "EAI_PROTOCOL", + message: "resolved protocol is unknown", + }, + UvError { + code: -3010, + name: "EAI_SERVICE", + message: "service not available for socket type", + }, + UvError { + code: -3011, + name: "EAI_SOCKTYPE", + message: "socket type not supported", + }, + UvError { + code: -37, + name: "EALREADY", + message: "connection already in progress", + }, + UvError { + code: -9, + name: "EBADF", + message: "bad file descriptor", + }, + UvError { + code: -16, + name: "EBUSY", + message: "resource busy or locked", + }, + UvError { + code: -89, + name: "ECANCELED", + message: "operation canceled", + }, + UvError { + code: -4080, + name: "ECHARSET", + message: "invalid Unicode character", + }, + UvError { + code: -53, + name: "ECONNABORTED", + message: "software caused connection abort", + }, + UvError { + code: -61, + name: "ECONNREFUSED", + message: "connection refused", + }, + UvError { + code: -54, + name: "ECONNRESET", + message: "connection reset by peer", + }, + UvError { + code: -39, + name: "EDESTADDRREQ", + message: "destination address required", + }, + UvError { + code: -17, + name: "EEXIST", + message: "file already exists", + }, + UvError { + code: -14, + name: "EFAULT", + message: "bad address in system call argument", + }, + UvError { + code: -27, + name: "EFBIG", + message: "file too large", + }, + UvError { + code: -65, + name: "EHOSTUNREACH", + message: "host is unreachable", + }, + UvError { + code: -4, + name: "EINTR", + message: "interrupted system call", + }, + UvError { + code: -22, + name: "EINVAL", + message: "invalid argument", + }, + UvError { + code: -5, + name: "EIO", + message: "i/o error", + }, + UvError { + code: -56, + name: "EISCONN", + message: "socket is already connected", + }, + UvError { + code: -21, + name: "EISDIR", + message: "illegal operation on a directory", + }, + UvError { + code: -62, + name: "ELOOP", + message: "too many symbolic links encountered", + }, + UvError { + code: -24, + name: "EMFILE", + message: "too many open files", + }, + UvError { + code: -40, + name: "EMSGSIZE", + message: "message too long", + }, + UvError { + code: -63, + name: "ENAMETOOLONG", + message: "name too long", + }, + UvError { + code: -50, + name: "ENETDOWN", + message: "network is down", + }, + UvError { + code: -51, + name: "ENETUNREACH", + message: "network is unreachable", + }, + UvError { + code: -23, + name: "ENFILE", + message: "file table overflow", + }, + UvError { + code: -55, + name: "ENOBUFS", + message: "no buffer space available", + }, + UvError { + code: -19, + name: "ENODEV", + message: "no such device", + }, + UvError { + code: -2, + name: "ENOENT", + message: "no such file or directory", + }, + UvError { + code: -12, + name: "ENOMEM", + message: "not enough memory", + }, + UvError { + code: -4056, + name: "ENONET", + message: "machine is not on the network", + }, + UvError { + code: -42, + name: "ENOPROTOOPT", + message: "protocol not available", + }, + UvError { + code: -28, + name: "ENOSPC", + message: "no space left on device", + }, + UvError { + code: -78, + name: "ENOSYS", + message: "function not implemented", + }, + UvError { + code: -57, + name: "ENOTCONN", + message: "socket is not connected", + }, + UvError { + code: -20, + name: "ENOTDIR", + message: "not a directory", + }, + UvError { + code: -66, + name: "ENOTEMPTY", + message: "directory not empty", + }, + UvError { + code: -38, + name: "ENOTSOCK", + message: "socket operation on non-socket", + }, + UvError { + code: -45, + name: "ENOTSUP", + message: "operation not supported on socket", + }, + UvError { + code: -1, + name: "EPERM", + message: "operation not permitted", + }, + UvError { + code: -32, + name: "EPIPE", + message: "broken pipe", + }, + UvError { + code: -100, + name: "EPROTO", + message: "protocol error", + }, + UvError { + code: -43, + name: "EPROTONOSUPPORT", + message: "protocol not supported", + }, + UvError { + code: -41, + name: "EPROTOTYPE", + message: "protocol wrong type for socket", + }, + UvError { + code: -34, + name: "ERANGE", + message: "result too large", + }, + UvError { + code: -30, + name: "EROFS", + message: "read-only file system", + }, + UvError { + code: -58, + name: "ESHUTDOWN", + message: "cannot send after transport endpoint shutdown", + }, + UvError { + code: -29, + name: "ESPIPE", + message: "invalid seek", + }, + UvError { + code: -3, + name: "ESRCH", + message: "no such process", + }, + UvError { + code: -60, + name: "ETIMEDOUT", + message: "connection timed out", + }, + UvError { + code: -26, + name: "ETXTBSY", + message: "text file is busy", + }, + UvError { + code: -18, + name: "EXDEV", + message: "cross-device link not permitted", + }, + UvError { + code: -4094, + name: "UNKNOWN", + message: "unknown error", + }, + UvError { + code: -4095, + name: "EOF", + message: "end of file", + }, + UvError { + code: -6, + name: "ENXIO", + message: "no such device or address", + }, + UvError { + code: -31, + name: "EMLINK", + message: "too many links", + }, + UvError { + code: -64, + name: "EHOSTDOWN", + message: "host is down", + }, + UvError { + code: -4030, + name: "EREMOTEIO", + message: "remote I/O error", + }, + UvError { + code: -25, + name: "ENOTTY", + message: "inappropriate ioctl for device", + }, + UvError { + code: -79, + name: "EFTYPE", + message: "inappropriate file type or format", + }, + UvError { + code: -92, + name: "EILSEQ", + message: "illegal byte sequence", + }, +]; +const CODE_TO_ERROR_LINUX: &[UvError] = &[ + UvError { + code: -7, + name: "E2BIG", + message: "argument list too long", + }, + UvError { + code: -13, + name: "EACCES", + message: "permission denied", + }, + UvError { + code: -98, + name: "EADDRINUSE", + message: "address already in use", + }, + UvError { + code: -99, + name: "EADDRNOTAVAIL", + message: "address not available", + }, + UvError { + code: -97, + name: "EAFNOSUPPORT", + message: "address family not supported", + }, + UvError { + code: -11, + name: "EAGAIN", + message: "resource temporarily unavailable", + }, + UvError { + code: -3000, + name: "EAI_ADDRFAMILY", + message: "address family not supported", + }, + UvError { + code: -3001, + name: "EAI_AGAIN", + message: "temporary failure", + }, + UvError { + code: -3002, + name: "EAI_BADFLAGS", + message: "bad ai_flags value", + }, + UvError { + code: -3013, + name: "EAI_BADHINTS", + message: "invalid value for hints", + }, + UvError { + code: -3003, + name: "EAI_CANCELED", + message: "request canceled", + }, + UvError { + code: -3004, + name: "EAI_FAIL", + message: "permanent failure", + }, + UvError { + code: -3005, + name: "EAI_FAMILY", + message: "ai_family not supported", + }, + UvError { + code: -3006, + name: "EAI_MEMORY", + message: "out of memory", + }, + UvError { + code: -3007, + name: "EAI_NODATA", + message: "no address", + }, + UvError { + code: -3008, + name: "EAI_NONAME", + message: "unknown node or service", + }, + UvError { + code: -3009, + name: "EAI_OVERFLOW", + message: "argument buffer overflow", + }, + UvError { + code: -3014, + name: "EAI_PROTOCOL", + message: "resolved protocol is unknown", + }, + UvError { + code: -3010, + name: "EAI_SERVICE", + message: "service not available for socket type", + }, + UvError { + code: -3011, + name: "EAI_SOCKTYPE", + message: "socket type not supported", + }, + UvError { + code: -114, + name: "EALREADY", + message: "connection already in progress", + }, + UvError { + code: -9, + name: "EBADF", + message: "bad file descriptor", + }, + UvError { + code: -16, + name: "EBUSY", + message: "resource busy or locked", + }, + UvError { + code: -125, + name: "ECANCELED", + message: "operation canceled", + }, + UvError { + code: -4080, + name: "ECHARSET", + message: "invalid Unicode character", + }, + UvError { + code: -103, + name: "ECONNABORTED", + message: "software caused connection abort", + }, + UvError { + code: -111, + name: "ECONNREFUSED", + message: "connection refused", + }, + UvError { + code: -104, + name: "ECONNRESET", + message: "connection reset by peer", + }, + UvError { + code: -89, + name: "EDESTADDRREQ", + message: "destination address required", + }, + UvError { + code: -17, + name: "EEXIST", + message: "file already exists", + }, + UvError { + code: -14, + name: "EFAULT", + message: "bad address in system call argument", + }, + UvError { + code: -27, + name: "EFBIG", + message: "file too large", + }, + UvError { + code: -113, + name: "EHOSTUNREACH", + message: "host is unreachable", + }, + UvError { + code: -4, + name: "EINTR", + message: "interrupted system call", + }, + UvError { + code: -22, + name: "EINVAL", + message: "invalid argument", + }, + UvError { + code: -5, + name: "EIO", + message: "i/o error", + }, + UvError { + code: -106, + name: "EISCONN", + message: "socket is already connected", + }, + UvError { + code: -21, + name: "EISDIR", + message: "illegal operation on a directory", + }, + UvError { + code: -40, + name: "ELOOP", + message: "too many symbolic links encountered", + }, + UvError { + code: -24, + name: "EMFILE", + message: "too many open files", + }, + UvError { + code: -90, + name: "EMSGSIZE", + message: "message too long", + }, + UvError { + code: -36, + name: "ENAMETOOLONG", + message: "name too long", + }, + UvError { + code: -100, + name: "ENETDOWN", + message: "network is down", + }, + UvError { + code: -101, + name: "ENETUNREACH", + message: "network is unreachable", + }, + UvError { + code: -23, + name: "ENFILE", + message: "file table overflow", + }, + UvError { + code: -105, + name: "ENOBUFS", + message: "no buffer space available", + }, + UvError { + code: -19, + name: "ENODEV", + message: "no such device", + }, + UvError { + code: -2, + name: "ENOENT", + message: "no such file or directory", + }, + UvError { + code: -12, + name: "ENOMEM", + message: "not enough memory", + }, + UvError { + code: -64, + name: "ENONET", + message: "machine is not on the network", + }, + UvError { + code: -92, + name: "ENOPROTOOPT", + message: "protocol not available", + }, + UvError { + code: -28, + name: "ENOSPC", + message: "no space left on device", + }, + UvError { + code: -38, + name: "ENOSYS", + message: "function not implemented", + }, + UvError { + code: -107, + name: "ENOTCONN", + message: "socket is not connected", + }, + UvError { + code: -20, + name: "ENOTDIR", + message: "not a directory", + }, + UvError { + code: -39, + name: "ENOTEMPTY", + message: "directory not empty", + }, + UvError { + code: -88, + name: "ENOTSOCK", + message: "socket operation on non-socket", + }, + UvError { + code: -95, + name: "ENOTSUP", + message: "operation not supported on socket", + }, + UvError { + code: -1, + name: "EPERM", + message: "operation not permitted", + }, + UvError { + code: -32, + name: "EPIPE", + message: "broken pipe", + }, + UvError { + code: -71, + name: "EPROTO", + message: "protocol error", + }, + UvError { + code: -93, + name: "EPROTONOSUPPORT", + message: "protocol not supported", + }, + UvError { + code: -91, + name: "EPROTOTYPE", + message: "protocol wrong type for socket", + }, + UvError { + code: -34, + name: "ERANGE", + message: "result too large", + }, + UvError { + code: -30, + name: "EROFS", + message: "read-only file system", + }, + UvError { + code: -108, + name: "ESHUTDOWN", + message: "cannot send after transport endpoint shutdown", + }, + UvError { + code: -29, + name: "ESPIPE", + message: "invalid seek", + }, + UvError { + code: -3, + name: "ESRCH", + message: "no such process", + }, + UvError { + code: -110, + name: "ETIMEDOUT", + message: "connection timed out", + }, + UvError { + code: -26, + name: "ETXTBSY", + message: "text file is busy", + }, + UvError { + code: -18, + name: "EXDEV", + message: "cross-device link not permitted", + }, + UvError { + code: -4094, + name: "UNKNOWN", + message: "unknown error", + }, + UvError { + code: -4095, + name: "EOF", + message: "end of file", + }, + UvError { + code: -6, + name: "ENXIO", + message: "no such device or address", + }, + UvError { + code: -31, + name: "EMLINK", + message: "too many links", + }, + UvError { + code: -112, + name: "EHOSTDOWN", + message: "host is down", + }, + UvError { + code: -121, + name: "EREMOTEIO", + message: "remote I/O error", + }, + UvError { + code: -25, + name: "ENOTTY", + message: "inappropriate ioctl for device", + }, + UvError { + code: -4028, + name: "EFTYPE", + message: "inappropriate file type or format", + }, + UvError { + code: -84, + name: "EILSEQ", + message: "illegal byte sequence", + }, +]; +const CODE_TO_ERROR_FREEBSD: &[UvError] = &[ + UvError { + code: -7, + name: "E2BIG", + message: "argument list too long", + }, + UvError { + code: -13, + name: "EACCES", + message: "permission denied", + }, + UvError { + code: -48, + name: "EADDRINUSE", + message: "address already in use", + }, + UvError { + code: -49, + name: "EADDRNOTAVAIL", + message: "address not available", + }, + UvError { + code: -47, + name: "EAFNOSUPPORT", + message: "address family not supported", + }, + UvError { + code: -35, + name: "EAGAIN", + message: "resource temporarily unavailable", + }, + UvError { + code: -3000, + name: "EAI_ADDRFAMILY", + message: "address family not supported", + }, + UvError { + code: -3001, + name: "EAI_AGAIN", + message: "temporary failure", + }, + UvError { + code: -3002, + name: "EAI_BADFLAGS", + message: "bad ai_flags value", + }, + UvError { + code: -3013, + name: "EAI_BADHINTS", + message: "invalid value for hints", + }, + UvError { + code: -3003, + name: "EAI_CANCELED", + message: "request canceled", + }, + UvError { + code: -3004, + name: "EAI_FAIL", + message: "permanent failure", + }, + UvError { + code: -3005, + name: "EAI_FAMILY", + message: "ai_family not supported", + }, + UvError { + code: -3006, + name: "EAI_MEMORY", + message: "out of memory", + }, + UvError { + code: -3007, + name: "EAI_NODATA", + message: "no address", + }, + UvError { + code: -3008, + name: "EAI_NONAME", + message: "unknown node or service", + }, + UvError { + code: -3009, + name: "EAI_OVERFLOW", + message: "argument buffer overflow", + }, + UvError { + code: -3014, + name: "EAI_PROTOCOL", + message: "resolved protocol is unknown", + }, + UvError { + code: -3010, + name: "EAI_SERVICE", + message: "service not available for socket type", + }, + UvError { + code: -3011, + name: "EAI_SOCKTYPE", + message: "socket type not supported", + }, + UvError { + code: -37, + name: "EALREADY", + message: "connection already in progress", + }, + UvError { + code: -9, + name: "EBADF", + message: "bad file descriptor", + }, + UvError { + code: -16, + name: "EBUSY", + message: "resource busy or locked", + }, + UvError { + code: -85, + name: "ECANCELED", + message: "operation canceled", + }, + UvError { + code: -4080, + name: "ECHARSET", + message: "invalid Unicode character", + }, + UvError { + code: -53, + name: "ECONNABORTED", + message: "software caused connection abort", + }, + UvError { + code: -61, + name: "ECONNREFUSED", + message: "connection refused", + }, + UvError { + code: -54, + name: "ECONNRESET", + message: "connection reset by peer", + }, + UvError { + code: -39, + name: "EDESTADDRREQ", + message: "destination address required", + }, + UvError { + code: -17, + name: "EEXIST", + message: "file already exists", + }, + UvError { + code: -14, + name: "EFAULT", + message: "bad address in system call argument", + }, + UvError { + code: -27, + name: "EFBIG", + message: "file too large", + }, + UvError { + code: -65, + name: "EHOSTUNREACH", + message: "host is unreachable", + }, + UvError { + code: -4, + name: "EINTR", + message: "interrupted system call", + }, + UvError { + code: -22, + name: "EINVAL", + message: "invalid argument", + }, + UvError { + code: -5, + name: "EIO", + message: "i/o error", + }, + UvError { + code: -56, + name: "EISCONN", + message: "socket is already connected", + }, + UvError { + code: -21, + name: "EISDIR", + message: "illegal operation on a directory", + }, + UvError { + code: -62, + name: "ELOOP", + message: "too many symbolic links encountered", + }, + UvError { + code: -24, + name: "EMFILE", + message: "too many open files", + }, + UvError { + code: -40, + name: "EMSGSIZE", + message: "message too long", + }, + UvError { + code: -63, + name: "ENAMETOOLONG", + message: "name too long", + }, + UvError { + code: -50, + name: "ENETDOWN", + message: "network is down", + }, + UvError { + code: -51, + name: "ENETUNREACH", + message: "network is unreachable", + }, + UvError { + code: -23, + name: "ENFILE", + message: "file table overflow", + }, + UvError { + code: -55, + name: "ENOBUFS", + message: "no buffer space available", + }, + UvError { + code: -19, + name: "ENODEV", + message: "no such device", + }, + UvError { + code: -2, + name: "ENOENT", + message: "no such file or directory", + }, + UvError { + code: -12, + name: "ENOMEM", + message: "not enough memory", + }, + UvError { + code: -4056, + name: "ENONET", + message: "machine is not on the network", + }, + UvError { + code: -42, + name: "ENOPROTOOPT", + message: "protocol not available", + }, + UvError { + code: -28, + name: "ENOSPC", + message: "no space left on device", + }, + UvError { + code: -78, + name: "ENOSYS", + message: "function not implemented", + }, + UvError { + code: -57, + name: "ENOTCONN", + message: "socket is not connected", + }, + UvError { + code: -20, + name: "ENOTDIR", + message: "not a directory", + }, + UvError { + code: -66, + name: "ENOTEMPTY", + message: "directory not empty", + }, + UvError { + code: -38, + name: "ENOTSOCK", + message: "socket operation on non-socket", + }, + UvError { + code: -45, + name: "ENOTSUP", + message: "operation not supported on socket", + }, + UvError { + code: -84, + name: "EOVERFLOW", + message: "value too large for defined data type", + }, + UvError { + code: -1, + name: "EPERM", + message: "operation not permitted", + }, + UvError { + code: -32, + name: "EPIPE", + message: "broken pipe", + }, + UvError { + code: -92, + name: "EPROTO", + message: "protocol error", + }, + UvError { + code: -43, + name: "EPROTONOSUPPORT", + message: "protocol not supported", + }, + UvError { + code: -41, + name: "EPROTOTYPE", + message: "protocol wrong type for socket", + }, + UvError { + code: -34, + name: "ERANGE", + message: "result too large", + }, + UvError { + code: -30, + name: "EROFS", + message: "read-only file system", + }, + UvError { + code: -58, + name: "ESHUTDOWN", + message: "cannot send after transport endpoint shutdown", + }, + UvError { + code: -29, + name: "ESPIPE", + message: "invalid seek", + }, + UvError { + code: -3, + name: "ESRCH", + message: "no such process", + }, + UvError { + code: -60, + name: "ETIMEDOUT", + message: "connection timed out", + }, + UvError { + code: -26, + name: "ETXTBSY", + message: "text file is busy", + }, + UvError { + code: -18, + name: "EXDEV", + message: "cross-device link not permitted", + }, + UvError { + code: -4094, + name: "UNKNOWN", + message: "unknown error", + }, + UvError { + code: -4095, + name: "EOF", + message: "end of file", + }, + UvError { + code: -6, + name: "ENXIO", + message: "no such device or address", + }, + UvError { + code: -31, + name: "EMLINK", + message: "too many links", + }, + UvError { + code: -64, + name: "EHOSTDOWN", + message: "host is down", + }, + UvError { + code: -4030, + name: "EREMOTEIO", + message: "remote I/O error", + }, + UvError { + code: -25, + name: "ENOTTY", + message: "inappropriate ioctl for device", + }, + UvError { + code: -79, + name: "EFTYPE", + message: "inappropriate file type or format", + }, + UvError { + code: -86, + name: "EILSEQ", + message: "illegal byte sequence", + }, + UvError { + code: -44, + name: "ESOCKTNOSUPPORT", + message: "socket type not supported", + }, +]; +const CODE_TO_ERROR_OPENBSD: &[UvError] = &[ + UvError { + code: -7, + name: "E2BIG", + message: "argument list too long", + }, + UvError { + code: -13, + name: "EACCES", + message: "permission denied", + }, + UvError { + code: -48, + name: "EADDRINUSE", + message: "address already in use", + }, + UvError { + code: -49, + name: "EADDRNOTAVAIL", + message: "address not available", + }, + UvError { + code: -47, + name: "EAFNOSUPPORT", + message: "address family not supported", + }, + UvError { + code: -35, + name: "EAGAIN", + message: "resource temporarily unavailable", + }, + UvError { + code: -3000, + name: "EAI_ADDRFAMILY", + message: "address family not supported", + }, + UvError { + code: -3001, + name: "EAI_AGAIN", + message: "temporary failure", + }, + UvError { + code: -3002, + name: "EAI_BADFLAGS", + message: "bad ai_flags value", + }, + UvError { + code: -3013, + name: "EAI_BADHINTS", + message: "invalid value for hints", + }, + UvError { + code: -3003, + name: "EAI_CANCELED", + message: "request canceled", + }, + UvError { + code: -3004, + name: "EAI_FAIL", + message: "permanent failure", + }, + UvError { + code: -3005, + name: "EAI_FAMILY", + message: "ai_family not supported", + }, + UvError { + code: -3006, + name: "EAI_MEMORY", + message: "out of memory", + }, + UvError { + code: -3007, + name: "EAI_NODATA", + message: "no address", + }, + UvError { + code: -3008, + name: "EAI_NONAME", + message: "unknown node or service", + }, + UvError { + code: -3009, + name: "EAI_OVERFLOW", + message: "argument buffer overflow", + }, + UvError { + code: -3014, + name: "EAI_PROTOCOL", + message: "resolved protocol is unknown", + }, + UvError { + code: -3010, + name: "EAI_SERVICE", + message: "service not available for socket type", + }, + UvError { + code: -3011, + name: "EAI_SOCKTYPE", + message: "socket type not supported", + }, + UvError { + code: -37, + name: "EALREADY", + message: "connection already in progress", + }, + UvError { + code: -9, + name: "EBADF", + message: "bad file descriptor", + }, + UvError { + code: -16, + name: "EBUSY", + message: "resource busy or locked", + }, + UvError { + code: -88, + name: "ECANCELED", + message: "operation canceled", + }, + UvError { + code: -4080, + name: "ECHARSET", + message: "invalid Unicode character", + }, + UvError { + code: -53, + name: "ECONNABORTED", + message: "software caused connection abort", + }, + UvError { + code: -61, + name: "ECONNREFUSED", + message: "connection refused", + }, + UvError { + code: -54, + name: "ECONNRESET", + message: "connection reset by peer", + }, + UvError { + code: -39, + name: "EDESTADDRREQ", + message: "destination address required", + }, + UvError { + code: -17, + name: "EEXIST", + message: "file already exists", + }, + UvError { + code: -14, + name: "EFAULT", + message: "bad address in system call argument", + }, + UvError { + code: -27, + name: "EFBIG", + message: "file too large", + }, + UvError { + code: -65, + name: "EHOSTUNREACH", + message: "host is unreachable", + }, + UvError { + code: -4, + name: "EINTR", + message: "interrupted system call", + }, + UvError { + code: -22, + name: "EINVAL", + message: "invalid argument", + }, + UvError { + code: -5, + name: "EIO", + message: "i/o error", + }, + UvError { + code: -56, + name: "EISCONN", + message: "socket is already connected", + }, + UvError { + code: -21, + name: "EISDIR", + message: "illegal operation on a directory", + }, + UvError { + code: -62, + name: "ELOOP", + message: "too many symbolic links encountered", + }, + UvError { + code: -24, + name: "EMFILE", + message: "too many open files", + }, + UvError { + code: -40, + name: "EMSGSIZE", + message: "message too long", + }, + UvError { + code: -63, + name: "ENAMETOOLONG", + message: "name too long", + }, + UvError { + code: -50, + name: "ENETDOWN", + message: "network is down", + }, + UvError { + code: -51, + name: "ENETUNREACH", + message: "network is unreachable", + }, + UvError { + code: -23, + name: "ENFILE", + message: "file table overflow", + }, + UvError { + code: -55, + name: "ENOBUFS", + message: "no buffer space available", + }, + UvError { + code: -19, + name: "ENODEV", + message: "no such device", + }, + UvError { + code: -2, + name: "ENOENT", + message: "no such file or directory", + }, + UvError { + code: -12, + name: "ENOMEM", + message: "not enough memory", + }, + UvError { + code: -4056, + name: "ENONET", + message: "machine is not on the network", + }, + UvError { + code: -42, + name: "ENOPROTOOPT", + message: "protocol not available", + }, + UvError { + code: -28, + name: "ENOSPC", + message: "no space left on device", + }, + UvError { + code: -78, + name: "ENOSYS", + message: "function not implemented", + }, + UvError { + code: -57, + name: "ENOTCONN", + message: "socket is not connected", + }, + UvError { + code: -20, + name: "ENOTDIR", + message: "not a directory", + }, + UvError { + code: -66, + name: "ENOTEMPTY", + message: "directory not empty", + }, + UvError { + code: -38, + name: "ENOTSOCK", + message: "socket operation on non-socket", + }, + UvError { + code: -45, + name: "ENOTSUP", + message: "operation not supported on socket", + }, + UvError { + code: -87, + name: "EOVERFLOW", + message: "value too large for defined data type", + }, + UvError { + code: -1, + name: "EPERM", + message: "operation not permitted", + }, + UvError { + code: -32, + name: "EPIPE", + message: "broken pipe", + }, + UvError { + code: -95, + name: "EPROTO", + message: "protocol error", + }, + UvError { + code: -43, + name: "EPROTONOSUPPORT", + message: "protocol not supported", + }, + UvError { + code: -41, + name: "EPROTOTYPE", + message: "protocol wrong type for socket", + }, + UvError { + code: -34, + name: "ERANGE", + message: "result too large", + }, + UvError { + code: -30, + name: "EROFS", + message: "read-only file system", + }, + UvError { + code: -58, + name: "ESHUTDOWN", + message: "cannot send after transport endpoint shutdown", + }, + UvError { + code: -29, + name: "ESPIPE", + message: "invalid seek", + }, + UvError { + code: -3, + name: "ESRCH", + message: "no such process", + }, + UvError { + code: -60, + name: "ETIMEDOUT", + message: "connection timed out", + }, + UvError { + code: -26, + name: "ETXTBSY", + message: "text file is busy", + }, + UvError { + code: -18, + name: "EXDEV", + message: "cross-device link not permitted", + }, + UvError { + code: -4094, + name: "UNKNOWN", + message: "unknown error", + }, + UvError { + code: -4095, + name: "EOF", + message: "end of file", + }, + UvError { + code: -6, + name: "ENXIO", + message: "no such device or address", + }, + UvError { + code: -31, + name: "EMLINK", + message: "too many links", + }, + UvError { + code: -64, + name: "EHOSTDOWN", + message: "host is down", + }, + UvError { + code: -4030, + name: "EREMOTEIO", + message: "remote I/O error", + }, + UvError { + code: -25, + name: "ENOTTY", + message: "inappropriate ioctl for device", + }, + UvError { + code: -79, + name: "EFTYPE", + message: "inappropriate file type or format", + }, + UvError { + code: -84, + name: "EILSEQ", + message: "illegal byte sequence", + }, + UvError { + code: -44, + name: "ESOCKTNOSUPPORT", + message: "socket type not supported", + }, +]; + +fn active_errors() -> &'static [UvError] { + if cfg!(windows) { + CODE_TO_ERROR_WINDOWS + } else if cfg!(target_os = "macos") { + CODE_TO_ERROR_DARWIN + } else if cfg!(any(target_os = "linux", target_os = "android")) { + CODE_TO_ERROR_LINUX + } else if cfg!(target_os = "freebsd") { + CODE_TO_ERROR_FREEBSD + } else if cfg!(target_os = "openbsd") { + CODE_TO_ERROR_OPENBSD + } else { + CODE_TO_ERROR_LINUX + } +} + +fn error_for_code(errno: i32) -> Option<&'static UvError> { + active_errors().iter().find(|error| error.code == errno) +} + +fn code_for_name(name: &str) -> Option { + active_errors() + .iter() + .find(|error| error.name == name) + .map(|error| error.code) +} + +fn set_value( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: v8::Local, +) { + let key = v8::String::new(scope, name).unwrap(); + obj.set(scope, key.into(), value); +} + +fn set_i32( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: i32, +) { + let value = v8::Integer::new(scope, value); + set_value(scope, obj, name, value.into()); +} + +fn set_code_const( + scope: &mut v8::PinScope, + obj: v8::Local, + export_name: &str, + code_name: &str, +) { + if let Some(code) = code_for_name(code_name) { + set_i32(scope, obj, export_name, code); + } else { + let undefined: v8::Local = v8::undefined(scope).into(); + set_value(scope, obj, export_name, undefined); + } +} + +fn core_ops<'s>(scope: &mut v8::PinScope<'s, '_>) -> v8::Local<'s, v8::Object> { + let context = scope.get_current_context(); + let global = context.global(scope); + let deno_key = v8::String::new(scope, "Deno").unwrap(); + let core_key = v8::String::new(scope, "core").unwrap(); + let ops_key = v8::String::new(scope, "ops").unwrap(); + let deno = global.get(scope, deno_key.into()).unwrap(); + let deno = v8::Local::::try_from(deno).unwrap(); + let core = deno.get(scope, core_key.into()).unwrap(); + let core = v8::Local::::try_from(core).unwrap(); + let ops = core.get(scope, ops_key.into()).unwrap(); + v8::Local::::try_from(ops).unwrap() +} + +fn set_op_alias( + scope: &mut v8::PinScope, + obj: v8::Local, + export_name: &str, + op_name: &str, +) { + let ops = core_ops(scope); + let op_key = v8::String::new(scope, op_name).unwrap(); + let op = ops.get(scope, op_key.into()).unwrap(); + set_value(scope, obj, export_name, op); +} + +fn build_error_map<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Map> { + let map = v8::Map::new(scope); + for error in active_errors() { + let key = v8::Integer::new(scope, error.code); + let value = v8::Array::new(scope, 2); + let name = v8::String::new(scope, error.name).unwrap(); + let message = v8::String::new(scope, error.message).unwrap(); + value.set_index(scope, 0, name.into()); + value.set_index(scope, 1, message.into()); + map.set(scope, key.into(), value.into()).unwrap(); + } + map +} + +fn build_code_map<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Map> { + let map = v8::Map::new(scope); + for error in active_errors() { + let key = v8::String::new(scope, error.name).unwrap(); + let value = v8::Integer::new(scope, error.code); + map.set(scope, key.into(), value.into()).unwrap(); + } + map +} + +#[op2] +pub fn op_node_internal_binding_uv<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + let error_map = build_error_map(scope); + set_value(scope, obj, "errorMap", error_map.into()); + let code_map = build_code_map(scope); + set_value(scope, obj, "codeMap", code_map.into()); + + set_code_const(scope, obj, "UV_EAI_MEMORY", "EAI_MEMORY"); + set_code_const(scope, obj, "UV_EBADF", "EBADF"); + set_code_const(scope, obj, "UV_ECANCELED", "ECANCELED"); + set_code_const(scope, obj, "UV_EEXIST", "EEXIST"); + set_code_const(scope, obj, "UV_EINVAL", "EINVAL"); + set_code_const(scope, obj, "UV_ENETUNREACH", "ENETUNREACH"); + set_code_const(scope, obj, "UV_ENOENT", "ENOENT"); + set_code_const(scope, obj, "UV_ENOMEM", "ENOMEM"); + set_code_const(scope, obj, "UV_ENOTSOCK", "ENOTSOCK"); + set_code_const(scope, obj, "UV_ETIMEDOUT", "ETIMEDOUT"); + set_code_const(scope, obj, "UV_UNKNOWN", "UNKNOWN"); + set_code_const(scope, obj, "UV_EOF", "EOF"); + + set_op_alias( + scope, + obj, + "mapSysErrnoToUvErrno", + "op_node_uv_map_sys_errno_to_uv_errno", + ); + set_op_alias(scope, obj, "errname", "op_node_uv_errname"); + set_op_alias( + scope, + obj, + "getErrorMessage", + "op_node_uv_get_error_message", + ); + set_op_alias(scope, obj, "getErrorMap", "op_node_uv_get_error_map"); + set_op_alias(scope, obj, "getCodeMap", "op_node_uv_get_code_map"); + obj +} + +#[op2(fast)] +pub fn op_node_uv_map_sys_errno_to_uv_errno(#[smi] sys_errno: i32) -> i32 { + if cfg!(windows) { + let code = node_sys_to_uv_error(sys_errno); + code_for_name(code).unwrap_or(-sys_errno) + } else { + -sys_errno + } +} + +#[op2] +#[string] +pub fn op_node_uv_errname(#[smi] errno: i32) -> String { + if let Some(error) = error_for_code(errno) { + error.name.to_string() + } else { + format!("UNKNOWN ({errno})") + } +} + +#[op2] +#[string] +pub fn op_node_uv_get_error_message(#[smi] errno: i32) -> String { + if let Some(error) = error_for_code(errno) { + error.message.to_string() + } else { + format!("UNKNOWN ({errno})") + } +} + +#[op2] +pub fn op_node_uv_get_error_map<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Map> { + build_error_map(scope) +} + +#[op2] +pub fn op_node_uv_get_code_map<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Map> { + build_code_map(scope) +} diff --git a/ext/node/ops/mod.rs b/ext/node/ops/mod.rs index 3d829d2c95763c..74f57aa8b5e441 100644 --- a/ext/node/ops/mod.rs +++ b/ext/node/ops/mod.rs @@ -11,6 +11,9 @@ pub mod http2; pub mod idna; pub mod inspector; pub mod internal_binding; +pub mod internal_binding_constants; +pub mod internal_binding_crypto; +pub mod internal_binding_uv; pub mod ipc; pub mod llhttp; pub mod module; diff --git a/ext/node/ops/winerror.rs b/ext/node/ops/winerror.rs index 0a855fe347679c..77f43351db1c68 100644 --- a/ext/node/ops/winerror.rs +++ b/ext/node/ops/winerror.rs @@ -29,10 +29,8 @@ use deno_core::op2; -#[op2] -#[string] -pub fn op_node_sys_to_uv_error(err: i32) -> String { - let uv_err = match err { +pub fn node_sys_to_uv_error(err: i32) -> &'static str { + match err { ERROR_NOACCESS => "EACCES", WSAEACCES => "EACCES", ERROR_CANT_ACCESS_FILE => "EACCES", @@ -132,8 +130,13 @@ pub fn op_node_sys_to_uv_error(err: i32) -> String { ERROR_META_EXPANSION_TOO_LONG => "E2BIG", WSAESOCKTNOSUPPORT => "ESOCKTNOSUPPORT", _ => "UNKNOWN", - }; - uv_err.to_string() + } +} + +#[op2] +#[string] +pub fn op_node_sys_to_uv_error(err: i32) -> String { + node_sys_to_uv_error(err).to_string() } // Windows system error codes diff --git a/ext/node/polyfills/internal_binding/_timingSafeEqual.ts b/ext/node/polyfills/internal_binding/_timingSafeEqual.ts index 22ae0a0c258fd7..0f20b721231ea0 100644 --- a/ext/node/polyfills/internal_binding/_timingSafeEqual.ts +++ b/ext/node/polyfills/internal_binding/_timingSafeEqual.ts @@ -1,121 +1,6 @@ // Copyright 2018-2026 the Deno authors. MIT license. (function () { -const { core, primordials } = __bootstrap; -const { Buffer } = core.loadExtScript("ext:deno_node/internal/buffer.mjs"); -const { - ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH, - ERR_INVALID_ARG_TYPE, -} = core.loadExtScript("ext:deno_node/internal/errors.ts"); - -const { - isAnyArrayBuffer, - isArrayBufferView, - isDataView, -} = core; -const { - ArrayBufferIsView, - ArrayBufferPrototypeGetByteLength, - DataView, - DataViewPrototypeGetBuffer, - DataViewPrototypeGetByteLength, - DataViewPrototypeGetByteOffset, - DataViewPrototypeGetUint8, - ObjectPrototypeIsPrototypeOf, - TypedArrayPrototypeGetBuffer, - TypedArrayPrototypeGetByteLength, - TypedArrayPrototypeGetByteOffset, -} = primordials; - -function validateBuffer( - buf: unknown, - name: string, -): asserts buf is ArrayBufferLike | ArrayBufferView { - if (!isAnyArrayBuffer(buf) && !isArrayBufferView(buf)) { - throw new ERR_INVALID_ARG_TYPE( - name, - ["Buffer", "ArrayBuffer", "TypedArray", "DataView"], - buf, - ); - } -} - -function byteLengthOf( - ab: ArrayBufferView | ArrayBufferLike | DataView, -): number { - if (isDataView(ab)) { - return DataViewPrototypeGetByteLength(ab); - } - if (ArrayBufferIsView(ab)) { - return TypedArrayPrototypeGetByteLength(ab); - } - return ArrayBufferPrototypeGetByteLength(ab); -} - -function toDataView(ab: ArrayBufferLike | ArrayBufferView): DataView { - if (ArrayBufferIsView(ab)) { - if (isDataView(ab)) { - return new DataView( - DataViewPrototypeGetBuffer(ab), - DataViewPrototypeGetByteOffset(ab), - DataViewPrototypeGetByteLength(ab), - ); - } - return new DataView( - TypedArrayPrototypeGetBuffer(ab), - TypedArrayPrototypeGetByteOffset(ab), - TypedArrayPrototypeGetByteLength(ab), - ); - } - return new DataView(ab); -} - -/** Compare to array buffers or data views in a way that timing based attacks - * cannot gain information about the platform. */ -function stdTimingSafeEqual( - a: ArrayBufferView | ArrayBufferLike | DataView, - b: ArrayBufferView | ArrayBufferLike | DataView, -): boolean { - if (byteLengthOf(a) !== byteLengthOf(b)) { - throw new ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH(); - } - if (!isDataView(a)) { - a = toDataView(a); - } - if (!isDataView(b)) { - b = toDataView(b); - } - const length = DataViewPrototypeGetByteLength(a); - let out = 0; - let i = -1; - while (++i < length) { - out |= DataViewPrototypeGetUint8(a, i) ^ DataViewPrototypeGetUint8(b, i); - } - return out === 0; -} - -const timingSafeEqual = ( - buf1: Buffer | DataView | ArrayBuffer, - buf2: Buffer | DataView | ArrayBuffer, -): boolean => { - validateBuffer(buf1, "buf1"); - validateBuffer(buf2, "buf2"); - if (ObjectPrototypeIsPrototypeOf(Buffer.prototype, buf1)) { - buf1 = new DataView( - TypedArrayPrototypeGetBuffer(buf1), - TypedArrayPrototypeGetByteOffset(buf1), - TypedArrayPrototypeGetByteLength(buf1), - ); - } - if (ObjectPrototypeIsPrototypeOf(Buffer.prototype, buf2)) { - buf2 = new DataView( - TypedArrayPrototypeGetBuffer(buf2), - TypedArrayPrototypeGetByteOffset(buf2), - TypedArrayPrototypeGetByteLength(buf2), - ); - } - return stdTimingSafeEqual(buf1, buf2); -}; - -return { timingSafeEqual, default: { timingSafeEqual } }; +const { op_node_internal_binding_timing_safe_equal } = __bootstrap.core.ops; +return op_node_internal_binding_timing_safe_equal(); })(); diff --git a/ext/node/polyfills/internal_binding/constants.ts b/ext/node/polyfills/internal_binding/constants.ts index 2bca6f568442b0..99bea22fa9addd 100644 --- a/ext/node/polyfills/internal_binding/constants.ts +++ b/ext/node/polyfills/internal_binding/constants.ts @@ -1,898 +1,6 @@ // Copyright 2018-2026 the Deno authors. MIT license. (function () { -const { primordials } = __bootstrap; -const { ObjectFreeze } = primordials; -const { core } = __bootstrap; -const { op_node_build_os, op_node_fs_constants } = core.ops; - -let os: { - dlopen: { - RTLD_DEEPBIND?: number; - RTLD_GLOBAL?: number; - RTLD_LAZY?: number; - RTLD_LOCAL?: number; - RTLD_NOW?: number; - }; - errno: { - E2BIG: number; - EACCES: number; - EADDRINUSE: number; - EADDRNOTAVAIL: number; - EAFNOSUPPORT: number; - EAGAIN: number; - EALREADY: number; - EBADF: number; - EBADMSG: number; - EBUSY: number; - ECANCELED: number; - ECHILD: number; - ECONNABORTED: number; - ECONNREFUSED: number; - ECONNRESET: number; - EDEADLK: number; - EDESTADDRREQ: number; - EDOM: number; - EDQUOT?: number; - EEXIST: number; - EFAULT: number; - EFBIG: number; - EHOSTUNREACH: number; - EIDRM: number; - EILSEQ: number; - EINPROGRESS: number; - EINTR: number; - EINVAL: number; - EIO: number; - EISCONN: number; - EISDIR: number; - ELOOP: number; - EMFILE: number; - EMLINK: number; - EMSGSIZE: number; - EMULTIHOP?: number; - ENAMETOOLONG: number; - ENETDOWN: number; - ENETRESET: number; - ENETUNREACH: number; - ENFILE: number; - ENOBUFS: number; - ENODATA: number; - ENODEV: number; - ENOENT: number; - ENOEXEC: number; - ENOLCK: number; - ENOLINK: number; - ENOMEM: number; - ENOMSG: number; - ENOPROTOOPT: number; - ENOSPC: number; - ENOSR: number; - ENOSTR: number; - ENOSYS: number; - ENOTCONN: number; - ENOTDIR: number; - ENOTEMPTY: number; - ENOTSOCK: number; - ENOTSUP: number; - ENOTTY: number; - ENXIO: number; - EOPNOTSUPP: number; - EOVERFLOW: number; - EPERM: number; - EPIPE: number; - EPROTO: number; - EPROTONOSUPPORT: number; - EPROTOTYPE: number; - ERANGE: number; - EROFS: number; - ESPIPE: number; - ESRCH: number; - ESTALE?: number; - ETIME: number; - ETIMEDOUT: number; - ETXTBSY: number; - EWOULDBLOCK: number; - EXDEV: number; - WSA_E_CANCELLED?: number; - WSA_E_NO_MORE?: number; - WSAEACCES?: number; - WSAEADDRINUSE?: number; - WSAEADDRNOTAVAIL?: number; - WSAEAFNOSUPPORT?: number; - WSAEALREADY?: number; - WSAEBADF?: number; - WSAECANCELLED?: number; - WSAECONNABORTED?: number; - WSAECONNREFUSED?: number; - WSAECONNRESET?: number; - WSAEDESTADDRREQ?: number; - WSAEDISCON?: number; - WSAEDQUOT?: number; - WSAEFAULT?: number; - WSAEHOSTDOWN?: number; - WSAEHOSTUNREACH?: number; - WSAEINPROGRESS?: number; - WSAEINTR?: number; - WSAEINVAL?: number; - WSAEINVALIDPROCTABLE?: number; - WSAEINVALIDPROVIDER?: number; - WSAEISCONN?: number; - WSAELOOP?: number; - WSAEMFILE?: number; - WSAEMSGSIZE?: number; - WSAENAMETOOLONG?: number; - WSAENETDOWN?: number; - WSAENETRESET?: number; - WSAENETUNREACH?: number; - WSAENOBUFS?: number; - WSAENOMORE?: number; - WSAENOPROTOOPT?: number; - WSAENOTCONN?: number; - WSAENOTEMPTY?: number; - WSAENOTSOCK?: number; - WSAEOPNOTSUPP?: number; - WSAEPFNOSUPPORT?: number; - WSAEPROCLIM?: number; - WSAEPROTONOSUPPORT?: number; - WSAEPROTOTYPE?: number; - WSAEPROVIDERFAILEDINIT?: number; - WSAEREFUSED?: number; - WSAEREMOTE?: number; - WSAESHUTDOWN?: number; - WSAESOCKTNOSUPPORT?: number; - WSAESTALE?: number; - WSAETIMEDOUT?: number; - WSAETOOMANYREFS?: number; - WSAEUSERS?: number; - WSAEWOULDBLOCK?: number; - WSANOTINITIALISED?: number; - WSASERVICE_NOT_FOUND?: number; - WSASYSCALLFAILURE?: number; - WSASYSNOTREADY?: number; - WSATYPE_NOT_FOUND?: number; - WSAVERNOTSUPPORTED?: number; - }; - priority: { - PRIORITY_ABOVE_NORMAL: number; - PRIORITY_BELOW_NORMAL: number; - PRIORITY_HIGH: number; - PRIORITY_HIGHEST: number; - PRIORITY_LOW: number; - PRIORITY_NORMAL: number; - }; - signals: { - SIGABRT: number; - SIGALRM?: number; - SIGBREAK?: number; - SIGBUS?: number; - SIGCHLD?: number; - SIGCONT?: number; - SIGFPE: number; - SIGHUP: number; - SIGILL: number; - SIGINFO?: number; - SIGINT: number; - SIGIO?: number; - SIGIOT?: number; - SIGKILL: number; - SIGPIPE?: number; - SIGPOLL?: number; - SIGPROF?: number; - SIGPWR?: number; - SIGQUIT?: number; - SIGSEGV: number; - SIGSTKFLT?: number; - SIGSTOP?: number; - SIGSYS?: number; - SIGTERM: number; - SIGTRAP?: number; - SIGTSTP?: number; - SIGTTIN?: number; - SIGTTOU?: number; - SIGUNUSED?: number; - SIGURG?: number; - SIGUSR1?: number; - SIGUSR2?: number; - SIGVTALRM?: number; - SIGWINCH: number; - SIGXCPU?: number; - SIGXFSZ?: number; - }; - UV_UDP_IPV6ONLY: number; - UV_UDP_REUSEADDR: number; -}; - -const buildOs = op_node_build_os(); -if (buildOs === "darwin") { - os = { - UV_UDP_IPV6ONLY: 2, - UV_UDP_REUSEADDR: 4, - dlopen: { - RTLD_LAZY: 1, - RTLD_NOW: 2, - RTLD_GLOBAL: 8, - RTLD_LOCAL: 4, - }, - errno: { - E2BIG: 7, - EACCES: 13, - EADDRINUSE: 48, - EADDRNOTAVAIL: 49, - EAFNOSUPPORT: 47, - EAGAIN: 35, - EALREADY: 37, - EBADF: 9, - EBADMSG: 94, - EBUSY: 16, - ECANCELED: 89, - ECHILD: 10, - ECONNABORTED: 53, - ECONNREFUSED: 61, - ECONNRESET: 54, - EDEADLK: 11, - EDESTADDRREQ: 39, - EDOM: 33, - EDQUOT: 69, - EEXIST: 17, - EFAULT: 14, - EFBIG: 27, - EHOSTUNREACH: 65, - EIDRM: 90, - EILSEQ: 92, - EINPROGRESS: 36, - EINTR: 4, - EINVAL: 22, - EIO: 5, - EISCONN: 56, - EISDIR: 21, - ELOOP: 62, - EMFILE: 24, - EMLINK: 31, - EMSGSIZE: 40, - EMULTIHOP: 95, - ENAMETOOLONG: 63, - ENETDOWN: 50, - ENETRESET: 52, - ENETUNREACH: 51, - ENFILE: 23, - ENOBUFS: 55, - ENODATA: 96, - ENODEV: 19, - ENOENT: 2, - ENOEXEC: 8, - ENOLCK: 77, - ENOLINK: 97, - ENOMEM: 12, - ENOMSG: 91, - ENOPROTOOPT: 42, - ENOSPC: 28, - ENOSR: 98, - ENOSTR: 99, - ENOSYS: 78, - ENOTCONN: 57, - ENOTDIR: 20, - ENOTEMPTY: 66, - ENOTSOCK: 38, - ENOTSUP: 45, - ENOTTY: 25, - ENXIO: 6, - EOPNOTSUPP: 102, - EOVERFLOW: 84, - EPERM: 1, - EPIPE: 32, - EPROTO: 100, - EPROTONOSUPPORT: 43, - EPROTOTYPE: 41, - ERANGE: 34, - EROFS: 30, - ESPIPE: 29, - ESRCH: 3, - ESTALE: 70, - ETIME: 101, - ETIMEDOUT: 60, - ETXTBSY: 26, - EWOULDBLOCK: 35, - EXDEV: 18, - }, - signals: { - SIGHUP: 1, - SIGINT: 2, - SIGQUIT: 3, - SIGILL: 4, - SIGTRAP: 5, - SIGABRT: 6, - SIGIOT: 6, - SIGBUS: 10, - SIGFPE: 8, - SIGKILL: 9, - SIGUSR1: 30, - SIGSEGV: 11, - SIGUSR2: 31, - SIGPIPE: 13, - SIGALRM: 14, - SIGTERM: 15, - SIGCHLD: 20, - SIGCONT: 19, - SIGSTOP: 17, - SIGTSTP: 18, - SIGTTIN: 21, - SIGTTOU: 22, - SIGURG: 16, - SIGXCPU: 24, - SIGXFSZ: 25, - SIGVTALRM: 26, - SIGPROF: 27, - SIGWINCH: 28, - SIGIO: 23, - SIGINFO: 29, - SIGSYS: 12, - }, - priority: { - PRIORITY_LOW: 19, - PRIORITY_BELOW_NORMAL: 10, - PRIORITY_NORMAL: 0, - PRIORITY_ABOVE_NORMAL: -7, - PRIORITY_HIGH: -14, - PRIORITY_HIGHEST: -20, - }, - }; -} else if (buildOs === "linux" || buildOs === "android") { - os = { - UV_UDP_IPV6ONLY: 2, - UV_UDP_REUSEADDR: 4, - dlopen: { - RTLD_LAZY: 1, - RTLD_NOW: 2, - RTLD_GLOBAL: 256, - RTLD_LOCAL: 0, - RTLD_DEEPBIND: 8, - }, - errno: { - E2BIG: 7, - EACCES: 13, - EADDRINUSE: 98, - EADDRNOTAVAIL: 99, - EAFNOSUPPORT: 97, - EAGAIN: 11, - EALREADY: 114, - EBADF: 9, - EBADMSG: 74, - EBUSY: 16, - ECANCELED: 125, - ECHILD: 10, - ECONNABORTED: 103, - ECONNREFUSED: 111, - ECONNRESET: 104, - EDEADLK: 35, - EDESTADDRREQ: 89, - EDOM: 33, - EDQUOT: 122, - EEXIST: 17, - EFAULT: 14, - EFBIG: 27, - EHOSTUNREACH: 113, - EIDRM: 43, - EILSEQ: 84, - EINPROGRESS: 115, - EINTR: 4, - EINVAL: 22, - EIO: 5, - EISCONN: 106, - EISDIR: 21, - ELOOP: 40, - EMFILE: 24, - EMLINK: 31, - EMSGSIZE: 90, - EMULTIHOP: 72, - ENAMETOOLONG: 36, - ENETDOWN: 100, - ENETRESET: 102, - ENETUNREACH: 101, - ENFILE: 23, - ENOBUFS: 105, - ENODATA: 61, - ENODEV: 19, - ENOENT: 2, - ENOEXEC: 8, - ENOLCK: 37, - ENOLINK: 67, - ENOMEM: 12, - ENOMSG: 42, - ENOPROTOOPT: 92, - ENOSPC: 28, - ENOSR: 63, - ENOSTR: 60, - ENOSYS: 38, - ENOTCONN: 107, - ENOTDIR: 20, - ENOTEMPTY: 39, - ENOTSOCK: 88, - ENOTSUP: 95, - ENOTTY: 25, - ENXIO: 6, - EOPNOTSUPP: 95, - EOVERFLOW: 75, - EPERM: 1, - EPIPE: 32, - EPROTO: 71, - EPROTONOSUPPORT: 93, - EPROTOTYPE: 91, - ERANGE: 34, - EROFS: 30, - ESPIPE: 29, - ESRCH: 3, - ESTALE: 116, - ETIME: 62, - ETIMEDOUT: 110, - ETXTBSY: 26, - EWOULDBLOCK: 11, - EXDEV: 18, - }, - signals: { - SIGHUP: 1, - SIGINT: 2, - SIGQUIT: 3, - SIGILL: 4, - SIGTRAP: 5, - SIGABRT: 6, - SIGIOT: 6, - SIGBUS: 7, - SIGFPE: 8, - SIGKILL: 9, - SIGUSR1: 10, - SIGSEGV: 11, - SIGUSR2: 12, - SIGPIPE: 13, - SIGALRM: 14, - SIGTERM: 15, - SIGCHLD: 17, - SIGSTKFLT: 16, - SIGCONT: 18, - SIGSTOP: 19, - SIGTSTP: 20, - SIGTTIN: 21, - SIGTTOU: 22, - SIGURG: 23, - SIGXCPU: 24, - SIGXFSZ: 25, - SIGVTALRM: 26, - SIGPROF: 27, - SIGWINCH: 28, - SIGIO: 29, - SIGPOLL: 29, - SIGPWR: 30, - SIGSYS: 31, - SIGUNUSED: 31, - }, - priority: { - PRIORITY_LOW: 19, - PRIORITY_BELOW_NORMAL: 10, - PRIORITY_NORMAL: 0, - PRIORITY_ABOVE_NORMAL: -7, - PRIORITY_HIGH: -14, - PRIORITY_HIGHEST: -20, - }, - }; -} else { - os = { - UV_UDP_IPV6ONLY: 2, - UV_UDP_REUSEADDR: 4, - dlopen: {}, - errno: { - E2BIG: 7, - EACCES: 13, - EADDRINUSE: 100, - EADDRNOTAVAIL: 101, - EAFNOSUPPORT: 102, - EAGAIN: 11, - EALREADY: 103, - EBADF: 9, - EBADMSG: 104, - EBUSY: 16, - ECANCELED: 105, - ECHILD: 10, - ECONNABORTED: 106, - ECONNREFUSED: 107, - ECONNRESET: 108, - EDEADLK: 36, - EDESTADDRREQ: 109, - EDOM: 33, - EEXIST: 17, - EFAULT: 14, - EFBIG: 27, - EHOSTUNREACH: 110, - EIDRM: 111, - EILSEQ: 42, - EINPROGRESS: 112, - EINTR: 4, - EINVAL: 22, - EIO: 5, - EISCONN: 113, - EISDIR: 21, - ELOOP: 114, - EMFILE: 24, - EMLINK: 31, - EMSGSIZE: 115, - ENAMETOOLONG: 38, - ENETDOWN: 116, - ENETRESET: 117, - ENETUNREACH: 118, - ENFILE: 23, - ENOBUFS: 119, - ENODATA: 120, - ENODEV: 19, - ENOENT: 2, - ENOEXEC: 8, - ENOLCK: 39, - ENOLINK: 121, - ENOMEM: 12, - ENOMSG: 122, - ENOPROTOOPT: 123, - ENOSPC: 28, - ENOSR: 124, - ENOSTR: 125, - ENOSYS: 40, - ENOTCONN: 126, - ENOTDIR: 20, - ENOTEMPTY: 41, - ENOTSOCK: 128, - ENOTSUP: 129, - ENOTTY: 25, - ENXIO: 6, - EOPNOTSUPP: 130, - EOVERFLOW: 132, - EPERM: 1, - EPIPE: 32, - EPROTO: 134, - EPROTONOSUPPORT: 135, - EPROTOTYPE: 136, - ERANGE: 34, - EROFS: 30, - ESPIPE: 29, - ESRCH: 3, - ETIME: 137, - ETIMEDOUT: 138, - ETXTBSY: 139, - EWOULDBLOCK: 140, - EXDEV: 18, - WSAEINTR: 10004, - WSAEBADF: 10009, - WSAEACCES: 10013, - WSAEFAULT: 10014, - WSAEINVAL: 10022, - WSAEMFILE: 10024, - WSAEWOULDBLOCK: 10035, - WSAEINPROGRESS: 10036, - WSAEALREADY: 10037, - WSAENOTSOCK: 10038, - WSAEDESTADDRREQ: 10039, - WSAEMSGSIZE: 10040, - WSAEPROTOTYPE: 10041, - WSAENOPROTOOPT: 10042, - WSAEPROTONOSUPPORT: 10043, - WSAESOCKTNOSUPPORT: 10044, - WSAEOPNOTSUPP: 10045, - WSAEPFNOSUPPORT: 10046, - WSAEAFNOSUPPORT: 10047, - WSAEADDRINUSE: 10048, - WSAEADDRNOTAVAIL: 10049, - WSAENETDOWN: 10050, - WSAENETUNREACH: 10051, - WSAENETRESET: 10052, - WSAECONNABORTED: 10053, - WSAECONNRESET: 10054, - WSAENOBUFS: 10055, - WSAEISCONN: 10056, - WSAENOTCONN: 10057, - WSAESHUTDOWN: 10058, - WSAETOOMANYREFS: 10059, - WSAETIMEDOUT: 10060, - WSAECONNREFUSED: 10061, - WSAELOOP: 10062, - WSAENAMETOOLONG: 10063, - WSAEHOSTDOWN: 10064, - WSAEHOSTUNREACH: 10065, - WSAENOTEMPTY: 10066, - WSAEPROCLIM: 10067, - WSAEUSERS: 10068, - WSAEDQUOT: 10069, - WSAESTALE: 10070, - WSAEREMOTE: 10071, - WSASYSNOTREADY: 10091, - WSAVERNOTSUPPORTED: 10092, - WSANOTINITIALISED: 10093, - WSAEDISCON: 10101, - WSAENOMORE: 10102, - WSAECANCELLED: 10103, - WSAEINVALIDPROCTABLE: 10104, - WSAEINVALIDPROVIDER: 10105, - WSAEPROVIDERFAILEDINIT: 10106, - WSASYSCALLFAILURE: 10107, - WSASERVICE_NOT_FOUND: 10108, - WSATYPE_NOT_FOUND: 10109, - WSA_E_NO_MORE: 10110, - WSA_E_CANCELLED: 10111, - WSAEREFUSED: 10112, - }, - signals: { - SIGHUP: 1, - SIGINT: 2, - SIGILL: 4, - SIGABRT: 22, - SIGFPE: 8, - SIGKILL: 9, - SIGSEGV: 11, - SIGTERM: 15, - SIGBREAK: 21, - SIGWINCH: 28, - }, - priority: { - PRIORITY_LOW: 19, - PRIORITY_BELOW_NORMAL: 10, - PRIORITY_NORMAL: 0, - PRIORITY_ABOVE_NORMAL: -7, - PRIORITY_HIGH: -14, - PRIORITY_HIGHEST: -20, - }, - }; -} - -ObjectFreeze(os.signals); - -const fs = op_node_fs_constants(); - -const crypto = { - OPENSSL_VERSION_NUMBER: 269488319, - SSL_OP_ALL: 2147485780, - SSL_OP_ALLOW_NO_DHE_KEX: 1024, - SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION: 262144, - SSL_OP_CIPHER_SERVER_PREFERENCE: 4194304, - SSL_OP_CISCO_ANYCONNECT: 32768, - SSL_OP_COOKIE_EXCHANGE: 8192, - SSL_OP_CRYPTOPRO_TLSEXT_BUG: 2147483648, - SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS: 2048, - SSL_OP_EPHEMERAL_RSA: 0, - SSL_OP_LEGACY_SERVER_CONNECT: 4, - SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER: 0, - SSL_OP_MICROSOFT_SESS_ID_BUG: 0, - SSL_OP_MSIE_SSLV2_RSA_PADDING: 0, - SSL_OP_NETSCAPE_CA_DN_BUG: 0, - SSL_OP_NETSCAPE_CHALLENGE_BUG: 0, - SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG: 0, - SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG: 0, - SSL_OP_NO_COMPRESSION: 131072, - SSL_OP_NO_ENCRYPT_THEN_MAC: 524288, - SSL_OP_NO_QUERY_MTU: 4096, - SSL_OP_NO_RENEGOTIATION: 1073741824, - SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION: 65536, - SSL_OP_NO_SSLv2: 0, - SSL_OP_NO_SSLv3: 33554432, - SSL_OP_NO_TICKET: 16384, - SSL_OP_NO_TLSv1: 67108864, - SSL_OP_NO_TLSv1_1: 268435456, - SSL_OP_NO_TLSv1_2: 134217728, - SSL_OP_NO_TLSv1_3: 536870912, - SSL_OP_PKCS1_CHECK_1: 0, - SSL_OP_PKCS1_CHECK_2: 0, - SSL_OP_PRIORITIZE_CHACHA: 2097152, - SSL_OP_SINGLE_DH_USE: 0, - SSL_OP_SINGLE_ECDH_USE: 0, - SSL_OP_SSLEAY_080_CLIENT_DH_BUG: 0, - SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG: 0, - SSL_OP_TLS_BLOCK_PADDING_BUG: 0, - SSL_OP_TLS_D5_BUG: 0, - SSL_OP_TLS_ROLLBACK_BUG: 8388608, - ENGINE_METHOD_RSA: 1, - ENGINE_METHOD_DSA: 2, - ENGINE_METHOD_DH: 4, - ENGINE_METHOD_RAND: 8, - ENGINE_METHOD_EC: 2048, - ENGINE_METHOD_CIPHERS: 64, - ENGINE_METHOD_DIGESTS: 128, - ENGINE_METHOD_PKEY_METHS: 512, - ENGINE_METHOD_PKEY_ASN1_METHS: 1024, - ENGINE_METHOD_ALL: 65535, - ENGINE_METHOD_NONE: 0, - DH_CHECK_P_NOT_SAFE_PRIME: 2, - DH_CHECK_P_NOT_PRIME: 1, - DH_UNABLE_TO_CHECK_GENERATOR: 4, - DH_NOT_SUITABLE_GENERATOR: 8, - ALPN_ENABLED: 1, - RSA_PKCS1_PADDING: 1, - RSA_SSLV23_PADDING: 2, - RSA_NO_PADDING: 3, - RSA_PKCS1_OAEP_PADDING: 4, - RSA_X931_PADDING: 5, - RSA_PKCS1_PSS_PADDING: 6, - RSA_PSS_SALTLEN_DIGEST: -1, - RSA_PSS_SALTLEN_MAX_SIGN: -2, - RSA_PSS_SALTLEN_AUTO: -2, - defaultCoreCipherList: - "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA", - TLS1_VERSION: 769, - TLS1_1_VERSION: 770, - TLS1_2_VERSION: 771, - TLS1_3_VERSION: 772, - POINT_CONVERSION_COMPRESSED: 2, - POINT_CONVERSION_UNCOMPRESSED: 4, - POINT_CONVERSION_HYBRID: 6, -} as const; -const zlib = { - Z_NO_FLUSH: 0, - Z_PARTIAL_FLUSH: 1, - Z_SYNC_FLUSH: 2, - Z_FULL_FLUSH: 3, - Z_FINISH: 4, - Z_BLOCK: 5, - Z_OK: 0, - Z_STREAM_END: 1, - Z_NEED_DICT: 2, - Z_ERRNO: -1, - Z_STREAM_ERROR: -2, - Z_DATA_ERROR: -3, - Z_MEM_ERROR: -4, - Z_BUF_ERROR: -5, - Z_VERSION_ERROR: -6, - Z_NO_COMPRESSION: 0, - Z_BEST_SPEED: 1, - Z_BEST_COMPRESSION: 9, - Z_DEFAULT_COMPRESSION: -1, - Z_FILTERED: 1, - Z_HUFFMAN_ONLY: 2, - Z_RLE: 3, - Z_FIXED: 4, - Z_DEFAULT_STRATEGY: 0, - ZLIB_VERNUM: 4784, - DEFLATE: 1, - INFLATE: 2, - GZIP: 3, - GUNZIP: 4, - DEFLATERAW: 5, - INFLATERAW: 6, - UNZIP: 7, - BROTLI_DECODE: 8, - BROTLI_ENCODE: 9, - ZSTD_COMPRESS: 10, - ZSTD_DECOMPRESS: 11, - Z_MIN_WINDOWBITS: 8, - Z_MAX_WINDOWBITS: 15, - Z_DEFAULT_WINDOWBITS: 15, - Z_MIN_CHUNK: 64, - Z_MAX_CHUNK: Infinity, - Z_DEFAULT_CHUNK: 16384, - Z_MIN_MEMLEVEL: 1, - Z_MAX_MEMLEVEL: 9, - Z_DEFAULT_MEMLEVEL: 8, - Z_MIN_LEVEL: -1, - Z_MAX_LEVEL: 9, - Z_DEFAULT_LEVEL: -1, - BROTLI_OPERATION_PROCESS: 0, - BROTLI_OPERATION_FLUSH: 1, - BROTLI_OPERATION_FINISH: 2, - BROTLI_OPERATION_EMIT_METADATA: 3, - BROTLI_PARAM_MODE: 0, - BROTLI_MODE_GENERIC: 0, - BROTLI_MODE_TEXT: 1, - BROTLI_MODE_FONT: 2, - BROTLI_DEFAULT_MODE: 0, - BROTLI_PARAM_QUALITY: 1, - BROTLI_MIN_QUALITY: 0, - BROTLI_MAX_QUALITY: 11, - BROTLI_DEFAULT_QUALITY: 11, - BROTLI_PARAM_LGWIN: 2, - BROTLI_MIN_WINDOW_BITS: 10, - BROTLI_MAX_WINDOW_BITS: 24, - BROTLI_LARGE_MAX_WINDOW_BITS: 30, - BROTLI_DEFAULT_WINDOW: 22, - BROTLI_PARAM_LGBLOCK: 3, - BROTLI_MIN_INPUT_BLOCK_BITS: 16, - BROTLI_MAX_INPUT_BLOCK_BITS: 24, - BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING: 4, - BROTLI_PARAM_SIZE_HINT: 5, - BROTLI_PARAM_LARGE_WINDOW: 6, - BROTLI_PARAM_NPOSTFIX: 7, - BROTLI_PARAM_NDIRECT: 8, - BROTLI_DECODER_RESULT_ERROR: 0, - BROTLI_DECODER_RESULT_SUCCESS: 1, - BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT: 2, - BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT: 3, - BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION: 0, - BROTLI_DECODER_PARAM_LARGE_WINDOW: 1, - BROTLI_DECODER_NO_ERROR: 0, - BROTLI_DECODER_SUCCESS: 1, - BROTLI_DECODER_NEEDS_MORE_INPUT: 2, - BROTLI_DECODER_NEEDS_MORE_OUTPUT: 3, - BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE: -1, - BROTLI_DECODER_ERROR_FORMAT_RESERVED: -2, - BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE: -3, - BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET: -4, - BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME: -5, - BROTLI_DECODER_ERROR_FORMAT_CL_SPACE: -6, - BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE: -7, - BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT: -8, - BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1: -9, - BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2: -10, - BROTLI_DECODER_ERROR_FORMAT_TRANSFORM: -11, - BROTLI_DECODER_ERROR_FORMAT_DICTIONARY: -12, - BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS: -13, - BROTLI_DECODER_ERROR_FORMAT_PADDING_1: -14, - BROTLI_DECODER_ERROR_FORMAT_PADDING_2: -15, - BROTLI_DECODER_ERROR_FORMAT_DISTANCE: -16, - BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET: -19, - BROTLI_DECODER_ERROR_INVALID_ARGUMENTS: -20, - BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES: -21, - BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS: -22, - BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP: -25, - BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1: -26, - BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2: -27, - BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES: -30, - BROTLI_DECODER_ERROR_UNREACHABLE: -31, - // Zstd flush modes / end directives - ZSTD_e_continue: 0, - ZSTD_e_flush: 1, - ZSTD_e_end: 2, - // Zstd compression parameters - ZSTD_c_compressionLevel: 100, - ZSTD_c_windowLog: 101, - ZSTD_c_hashLog: 102, - ZSTD_c_chainLog: 103, - ZSTD_c_searchLog: 104, - ZSTD_c_minMatch: 105, - ZSTD_c_targetLength: 106, - ZSTD_c_strategy: 107, - ZSTD_c_enableLongDistanceMatching: 160, - ZSTD_c_ldmHashLog: 161, - ZSTD_c_ldmMinMatch: 162, - ZSTD_c_ldmBucketSizeLog: 163, - ZSTD_c_ldmHashRateLog: 164, - ZSTD_c_contentSizeFlag: 200, - ZSTD_c_checksumFlag: 201, - ZSTD_c_dictIDFlag: 202, - ZSTD_c_nbWorkers: 240, - ZSTD_c_jobSize: 241, - ZSTD_c_overlapLog: 242, - // Zstd decompression parameters - ZSTD_d_windowLogMax: 100, - // Zstd strategy constants - ZSTD_fast: 1, - ZSTD_dfast: 2, - ZSTD_greedy: 3, - ZSTD_lazy: 4, - ZSTD_lazy2: 5, - ZSTD_btlazy2: 6, - ZSTD_btopt: 7, - ZSTD_btultra: 8, - ZSTD_btultra2: 9, -} as const; -const trace = { - TRACE_EVENT_PHASE_BEGIN: 66, - TRACE_EVENT_PHASE_END: 69, - TRACE_EVENT_PHASE_COMPLETE: 88, - TRACE_EVENT_PHASE_INSTANT: 73, - TRACE_EVENT_PHASE_ASYNC_BEGIN: 83, - TRACE_EVENT_PHASE_ASYNC_STEP_INTO: 84, - TRACE_EVENT_PHASE_ASYNC_STEP_PAST: 112, - TRACE_EVENT_PHASE_ASYNC_END: 70, - TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN: 98, - TRACE_EVENT_PHASE_NESTABLE_ASYNC_END: 101, - TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT: 110, - TRACE_EVENT_PHASE_FLOW_BEGIN: 115, - TRACE_EVENT_PHASE_FLOW_STEP: 116, - TRACE_EVENT_PHASE_FLOW_END: 102, - TRACE_EVENT_PHASE_METADATA: 77, - TRACE_EVENT_PHASE_COUNTER: 67, - TRACE_EVENT_PHASE_SAMPLE: 80, - TRACE_EVENT_PHASE_CREATE_OBJECT: 78, - TRACE_EVENT_PHASE_SNAPSHOT_OBJECT: 79, - TRACE_EVENT_PHASE_DELETE_OBJECT: 68, - TRACE_EVENT_PHASE_MEMORY_DUMP: 118, - TRACE_EVENT_PHASE_MARK: 82, - TRACE_EVENT_PHASE_CLOCK_SYNC: 99, - TRACE_EVENT_PHASE_ENTER_CONTEXT: 40, - TRACE_EVENT_PHASE_LEAVE_CONTEXT: 41, - TRACE_EVENT_PHASE_LINK_IDS: 61, -} as const; - -return { os, fs, crypto, zlib, trace }; +const { op_node_internal_binding_constants } = __bootstrap.core.ops; +return op_node_internal_binding_constants(); })(); diff --git a/ext/node/polyfills/internal_binding/crypto.ts b/ext/node/polyfills/internal_binding/crypto.ts index 53b93d7e3be6a8..96b53c668dd73c 100644 --- a/ext/node/polyfills/internal_binding/crypto.ts +++ b/ext/node/polyfills/internal_binding/crypto.ts @@ -2,20 +2,6 @@ // Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. (function () { -const { core, primordials } = __bootstrap; -const { timingSafeEqual } = core.loadExtScript( - "ext:deno_node/internal_binding/_timingSafeEqual.ts", -); - -const { Error } = primordials; - -function getFipsCrypto(): boolean { - return false; -} - -function setFipsCrypto(_fips: boolean) { - throw new Error("FIPS mode is not supported in Deno."); -} - -return { timingSafeEqual, getFipsCrypto, setFipsCrypto }; +const { op_node_internal_binding_crypto } = __bootstrap.core.ops; +return op_node_internal_binding_crypto(); })(); diff --git a/ext/node/polyfills/internal_binding/uv.ts b/ext/node/polyfills/internal_binding/uv.ts index 907dc800294c35..6027a145ea2fae 100644 --- a/ext/node/polyfills/internal_binding/uv.ts +++ b/ext/node/polyfills/internal_binding/uv.ts @@ -1,611 +1,6 @@ // Copyright 2018-2026 the Deno authors. MIT license. -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -// This module ports: -// - https://github.com/nodejs/node/blob/master/src/uv.cc -// - https://github.com/nodejs/node/blob/master/deps/uv -// -// See also: http://docs.libuv.org/en/v1.x/errors.html#error-constants (function () { -const { core, primordials } = __bootstrap; -const { osType } = core.loadExtScript("ext:deno_node/_util/os.ts"); -const { uvTranslateSysError } = core.loadExtScript( - "ext:deno_node/internal_binding/_libuv_winerror.ts", -); -const { - ArrayPrototypeMap, - Error, - MapPrototypeGet, -} = primordials; - -// In Node these values are coming from libuv: -// Ref: https://github.com/libuv/libuv/blob/v1.x/include/uv/errno.h -// Ref: https://github.com/nodejs/node/blob/524123fbf064ff64bb6fcd83485cfc27db932f68/lib/internal/errors.js#L383 -// Since there is no easy way to port code from libuv and these maps are -// changing very rarely, we simply extract them from Node and store here. - -// Note -// Run the following to get the map: -// $ node -e "console.log(process.binding('uv').getErrorMap())" -// This setup automatically exports maps from both "win", "linux" & darwin: -// https://github.com/schwarzkopfb/node_errno_map - -type ErrorMapData = Array<[number, [string, string]]>; -type CodeMapData = Array<[string, number]>; - -const codeToErrorWindows: ErrorMapData = [ - [-4093, ["E2BIG", "argument list too long"]], - [-4092, ["EACCES", "permission denied"]], - [-4091, ["EADDRINUSE", "address already in use"]], - [-4090, ["EADDRNOTAVAIL", "address not available"]], - [-4089, ["EAFNOSUPPORT", "address family not supported"]], - [-4088, ["EAGAIN", "resource temporarily unavailable"]], - [-3000, ["EAI_ADDRFAMILY", "address family not supported"]], - [-3001, ["EAI_AGAIN", "temporary failure"]], - [-3002, ["EAI_BADFLAGS", "bad ai_flags value"]], - [-3013, ["EAI_BADHINTS", "invalid value for hints"]], - [-3003, ["EAI_CANCELED", "request canceled"]], - [-3004, ["EAI_FAIL", "permanent failure"]], - [-3005, ["EAI_FAMILY", "ai_family not supported"]], - [-3006, ["EAI_MEMORY", "out of memory"]], - [-3007, ["EAI_NODATA", "no address"]], - [-3008, ["EAI_NONAME", "unknown node or service"]], - [-3009, ["EAI_OVERFLOW", "argument buffer overflow"]], - [-3014, ["EAI_PROTOCOL", "resolved protocol is unknown"]], - [-3010, ["EAI_SERVICE", "service not available for socket type"]], - [-3011, ["EAI_SOCKTYPE", "socket type not supported"]], - [-4084, ["EALREADY", "connection already in progress"]], - [-4083, ["EBADF", "bad file descriptor"]], - [-4082, ["EBUSY", "resource busy or locked"]], - [-4081, ["ECANCELED", "operation canceled"]], - [-4080, ["ECHARSET", "invalid Unicode character"]], - [-4079, ["ECONNABORTED", "software caused connection abort"]], - [-4078, ["ECONNREFUSED", "connection refused"]], - [-4077, ["ECONNRESET", "connection reset by peer"]], - [-4076, ["EDESTADDRREQ", "destination address required"]], - [-4075, ["EEXIST", "file already exists"]], - [-4074, ["EFAULT", "bad address in system call argument"]], - [-4036, ["EFBIG", "file too large"]], - [-4073, ["EHOSTUNREACH", "host is unreachable"]], - [-4072, ["EINTR", "interrupted system call"]], - [-4071, ["EINVAL", "invalid argument"]], - [-4070, ["EIO", "i/o error"]], - [-4069, ["EISCONN", "socket is already connected"]], - [-4068, ["EISDIR", "illegal operation on a directory"]], - [-4067, ["ELOOP", "too many symbolic links encountered"]], - [-4066, ["EMFILE", "too many open files"]], - [-4065, ["EMSGSIZE", "message too long"]], - [-4064, ["ENAMETOOLONG", "name too long"]], - [-4063, ["ENETDOWN", "network is down"]], - [-4062, ["ENETUNREACH", "network is unreachable"]], - [-4061, ["ENFILE", "file table overflow"]], - [-4060, ["ENOBUFS", "no buffer space available"]], - [-4059, ["ENODEV", "no such device"]], - [-4058, ["ENOENT", "no such file or directory"]], - [-4057, ["ENOMEM", "not enough memory"]], - [-4056, ["ENONET", "machine is not on the network"]], - [-4035, ["ENOPROTOOPT", "protocol not available"]], - [-4055, ["ENOSPC", "no space left on device"]], - [-4054, ["ENOSYS", "function not implemented"]], - [-4053, ["ENOTCONN", "socket is not connected"]], - [-4052, ["ENOTDIR", "not a directory"]], - [-4051, ["ENOTEMPTY", "directory not empty"]], - [-4050, ["ENOTSOCK", "socket operation on non-socket"]], - [-4049, ["ENOTSUP", "operation not supported on socket"]], - [-4048, ["EPERM", "operation not permitted"]], - [-4047, ["EPIPE", "broken pipe"]], - [-4046, ["EPROTO", "protocol error"]], - [-4045, ["EPROTONOSUPPORT", "protocol not supported"]], - [-4044, ["EPROTOTYPE", "protocol wrong type for socket"]], - [-4034, ["ERANGE", "result too large"]], - [-4043, ["EROFS", "read-only file system"]], - [-4042, ["ESHUTDOWN", "cannot send after transport endpoint shutdown"]], - [-4041, ["ESPIPE", "invalid seek"]], - [-4040, ["ESRCH", "no such process"]], - [-4039, ["ETIMEDOUT", "connection timed out"]], - [-4038, ["ETXTBSY", "text file is busy"]], - [-4037, ["EXDEV", "cross-device link not permitted"]], - [-4094, ["UNKNOWN", "unknown error"]], - [-4095, ["EOF", "end of file"]], - [-4033, ["ENXIO", "no such device or address"]], - [-4032, ["EMLINK", "too many links"]], - [-4031, ["EHOSTDOWN", "host is down"]], - [-4030, ["EREMOTEIO", "remote I/O error"]], - [-4029, ["ENOTTY", "inappropriate ioctl for device"]], - [-4028, ["EFTYPE", "inappropriate file type or format"]], - [-4027, ["EILSEQ", "illegal byte sequence"]], -]; - -const errorToCodeWindows: CodeMapData = ArrayPrototypeMap( - codeToErrorWindows, - (entry) => [entry[1][0], entry[0]], -); - -const codeToErrorDarwin: ErrorMapData = [ - [-7, ["E2BIG", "argument list too long"]], - [-13, ["EACCES", "permission denied"]], - [-48, ["EADDRINUSE", "address already in use"]], - [-49, ["EADDRNOTAVAIL", "address not available"]], - [-47, ["EAFNOSUPPORT", "address family not supported"]], - [-35, ["EAGAIN", "resource temporarily unavailable"]], - [-3000, ["EAI_ADDRFAMILY", "address family not supported"]], - [-3001, ["EAI_AGAIN", "temporary failure"]], - [-3002, ["EAI_BADFLAGS", "bad ai_flags value"]], - [-3013, ["EAI_BADHINTS", "invalid value for hints"]], - [-3003, ["EAI_CANCELED", "request canceled"]], - [-3004, ["EAI_FAIL", "permanent failure"]], - [-3005, ["EAI_FAMILY", "ai_family not supported"]], - [-3006, ["EAI_MEMORY", "out of memory"]], - [-3007, ["EAI_NODATA", "no address"]], - [-3008, ["EAI_NONAME", "unknown node or service"]], - [-3009, ["EAI_OVERFLOW", "argument buffer overflow"]], - [-3014, ["EAI_PROTOCOL", "resolved protocol is unknown"]], - [-3010, ["EAI_SERVICE", "service not available for socket type"]], - [-3011, ["EAI_SOCKTYPE", "socket type not supported"]], - [-37, ["EALREADY", "connection already in progress"]], - [-9, ["EBADF", "bad file descriptor"]], - [-16, ["EBUSY", "resource busy or locked"]], - [-89, ["ECANCELED", "operation canceled"]], - [-4080, ["ECHARSET", "invalid Unicode character"]], - [-53, ["ECONNABORTED", "software caused connection abort"]], - [-61, ["ECONNREFUSED", "connection refused"]], - [-54, ["ECONNRESET", "connection reset by peer"]], - [-39, ["EDESTADDRREQ", "destination address required"]], - [-17, ["EEXIST", "file already exists"]], - [-14, ["EFAULT", "bad address in system call argument"]], - [-27, ["EFBIG", "file too large"]], - [-65, ["EHOSTUNREACH", "host is unreachable"]], - [-4, ["EINTR", "interrupted system call"]], - [-22, ["EINVAL", "invalid argument"]], - [-5, ["EIO", "i/o error"]], - [-56, ["EISCONN", "socket is already connected"]], - [-21, ["EISDIR", "illegal operation on a directory"]], - [-62, ["ELOOP", "too many symbolic links encountered"]], - [-24, ["EMFILE", "too many open files"]], - [-40, ["EMSGSIZE", "message too long"]], - [-63, ["ENAMETOOLONG", "name too long"]], - [-50, ["ENETDOWN", "network is down"]], - [-51, ["ENETUNREACH", "network is unreachable"]], - [-23, ["ENFILE", "file table overflow"]], - [-55, ["ENOBUFS", "no buffer space available"]], - [-19, ["ENODEV", "no such device"]], - [-2, ["ENOENT", "no such file or directory"]], - [-12, ["ENOMEM", "not enough memory"]], - [-4056, ["ENONET", "machine is not on the network"]], - [-42, ["ENOPROTOOPT", "protocol not available"]], - [-28, ["ENOSPC", "no space left on device"]], - [-78, ["ENOSYS", "function not implemented"]], - [-57, ["ENOTCONN", "socket is not connected"]], - [-20, ["ENOTDIR", "not a directory"]], - [-66, ["ENOTEMPTY", "directory not empty"]], - [-38, ["ENOTSOCK", "socket operation on non-socket"]], - [-45, ["ENOTSUP", "operation not supported on socket"]], - [-1, ["EPERM", "operation not permitted"]], - [-32, ["EPIPE", "broken pipe"]], - [-100, ["EPROTO", "protocol error"]], - [-43, ["EPROTONOSUPPORT", "protocol not supported"]], - [-41, ["EPROTOTYPE", "protocol wrong type for socket"]], - [-34, ["ERANGE", "result too large"]], - [-30, ["EROFS", "read-only file system"]], - [-58, ["ESHUTDOWN", "cannot send after transport endpoint shutdown"]], - [-29, ["ESPIPE", "invalid seek"]], - [-3, ["ESRCH", "no such process"]], - [-60, ["ETIMEDOUT", "connection timed out"]], - [-26, ["ETXTBSY", "text file is busy"]], - [-18, ["EXDEV", "cross-device link not permitted"]], - [-4094, ["UNKNOWN", "unknown error"]], - [-4095, ["EOF", "end of file"]], - [-6, ["ENXIO", "no such device or address"]], - [-31, ["EMLINK", "too many links"]], - [-64, ["EHOSTDOWN", "host is down"]], - [-4030, ["EREMOTEIO", "remote I/O error"]], - [-25, ["ENOTTY", "inappropriate ioctl for device"]], - [-79, ["EFTYPE", "inappropriate file type or format"]], - [-92, ["EILSEQ", "illegal byte sequence"]], -]; - -const errorToCodeDarwin: CodeMapData = ArrayPrototypeMap( - codeToErrorDarwin, - (entry) => [entry[1][0], entry[0]], -); - -const codeToErrorLinux: ErrorMapData = [ - [-7, ["E2BIG", "argument list too long"]], - [-13, ["EACCES", "permission denied"]], - [-98, ["EADDRINUSE", "address already in use"]], - [-99, ["EADDRNOTAVAIL", "address not available"]], - [-97, ["EAFNOSUPPORT", "address family not supported"]], - [-11, ["EAGAIN", "resource temporarily unavailable"]], - [-3000, ["EAI_ADDRFAMILY", "address family not supported"]], - [-3001, ["EAI_AGAIN", "temporary failure"]], - [-3002, ["EAI_BADFLAGS", "bad ai_flags value"]], - [-3013, ["EAI_BADHINTS", "invalid value for hints"]], - [-3003, ["EAI_CANCELED", "request canceled"]], - [-3004, ["EAI_FAIL", "permanent failure"]], - [-3005, ["EAI_FAMILY", "ai_family not supported"]], - [-3006, ["EAI_MEMORY", "out of memory"]], - [-3007, ["EAI_NODATA", "no address"]], - [-3008, ["EAI_NONAME", "unknown node or service"]], - [-3009, ["EAI_OVERFLOW", "argument buffer overflow"]], - [-3014, ["EAI_PROTOCOL", "resolved protocol is unknown"]], - [-3010, ["EAI_SERVICE", "service not available for socket type"]], - [-3011, ["EAI_SOCKTYPE", "socket type not supported"]], - [-114, ["EALREADY", "connection already in progress"]], - [-9, ["EBADF", "bad file descriptor"]], - [-16, ["EBUSY", "resource busy or locked"]], - [-125, ["ECANCELED", "operation canceled"]], - [-4080, ["ECHARSET", "invalid Unicode character"]], - [-103, ["ECONNABORTED", "software caused connection abort"]], - [-111, ["ECONNREFUSED", "connection refused"]], - [-104, ["ECONNRESET", "connection reset by peer"]], - [-89, ["EDESTADDRREQ", "destination address required"]], - [-17, ["EEXIST", "file already exists"]], - [-14, ["EFAULT", "bad address in system call argument"]], - [-27, ["EFBIG", "file too large"]], - [-113, ["EHOSTUNREACH", "host is unreachable"]], - [-4, ["EINTR", "interrupted system call"]], - [-22, ["EINVAL", "invalid argument"]], - [-5, ["EIO", "i/o error"]], - [-106, ["EISCONN", "socket is already connected"]], - [-21, ["EISDIR", "illegal operation on a directory"]], - [-40, ["ELOOP", "too many symbolic links encountered"]], - [-24, ["EMFILE", "too many open files"]], - [-90, ["EMSGSIZE", "message too long"]], - [-36, ["ENAMETOOLONG", "name too long"]], - [-100, ["ENETDOWN", "network is down"]], - [-101, ["ENETUNREACH", "network is unreachable"]], - [-23, ["ENFILE", "file table overflow"]], - [-105, ["ENOBUFS", "no buffer space available"]], - [-19, ["ENODEV", "no such device"]], - [-2, ["ENOENT", "no such file or directory"]], - [-12, ["ENOMEM", "not enough memory"]], - [-64, ["ENONET", "machine is not on the network"]], - [-92, ["ENOPROTOOPT", "protocol not available"]], - [-28, ["ENOSPC", "no space left on device"]], - [-38, ["ENOSYS", "function not implemented"]], - [-107, ["ENOTCONN", "socket is not connected"]], - [-20, ["ENOTDIR", "not a directory"]], - [-39, ["ENOTEMPTY", "directory not empty"]], - [-88, ["ENOTSOCK", "socket operation on non-socket"]], - [-95, ["ENOTSUP", "operation not supported on socket"]], - [-1, ["EPERM", "operation not permitted"]], - [-32, ["EPIPE", "broken pipe"]], - [-71, ["EPROTO", "protocol error"]], - [-93, ["EPROTONOSUPPORT", "protocol not supported"]], - [-91, ["EPROTOTYPE", "protocol wrong type for socket"]], - [-34, ["ERANGE", "result too large"]], - [-30, ["EROFS", "read-only file system"]], - [-108, ["ESHUTDOWN", "cannot send after transport endpoint shutdown"]], - [-29, ["ESPIPE", "invalid seek"]], - [-3, ["ESRCH", "no such process"]], - [-110, ["ETIMEDOUT", "connection timed out"]], - [-26, ["ETXTBSY", "text file is busy"]], - [-18, ["EXDEV", "cross-device link not permitted"]], - [-4094, ["UNKNOWN", "unknown error"]], - [-4095, ["EOF", "end of file"]], - [-6, ["ENXIO", "no such device or address"]], - [-31, ["EMLINK", "too many links"]], - [-112, ["EHOSTDOWN", "host is down"]], - [-121, ["EREMOTEIO", "remote I/O error"]], - [-25, ["ENOTTY", "inappropriate ioctl for device"]], - [-4028, ["EFTYPE", "inappropriate file type or format"]], - [-84, ["EILSEQ", "illegal byte sequence"]], -]; - -const errorToCodeLinux: CodeMapData = ArrayPrototypeMap( - codeToErrorLinux, - (entry) => [entry[1][0], entry[0]], -); - -const codeToErrorFreebsd: ErrorMapData = [ - [-7, ["E2BIG", "argument list too long"]], - [-13, ["EACCES", "permission denied"]], - [-48, ["EADDRINUSE", "address already in use"]], - [-49, ["EADDRNOTAVAIL", "address not available"]], - [-47, ["EAFNOSUPPORT", "address family not supported"]], - [-35, ["EAGAIN", "resource temporarily unavailable"]], - [-3000, ["EAI_ADDRFAMILY", "address family not supported"]], - [-3001, ["EAI_AGAIN", "temporary failure"]], - [-3002, ["EAI_BADFLAGS", "bad ai_flags value"]], - [-3013, ["EAI_BADHINTS", "invalid value for hints"]], - [-3003, ["EAI_CANCELED", "request canceled"]], - [-3004, ["EAI_FAIL", "permanent failure"]], - [-3005, ["EAI_FAMILY", "ai_family not supported"]], - [-3006, ["EAI_MEMORY", "out of memory"]], - [-3007, ["EAI_NODATA", "no address"]], - [-3008, ["EAI_NONAME", "unknown node or service"]], - [-3009, ["EAI_OVERFLOW", "argument buffer overflow"]], - [-3014, ["EAI_PROTOCOL", "resolved protocol is unknown"]], - [-3010, ["EAI_SERVICE", "service not available for socket type"]], - [-3011, ["EAI_SOCKTYPE", "socket type not supported"]], - [-37, ["EALREADY", "connection already in progress"]], - [-9, ["EBADF", "bad file descriptor"]], - [-16, ["EBUSY", "resource busy or locked"]], - [-85, ["ECANCELED", "operation canceled"]], - [-4080, ["ECHARSET", "invalid Unicode character"]], - [-53, ["ECONNABORTED", "software caused connection abort"]], - [-61, ["ECONNREFUSED", "connection refused"]], - [-54, ["ECONNRESET", "connection reset by peer"]], - [-39, ["EDESTADDRREQ", "destination address required"]], - [-17, ["EEXIST", "file already exists"]], - [-14, ["EFAULT", "bad address in system call argument"]], - [-27, ["EFBIG", "file too large"]], - [-65, ["EHOSTUNREACH", "host is unreachable"]], - [-4, ["EINTR", "interrupted system call"]], - [-22, ["EINVAL", "invalid argument"]], - [-5, ["EIO", "i/o error"]], - [-56, ["EISCONN", "socket is already connected"]], - [-21, ["EISDIR", "illegal operation on a directory"]], - [-62, ["ELOOP", "too many symbolic links encountered"]], - [-24, ["EMFILE", "too many open files"]], - [-40, ["EMSGSIZE", "message too long"]], - [-63, ["ENAMETOOLONG", "name too long"]], - [-50, ["ENETDOWN", "network is down"]], - [-51, ["ENETUNREACH", "network is unreachable"]], - [-23, ["ENFILE", "file table overflow"]], - [-55, ["ENOBUFS", "no buffer space available"]], - [-19, ["ENODEV", "no such device"]], - [-2, ["ENOENT", "no such file or directory"]], - [-12, ["ENOMEM", "not enough memory"]], - [-4056, ["ENONET", "machine is not on the network"]], - [-42, ["ENOPROTOOPT", "protocol not available"]], - [-28, ["ENOSPC", "no space left on device"]], - [-78, ["ENOSYS", "function not implemented"]], - [-57, ["ENOTCONN", "socket is not connected"]], - [-20, ["ENOTDIR", "not a directory"]], - [-66, ["ENOTEMPTY", "directory not empty"]], - [-38, ["ENOTSOCK", "socket operation on non-socket"]], - [-45, ["ENOTSUP", "operation not supported on socket"]], - [-84, ["EOVERFLOW", "value too large for defined data type"]], - [-1, ["EPERM", "operation not permitted"]], - [-32, ["EPIPE", "broken pipe"]], - [-92, ["EPROTO", "protocol error"]], - [-43, ["EPROTONOSUPPORT", "protocol not supported"]], - [-41, ["EPROTOTYPE", "protocol wrong type for socket"]], - [-34, ["ERANGE", "result too large"]], - [-30, ["EROFS", "read-only file system"]], - [-58, ["ESHUTDOWN", "cannot send after transport endpoint shutdown"]], - [-29, ["ESPIPE", "invalid seek"]], - [-3, ["ESRCH", "no such process"]], - [-60, ["ETIMEDOUT", "connection timed out"]], - [-26, ["ETXTBSY", "text file is busy"]], - [-18, ["EXDEV", "cross-device link not permitted"]], - [-4094, ["UNKNOWN", "unknown error"]], - [-4095, ["EOF", "end of file"]], - [-6, ["ENXIO", "no such device or address"]], - [-31, ["EMLINK", "too many links"]], - [-64, ["EHOSTDOWN", "host is down"]], - [-4030, ["EREMOTEIO", "remote I/O error"]], - [-25, ["ENOTTY", "inappropriate ioctl for device"]], - [-79, ["EFTYPE", "inappropriate file type or format"]], - [-86, ["EILSEQ", "illegal byte sequence"]], - [-44, ["ESOCKTNOSUPPORT", "socket type not supported"]], -]; - -const errorToCodeFreebsd: CodeMapData = ArrayPrototypeMap( - codeToErrorFreebsd, - (entry) => [entry[1][0], entry[0]], -); - -const codeToErrorOpenBSD: ErrorMapData = [ - [-7, ["E2BIG", "argument list too long"]], - [-13, ["EACCES", "permission denied"]], - [-48, ["EADDRINUSE", "address already in use"]], - [-49, ["EADDRNOTAVAIL", "address not available"]], - [-47, ["EAFNOSUPPORT", "address family not supported"]], - [-35, ["EAGAIN", "resource temporarily unavailable"]], - [-3000, ["EAI_ADDRFAMILY", "address family not supported"]], - [-3001, ["EAI_AGAIN", "temporary failure"]], - [-3002, ["EAI_BADFLAGS", "bad ai_flags value"]], - [-3013, ["EAI_BADHINTS", "invalid value for hints"]], - [-3003, ["EAI_CANCELED", "request canceled"]], - [-3004, ["EAI_FAIL", "permanent failure"]], - [-3005, ["EAI_FAMILY", "ai_family not supported"]], - [-3006, ["EAI_MEMORY", "out of memory"]], - [-3007, ["EAI_NODATA", "no address"]], - [-3008, ["EAI_NONAME", "unknown node or service"]], - [-3009, ["EAI_OVERFLOW", "argument buffer overflow"]], - [-3014, ["EAI_PROTOCOL", "resolved protocol is unknown"]], - [-3010, ["EAI_SERVICE", "service not available for socket type"]], - [-3011, ["EAI_SOCKTYPE", "socket type not supported"]], - [-37, ["EALREADY", "connection already in progress"]], - [-9, ["EBADF", "bad file descriptor"]], - [-16, ["EBUSY", "resource busy or locked"]], - [-88, ["ECANCELED", "operation canceled"]], - [-4080, ["ECHARSET", "invalid Unicode character"]], - [-53, ["ECONNABORTED", "software caused connection abort"]], - [-61, ["ECONNREFUSED", "connection refused"]], - [-54, ["ECONNRESET", "connection reset by peer"]], - [-39, ["EDESTADDRREQ", "destination address required"]], - [-17, ["EEXIST", "file already exists"]], - [-14, ["EFAULT", "bad address in system call argument"]], - [-27, ["EFBIG", "file too large"]], - [-65, ["EHOSTUNREACH", "host is unreachable"]], - [-4, ["EINTR", "interrupted system call"]], - [-22, ["EINVAL", "invalid argument"]], - [-5, ["EIO", "i/o error"]], - [-56, ["EISCONN", "socket is already connected"]], - [-21, ["EISDIR", "illegal operation on a directory"]], - [-62, ["ELOOP", "too many symbolic links encountered"]], - [-24, ["EMFILE", "too many open files"]], - [-40, ["EMSGSIZE", "message too long"]], - [-63, ["ENAMETOOLONG", "name too long"]], - [-50, ["ENETDOWN", "network is down"]], - [-51, ["ENETUNREACH", "network is unreachable"]], - [-23, ["ENFILE", "file table overflow"]], - [-55, ["ENOBUFS", "no buffer space available"]], - [-19, ["ENODEV", "no such device"]], - [-2, ["ENOENT", "no such file or directory"]], - [-12, ["ENOMEM", "not enough memory"]], - [-4056, ["ENONET", "machine is not on the network"]], - [-42, ["ENOPROTOOPT", "protocol not available"]], - [-28, ["ENOSPC", "no space left on device"]], - [-78, ["ENOSYS", "function not implemented"]], - [-57, ["ENOTCONN", "socket is not connected"]], - [-20, ["ENOTDIR", "not a directory"]], - [-66, ["ENOTEMPTY", "directory not empty"]], - [-38, ["ENOTSOCK", "socket operation on non-socket"]], - [-45, ["ENOTSUP", "operation not supported on socket"]], - [-87, ["EOVERFLOW", "value too large for defined data type"]], - [-1, ["EPERM", "operation not permitted"]], - [-32, ["EPIPE", "broken pipe"]], - [-95, ["EPROTO", "protocol error"]], - [-43, ["EPROTONOSUPPORT", "protocol not supported"]], - [-41, ["EPROTOTYPE", "protocol wrong type for socket"]], - [-34, ["ERANGE", "result too large"]], - [-30, ["EROFS", "read-only file system"]], - [-58, ["ESHUTDOWN", "cannot send after transport endpoint shutdown"]], - [-29, ["ESPIPE", "invalid seek"]], - [-3, ["ESRCH", "no such process"]], - [-60, ["ETIMEDOUT", "connection timed out"]], - [-26, ["ETXTBSY", "text file is busy"]], - [-18, ["EXDEV", "cross-device link not permitted"]], - [-4094, ["UNKNOWN", "unknown error"]], - [-4095, ["EOF", "end of file"]], - [-6, ["ENXIO", "no such device or address"]], - [-31, ["EMLINK", "too many links"]], - [-64, ["EHOSTDOWN", "host is down"]], - [-4030, ["EREMOTEIO", "remote I/O error"]], - [-25, ["ENOTTY", "inappropriate ioctl for device"]], - [-79, ["EFTYPE", "inappropriate file type or format"]], - [-84, ["EILSEQ", "illegal byte sequence"]], - [-44, ["ESOCKTNOSUPPORT", "socket type not supported"]], -]; - -const errorToCodeOpenBSD: CodeMapData = ArrayPrototypeMap( - codeToErrorOpenBSD, - (entry) => [entry[1][0], entry[0]], -); - -const unreachable = () => { - throw new Error("Unreachable code"); -}; - -// Must be a real Map (not SafeMap): it is returned to userland via -// getErrorMap() / process.binding("uv").getErrorMap() and must pass -// `instanceof Map` (SafeMap's prototype chain does not include Map). -// deno-lint-ignore prefer-primordials -const errorMap = new Map( - osType === "windows" - ? codeToErrorWindows - : osType === "darwin" - ? codeToErrorDarwin - : osType === "linux" - ? codeToErrorLinux - : osType === "android" - ? codeToErrorLinux - : osType === "freebsd" - ? codeToErrorFreebsd - : osType === "openbsd" - ? codeToErrorOpenBSD - : unreachable(), -); - -// Real Map (not SafeMap): returned to userland via getCodeMap() / -// process.binding("uv").getCodeMap(); must pass `instanceof Map`. -// deno-lint-ignore prefer-primordials -const codeMap = new Map( - osType === "windows" - ? errorToCodeWindows - : osType === "darwin" - ? errorToCodeDarwin - : osType === "linux" - ? errorToCodeLinux - : osType === "android" - ? errorToCodeLinux - : osType === "freebsd" - ? errorToCodeFreebsd - : osType === "openbsd" - ? errorToCodeOpenBSD - : unreachable(), -); - -function mapSysErrnoToUvErrno(sysErrno: number): number { - if (osType === "windows") { - const code = uvTranslateSysError(sysErrno); - return MapPrototypeGet(codeMap, code) ?? -sysErrno; - } else { - return -sysErrno; - } -} - -const UV_EAI_MEMORY = MapPrototypeGet(codeMap, "EAI_MEMORY")!; -const UV_EBADF = MapPrototypeGet(codeMap, "EBADF")!; -const UV_ECANCELED = MapPrototypeGet(codeMap, "ECANCELED")!; -const UV_EEXIST = MapPrototypeGet(codeMap, "EEXIST"); -const UV_EINVAL = MapPrototypeGet(codeMap, "EINVAL")!; -const UV_ENETUNREACH = MapPrototypeGet(codeMap, "ENETUNREACH")!; -const UV_ENOENT = MapPrototypeGet(codeMap, "ENOENT"); -const UV_ENOMEM = MapPrototypeGet(codeMap, "ENOMEM")!; -const UV_ENOTSOCK = MapPrototypeGet(codeMap, "ENOTSOCK")!; -const UV_ETIMEDOUT = MapPrototypeGet(codeMap, "ETIMEDOUT")!; -const UV_UNKNOWN = MapPrototypeGet(codeMap, "UNKNOWN")!; -const UV_EOF = MapPrototypeGet(codeMap, "EOF")!; - -function errname(errno: number): string { - const err = MapPrototypeGet(errorMap, errno); - if (err) { - return err[0]; - } - return `UNKNOWN (${errno})`; -} - -function getErrorMessage(errno: number): string { - const err = MapPrototypeGet(errorMap, errno); - - if (err) { - return err[1]; - } - return `UNKNOWN (${errno})`; -} - -function getErrorMap(): Map { - return errorMap; -} - -function getCodeMap(): Map { - return codeMap; -} - -return { - errorMap, - codeMap, - mapSysErrnoToUvErrno, - UV_EAI_MEMORY, - UV_EBADF, - UV_ECANCELED, - UV_EEXIST, - UV_EINVAL, - UV_ENETUNREACH, - UV_ENOENT, - UV_ENOMEM, - UV_ENOTSOCK, - UV_ETIMEDOUT, - UV_UNKNOWN, - UV_EOF, - errname, - getErrorMessage, - getErrorMap, - getCodeMap, -}; +const { op_node_internal_binding_uv } = __bootstrap.core.ops; +return op_node_internal_binding_uv(); })(); From 67dd7724b441cbd41ec614d463328138fb5369da Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 11:41:41 -0700 Subject: [PATCH 03/33] port buffer and util internal bindings to rust --- ext/node/lib.rs | 10 + ext/node/ops/buffer.rs | 327 ++++++++++++++++++ ext/node/ops/internal_binding.rs | 50 +++ ext/node/ops/util.rs | 276 +++++++++++++++ ext/node/polyfills/internal_binding/buffer.ts | 194 +---------- .../internal_binding/string_decoder.ts | 23 +- .../polyfills/internal_binding/symbols.ts | 12 +- ext/node/polyfills/internal_binding/util.ts | 116 +------ 8 files changed, 672 insertions(+), 336 deletions(-) diff --git a/ext/node/lib.rs b/ext/node/lib.rs index cacbdfb13ec07c..c7791ebfc046ad 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -320,11 +320,14 @@ deno_core::extension!(deno_node, ops::idna::op_node_idna_punycode_to_unicode, ops::idna::op_node_idna_punycode_decode, ops::idna::op_node_idna_punycode_encode, + ops::buffer::op_node_internal_binding_buffer, ops::internal_binding::op_node_internal_binding_ares, ops::internal_binding::op_node_internal_binding_encodings, ops::internal_binding::op_node_internal_binding_handle_wrap, ops::internal_binding::op_node_internal_binding_inspector, ops::internal_binding::op_node_internal_binding_libuv_winerror, + ops::internal_binding::op_node_internal_binding_string_decoder, + ops::internal_binding::op_node_internal_binding_symbols, ops::internal_binding::op_node_internal_binding_tty_wrap, ops::internal_binding::op_node_internal_binding_types, ops::internal_binding::op_node_ares_strerror, @@ -340,6 +343,7 @@ deno_core::extension!(deno_node, ops::internal_binding_uv::op_node_uv_get_error_map, ops::internal_binding_uv::op_node_uv_get_error_message, ops::internal_binding_uv::op_node_uv_map_sys_errno_to_uv_errno, + ops::util::op_node_internal_binding_util, ops::zlib::op_zlib_crc32, ops::zlib::op_zlib_crc32_string, ops::handle_wrap::op_node_new_async_id, @@ -898,6 +902,12 @@ deno_core::extension!(deno_node, ext.external_references.to_mut().extend( ops::internal_binding_crypto::external_references(), ); + ext.external_references.to_mut().extend( + ops::util::external_references(), + ); + ext.external_references.to_mut().extend( + ops::buffer::external_references(), + ); }, ); diff --git a/ext/node/ops/buffer.rs b/ext/node/ops/buffer.rs index a96bc05da118f0..6c8e078ce2fcc5 100644 --- a/ext/node/ops/buffer.rs +++ b/ext/node/ops/buffer.rs @@ -3,6 +3,8 @@ use deno_core::convert::Uint8Array; use deno_core::op2; use deno_core::v8; +use deno_core::v8::ExternalReference; +use deno_core::v8::MapFnTo; use deno_core::v8_static_strings; use deno_error::JsErrorBox; @@ -481,3 +483,328 @@ fn parse_array_index( } Ok(arg as usize) } + +fn set_value( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: v8::Local, +) { + let key = v8::String::new(scope, name).unwrap(); + obj.set(scope, key.into(), value); +} + +fn set_function( + scope: &mut v8::PinScope, + obj: v8::Local, + export_name: &str, + function: v8::Local, +) { + let name = v8::String::new(scope, export_name).unwrap(); + function.set_name(name); + set_value(scope, obj, export_name, function.into()); +} + +fn throw_error(scope: &mut v8::PinScope, message: &str) { + let message = v8::String::new(scope, message).unwrap(); + let exception = v8::Exception::error(scope, message); + scope.throw_exception(exception); +} + +fn throw_type_error(scope: &mut v8::PinScope, message: &str) { + let message = v8::String::new(scope, message).unwrap(); + let exception = v8::Exception::type_error(scope, message); + scope.throw_exception(exception); +} + +fn view_bytes( + scope: &mut v8::PinScope, + value: v8::Local, + name: &str, +) -> Option> { + if !value.is_array_buffer_view() { + throw_type_error(scope, &format!("{name} must be an ArrayBufferView")); + return None; + } + let view = v8::Local::::try_from(value).unwrap(); + let mut bytes = vec![0; view.byte_length()]; + let copied = view.copy_contents(&mut bytes); + debug_assert_eq!(copied, bytes.len()); + Some(bytes) +} + +fn index_of_needle( + source: &[u8], + needle: &[u8], + mut start: i64, + step: usize, +) -> i64 { + let source_len = source.len() as i64; + if start >= source_len { + return -1; + } + if start < 0 { + start = 0.max(source_len + start); + } + let start = start as usize; + if needle.is_empty() { + return start as i64; + } + if needle.len() > source.len() { + return -1; + } + let first = needle[0]; + let mut i = start; + while i < source.len() { + if source[i] == first + && i + needle.len() <= source.len() + && source[i..i + needle.len()] == *needle + { + return i as i64; + } + i += step; + } + -1 +} + +fn slice_end(len: usize, end: i64) -> usize { + if end < 0 { + (len as i64 + end).max(0) as usize + } else { + (end as usize).min(len) + } +} + +fn find_last_index(target: &[u8], needle: &[u8], mut offset: i64) -> i64 { + let target_len = target.len(); + let needle_len = needle.len(); + if offset > target_len as i64 { + offset = target_len as i64; + } + let searchable_len = slice_end(target_len, offset + needle_len as i64); + if needle_len == 0 { + return searchable_len as i64; + } + if needle_len > searchable_len { + return -1; + } + for index in (0..=searchable_len - needle_len).rev() { + if target[index..index + needle_len] == *needle { + return index as i64; + } + } + -1 +} + +fn index_of_buffer_from_bytes( + target: &[u8], + needle: &[u8], + mut byte_offset: i64, + encoding: i32, + forward: bool, +) -> i64 { + let target_len = target.len() as i64; + if encoding == 3 && (needle.len() < 2 || target.len() < 2) { + return -1; + } + if !forward { + if byte_offset < 0 { + byte_offset = target_len + byte_offset; + } + if needle.is_empty() { + return if byte_offset <= target_len { + byte_offset + } else { + target_len + }; + } + return find_last_index(target, needle, byte_offset); + } + if needle.is_empty() { + return if byte_offset <= target_len { + byte_offset + } else { + target_len + }; + } + index_of_needle( + target, + needle, + byte_offset, + if encoding == 3 { 2 } else { 1 }, + ) +} + +fn index_of_buffer_callback( + scope: &mut v8::PinScope, + args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + let encoding = args.get(3).int32_value(scope).unwrap_or(-1); + if !(0..=7).contains(&encoding) { + throw_error(scope, &format!("Unknown encoding code {encoding}")); + return; + } + let target = match view_bytes(scope, args.get(0), "targetBuffer") { + Some(target) => target, + None => return, + }; + let needle = match view_bytes(scope, args.get(1), "buffer") { + Some(needle) => needle, + None => return, + }; + let byte_offset = args.get(2).integer_value(scope).unwrap_or(0); + let forward = args.get(4).is_true(); + let index = index_of_buffer_from_bytes( + &target, + &needle, + byte_offset, + encoding, + forward, + ); + rv.set(v8::Number::new(scope, index as f64).into()); +} + +fn index_of_number_callback( + scope: &mut v8::PinScope, + args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + let target = match view_bytes(scope, args.get(0), "targetBuffer") { + Some(target) => target, + None => return, + }; + let number = args.get(1).uint32_value(scope).unwrap_or(0); + let byte_offset = args.get(2).integer_value(scope).unwrap_or(0); + let forward = args.get(3).is_true(); + let needle = [number as u8]; + let index = + index_of_buffer_from_bytes(&target, &needle, byte_offset, 1, forward); + rv.set(v8::Number::new(scope, index as f64).into()); +} + +fn index_of_needle_callback( + scope: &mut v8::PinScope, + args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + let source = match view_bytes(scope, args.get(0), "source") { + Some(source) => source, + None => return, + }; + let needle = match view_bytes(scope, args.get(1), "needle") { + Some(needle) => needle, + None => return, + }; + let start = args.get(2).integer_value(scope).unwrap_or(0); + let step = args.get(3).uint32_value(scope).unwrap_or(1).max(1) as usize; + let index = index_of_needle(&source, &needle, start, step); + rv.set(v8::Number::new(scope, index as f64).into()); +} + +fn fill_callback( + scope: &mut v8::PinScope, + args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + let buffer_value = args.get(0); + if !buffer_value.is_array_buffer_view() { + throw_type_error(scope, "buffer must be an ArrayBufferView"); + return; + } + let buffer = + v8::Local::::try_from(buffer_value).unwrap(); + let len = buffer.byte_length(); + let mut start = args.get(2).integer_value(scope).unwrap_or(0); + let mut end = args.get(3).integer_value(scope).unwrap_or(len as i64); + if start < 0 { + start = (len as i64 + start).max(0); + } + if end < 0 { + end = (len as i64 + end).max(0); + } + let start = (start as usize).min(len); + let end = (end as usize).min(len); + if end <= start { + rv.set(buffer_value); + return; + } + + let value = args.get(1); + let pattern = if value.is_array_buffer_view() { + view_bytes(scope, value, "value").unwrap_or_default() + } else if value.is_string() { + value + .to_string(scope) + .unwrap() + .to_rust_string_lossy(scope) + .into_bytes() + } else { + vec![value.uint32_value(scope).unwrap_or(0) as u8] + }; + if pattern.is_empty() { + rv.set(buffer_value); + return; + } + + // SAFETY: V8 gives a pointer to the ArrayBufferView data including its byte + // offset. The write range is clamped to the same view's byte length. + let data = + unsafe { std::slice::from_raw_parts_mut(buffer.data().cast::(), len) }; + for (index, slot) in data[start..end].iter_mut().enumerate() { + *slot = pattern[index % pattern.len()]; + } + rv.set(buffer_value); +} + +pub(crate) fn external_references() -> [ExternalReference; 4] { + [ + ExternalReference { + function: index_of_buffer_callback.map_fn_to(), + }, + ExternalReference { + function: index_of_number_callback.map_fn_to(), + }, + ExternalReference { + function: index_of_needle_callback.map_fn_to(), + }, + ExternalReference { + function: fill_callback.map_fn_to(), + }, + ] +} + +#[op2] +pub fn op_node_internal_binding_buffer<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + let index_of_buffer = + v8::FunctionTemplate::new(scope, index_of_buffer_callback) + .get_function(scope) + .unwrap(); + set_function(scope, obj, "indexOfBuffer", index_of_buffer); + let index_of_number = + v8::FunctionTemplate::new(scope, index_of_number_callback) + .get_function(scope) + .unwrap(); + set_function(scope, obj, "indexOfNumber", index_of_number); + let fill = v8::FunctionTemplate::new(scope, fill_callback) + .get_function(scope) + .unwrap(); + set_function(scope, obj, "fill", fill); + let index_of_needle = + v8::FunctionTemplate::new(scope, index_of_needle_callback) + .get_function(scope) + .unwrap(); + set_function(scope, obj, "indexOfNeedle", index_of_needle); + + let default_obj = v8::Object::new(scope); + for name in ["indexOfBuffer", "indexOfNumber"] { + let key = v8::String::new(scope, name).unwrap(); + let value = obj.get(scope, key.into()).unwrap(); + set_value(scope, default_obj, name, value); + } + set_value(scope, obj, "default", default_obj.into()); + obj +} diff --git a/ext/node/ops/internal_binding.rs b/ext/node/ops/internal_binding.rs index 06c6078ef46f74..50750e6397218f 100644 --- a/ext/node/ops/internal_binding.rs +++ b/ext/node/ops/internal_binding.rs @@ -36,6 +36,16 @@ fn set_bool( obj.set(scope, key.into(), value.into()); } +fn set_value( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: v8::Local, +) { + let key = v8::String::new(scope, name).unwrap(); + obj.set(scope, key.into(), value); +} + fn core_object<'s>( scope: &mut v8::PinScope<'s, '_>, ) -> v8::Local<'s, v8::Object> { @@ -176,6 +186,46 @@ pub fn op_node_internal_binding_ares<'s>( obj } +#[op2] +pub fn op_node_internal_binding_string_decoder<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + let encodings = v8::Array::new(scope, 8); + for (index, encoding) in [ + (0, "ascii"), + (1, "utf8"), + (2, "base64"), + (3, "utf16le"), + (4, "latin1"), + (5, "hex"), + (6, "buffer"), + (7, "base64url"), + ] { + let value = v8::String::new(scope, encoding).unwrap(); + encodings.set_index(scope, index, value.into()); + } + set_value(scope, obj, "encodings", encodings.into()); + let default_obj = v8::Object::new(scope); + set_value(scope, default_obj, "encodings", encodings.into()); + set_value(scope, obj, "default", default_obj.into()); + obj +} + +#[op2] +pub fn op_node_internal_binding_symbols<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + let async_id_description = v8::String::new(scope, "asyncIdSymbol").unwrap(); + let async_id = v8::Symbol::new(scope, Some(async_id_description)); + set_value(scope, obj, "asyncIdSymbol", async_id.into()); + let owner_description = v8::String::new(scope, "ownerSymbol").unwrap(); + let owner = v8::Symbol::new(scope, Some(owner_description)); + set_value(scope, obj, "ownerSymbol", owner.into()); + obj +} + #[op2] pub fn op_node_internal_binding_inspector<'s>( scope: &mut v8::PinScope<'s, '_>, diff --git a/ext/node/ops/util.rs b/ext/node/ops/util.rs index 6c9ee0f7556253..e7911ec2532701 100644 --- a/ext/node/ops/util.rs +++ b/ext/node/ops/util.rs @@ -5,6 +5,8 @@ use std::path::Path; use deno_core::OpState; use deno_core::op2; use deno_core::v8; +use deno_core::v8::ExternalReference; +use deno_core::v8::MapFnTo; use deno_dotenv::parse_env_content_hook; use deno_error::JsErrorBox; use node_resolver::InNpmPackageChecker; @@ -25,6 +27,9 @@ enum HandleType { Unknown, } +const HANDLE_TYPES: [&str; 6] = + ["TCP", "TTY", "UDP", "FILE", "PIPE", "UNKNOWN"]; + #[op2(fast)] pub fn op_node_guess_handle_type(_state: &mut OpState, fd: u32) -> u32 { guess_handle_type(fd as i32) as u32 @@ -224,6 +229,13 @@ pub fn op_node_get_own_non_index_properties<'s>( pub fn op_node_parse_env<'a>( scope: &mut v8::PinScope<'a, '_>, #[string] content: &str, +) -> v8::Local<'a, v8::Object> { + parse_env(scope, content) +} + +fn parse_env<'a>( + scope: &mut v8::PinScope<'a, '_>, + content: &str, ) -> v8::Local<'a, v8::Object> { let env_obj = v8::Object::new(scope); parse_env_content_hook(content, &mut |key, value| { @@ -233,3 +245,267 @@ pub fn op_node_parse_env<'a>( }); env_obj } + +fn set_value( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: v8::Local, +) { + let key = v8::String::new(scope, name).unwrap(); + obj.set(scope, key.into(), value); +} + +fn set_i32( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: i32, +) { + let value = v8::Integer::new(scope, value); + set_value(scope, obj, name, value.into()); +} + +fn set_function( + scope: &mut v8::PinScope, + obj: v8::Local, + export_name: &str, + function: v8::Local, +) { + let name = v8::String::new(scope, export_name).unwrap(); + function.set_name(name); + set_value(scope, obj, export_name, function.into()); +} + +fn throw_type_error(scope: &mut v8::PinScope, message: &str) { + let message = v8::String::new(scope, message).unwrap(); + let exception = v8::Exception::type_error(scope, message); + scope.throw_exception(exception); +} + +fn guess_handle_type_callback( + scope: &mut v8::PinScope, + args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + let fd = args.get(0).integer_value(scope).unwrap_or(-1) as i32; + let handle_type = guess_handle_type(fd) as usize; + let value = v8::String::new( + scope, + HANDLE_TYPES.get(handle_type).copied().unwrap_or("UNKNOWN"), + ) + .unwrap(); + rv.set(value.into()); +} + +fn is_array_index_value( + scope: &mut v8::PinScope, + value: v8::Local, +) -> bool { + if value.is_number() { + let Some(number) = value.number_value(scope) else { + return false; + }; + return number >= 0.0 + && number.fract() == 0.0 + && number as i32 as f64 == number; + } + if value.is_string() { + let value = value.to_string(scope).unwrap().to_rust_string_lossy(scope); + if value.is_empty() { + return false; + } + if value.len() > 1 && value.as_bytes()[0] == b'0' { + return false; + } + return value.bytes().all(|ch| ch.is_ascii_digit()); + } + false +} + +fn is_array_index_callback( + scope: &mut v8::PinScope, + args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + rv.set_bool(is_array_index_value(scope, args.get(0))); +} + +fn get_own_non_index_properties_callback( + scope: &mut v8::PinScope, + args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + let value = args.get(0); + if !value.is_object() { + throw_type_error(scope, "obj must be an object"); + return; + } + let obj = v8::Local::::try_from(value).unwrap(); + let filter = args.get(1).uint32_value(scope).unwrap_or(0); + let mut property_filter = v8::PropertyFilter::ALL_PROPERTIES; + if filter & 1 << 0 != 0 { + property_filter = property_filter | v8::PropertyFilter::ONLY_WRITABLE; + } + if filter & 1 << 1 != 0 { + property_filter = property_filter | v8::PropertyFilter::ONLY_ENUMERABLE; + } + if filter & 1 << 2 != 0 { + property_filter = property_filter | v8::PropertyFilter::ONLY_CONFIGURABLE; + } + if filter & 1 << 3 != 0 { + property_filter = property_filter | v8::PropertyFilter::SKIP_STRINGS; + } + if filter & 1 << 4 != 0 { + property_filter = property_filter | v8::PropertyFilter::SKIP_SYMBOLS; + } + + v8::tc_scope!(let tc_scope, scope); + let result = obj.get_property_names( + tc_scope, + v8::GetPropertyNamesArgs { + index_filter: v8::IndexFilter::SkipIndices, + property_filter, + key_conversion: v8::KeyConversionMode::NoNumbers, + mode: v8::KeyCollectionMode::OwnOnly, + }, + ); + match result { + Some(names) => rv.set(names.into()), + None => { + if tc_scope.has_caught() || tc_scope.has_terminated() { + tc_scope.rethrow(); + } else { + throw_type_error(tc_scope, "Failed to get own non-index properties"); + } + } + } +} + +fn array_buffer_view_has_buffer_callback( + scope: &mut v8::PinScope, + args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + let value = args.get(0); + if !value.is_array_buffer_view() { + throw_type_error(scope, "view must be an ArrayBufferView"); + return; + } + let view = v8::Local::::try_from(value).unwrap(); + rv.set_bool(view.has_buffer()); +} + +fn parse_env_callback( + scope: &mut v8::PinScope, + args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + let Some(content) = args.get(0).to_string(scope) else { + throw_type_error(scope, "env must be a string"); + return; + }; + let content = content.to_rust_string_lossy(scope); + let env = parse_env(scope, &content); + rv.set(env.into()); +} + +pub(crate) fn external_references() -> [ExternalReference; 5] { + [ + ExternalReference { + function: guess_handle_type_callback.map_fn_to(), + }, + ExternalReference { + function: is_array_index_callback.map_fn_to(), + }, + ExternalReference { + function: get_own_non_index_properties_callback.map_fn_to(), + }, + ExternalReference { + function: array_buffer_view_has_buffer_callback.map_fn_to(), + }, + ExternalReference { + function: parse_env_callback.map_fn_to(), + }, + ] +} + +#[op2] +pub fn op_node_internal_binding_util<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + + let guess_handle_type = + v8::FunctionTemplate::new(scope, guess_handle_type_callback) + .get_function(scope) + .unwrap(); + set_function(scope, obj, "guessHandleType", guess_handle_type); + let is_array_index = + v8::FunctionTemplate::new(scope, is_array_index_callback) + .get_function(scope) + .unwrap(); + set_function(scope, obj, "isArrayIndex", is_array_index); + let get_own_non_index_properties = + v8::FunctionTemplate::new(scope, get_own_non_index_properties_callback) + .get_function(scope) + .unwrap(); + set_function( + scope, + obj, + "getOwnNonIndexProperties", + get_own_non_index_properties, + ); + let array_buffer_view_has_buffer = + v8::FunctionTemplate::new(scope, array_buffer_view_has_buffer_callback) + .get_function(scope) + .unwrap(); + set_function( + scope, + obj, + "arrayBufferViewHasBuffer", + array_buffer_view_has_buffer, + ); + let parse_env = v8::FunctionTemplate::new(scope, parse_env_callback) + .get_function(scope) + .unwrap(); + set_function(scope, obj, "parseEnv", parse_env); + + for (name, value) in [ + ("ALL_PROPERTIES", 0), + ("ONLY_WRITABLE", 1), + ("ONLY_ENUMERABLE", 2), + ("ONLY_CONFIGURABLE", 4), + ("ONLY_ENUM_WRITABLE", 6), + ("SKIP_STRINGS", 8), + ("SKIP_SYMBOLS", 16), + ] { + set_i32(scope, obj, name, value); + } + + let symbol_name = + v8::String::new(scope, "nodejs.worker_threads.untransferable").unwrap(); + let untransferable_symbol = v8::Symbol::for_key(scope, symbol_name); + set_value( + scope, + obj, + "untransferableSymbol", + untransferable_symbol.into(), + ); + + let default_obj = v8::Object::new(scope); + for name in [ + "guessHandleType", + "isArrayIndex", + "getOwnNonIndexProperties", + "arrayBufferViewHasBuffer", + "parseEnv", + "untransferableSymbol", + ] { + let key = v8::String::new(scope, name).unwrap(); + let value = obj.get(scope, key.into()).unwrap(); + set_value(scope, default_obj, name, value); + } + set_value(scope, obj, "default", default_obj.into()); + obj +} diff --git a/ext/node/polyfills/internal_binding/buffer.ts b/ext/node/polyfills/internal_binding/buffer.ts index 4b28b3ec261163..463a82d5fc5087 100644 --- a/ext/node/polyfills/internal_binding/buffer.ts +++ b/ext/node/polyfills/internal_binding/buffer.ts @@ -1,194 +1,6 @@ // Copyright 2018-2026 the Deno authors. MIT license. -(function () { -const { core, primordials } = __bootstrap; -const { Encodings } = core.loadExtScript( - "ext:deno_node/internal_binding/_node.ts", -); - -const { - Error, - MathMax, - TypedArrayFrom, - TypedArrayPrototypeGetLength, - TypedArrayPrototypeSlice, - Uint8Array, -} = primordials; - -function fill( - buffer, - value, - start, - end, -) { - // Ignore primordial: `fill` is a method from Node.js Buffer. - // deno-lint-ignore prefer-primordials - return buffer.fill(value, start, end); -} - -function indexOfNeedle( - source: Uint8Array, - needle: Uint8Array, - start = 0, - step = 1, -): number { - const sourceLength = TypedArrayPrototypeGetLength(source); - const needleLength = TypedArrayPrototypeGetLength(needle); - - if (start >= sourceLength) { - return -1; - } - if (start < 0) { - start = MathMax(0, sourceLength + start); - } - const s = needle[0]; - for (let i = start; i < sourceLength; i += step) { - if (source[i] !== s) continue; - const pin = i; - let matched = 1; - let j = i; - while (matched < needleLength) { - j++; - if (source[j] !== needle[j - pin]) { - break; - } - matched++; - } - if (matched === needleLength) { - return pin; - } - } - return -1; -} - -// TODO(Soremwar) -// Check if offset or buffer can be transform in order to just use std's lastIndexOf directly -// This implementation differs from std's lastIndexOf in the fact that -// it also includes items outside of the offset as long as part of the -// set is contained inside of the offset -// Probably way slower too -function findLastIndex( - targetBuffer: Uint8Array, - buffer: Uint8Array, - offset: number, -) { - const targetBufferLength = TypedArrayPrototypeGetLength(targetBuffer); - const bufferLength = TypedArrayPrototypeGetLength(buffer); - - offset = offset > targetBufferLength ? targetBufferLength : offset; - - const searchableBuffer = TypedArrayPrototypeSlice( - targetBuffer, - 0, - offset + bufferLength, - ); - const searchableBufferLastIndex = - TypedArrayPrototypeGetLength(searchableBuffer) - 1; - const bufferLastIndex = bufferLength - 1; - - // Important to keep track of the last match index in order to backtrack after an incomplete match - // Not doing this will cause the search to skip all possible matches that happened in the - // last match range - let lastMatchIndex = -1; - let matches = 0; - let index = -1; - for (let x = 0; x <= searchableBufferLastIndex; x++) { - if ( - searchableBuffer[searchableBufferLastIndex - x] === - buffer[bufferLastIndex - matches] - ) { - if (lastMatchIndex === -1) { - lastMatchIndex = x; - } - matches++; - } else { - matches = 0; - if (lastMatchIndex !== -1) { - // Restart the search right after the last index was ignored - x = lastMatchIndex + 1; - lastMatchIndex = -1; - } - continue; - } - - if (matches === bufferLength) { - index = x; - break; - } - } - - if (index === -1) return index; - - return searchableBufferLastIndex - index; -} -function indexOfBuffer( - targetBuffer: Uint8Array, - buffer: Uint8Array, - byteOffset: number, - encoding: Encodings, - forwardDirection: boolean, -) { - if (Encodings[encoding] === undefined) { - throw new Error(`Unknown encoding code ${encoding}`); - } - - const targetBufferLength = TypedArrayPrototypeGetLength(targetBuffer); - const bufferLength = TypedArrayPrototypeGetLength(buffer); - const isUcs2 = encoding === Encodings.UCS2; - - // If the encoding is UCS2 and haystack or needle has a length less than 2, the search will always fail - // https://github.com/nodejs/node/blob/fbdfe9399cf6c660e67fd7d6ceabfb106e32d787/src/node_buffer.cc#L1067-L1069 - if (isUcs2) { - if (bufferLength < 2 || targetBufferLength < 2) { - return -1; - } - } - - if (!forwardDirection) { - // If negative the offset is calculated from the end of the buffer - - if (byteOffset < 0) { - byteOffset = targetBufferLength + byteOffset; - } - - if (bufferLength === 0) { - return byteOffset <= targetBufferLength ? byteOffset : targetBufferLength; - } - - return findLastIndex(targetBuffer, buffer, byteOffset); - } - - if (buffer.length === 0) { - return byteOffset <= targetBufferLength ? byteOffset : targetBufferLength; - } - - return indexOfNeedle(targetBuffer, buffer, byteOffset, isUcs2 ? 2 : 1); -} - -function indexOfNumber( - targetBuffer: Uint8Array, - number: number, - byteOffset: number, - forwardDirection: boolean, -) { - return indexOfBuffer( - targetBuffer, - // Uses only the last 2 hex digits of the number - // https://github.com/nodejs/node/issues/7591#issuecomment-231178104 - TypedArrayFrom(Uint8Array, [number & 255]), - byteOffset, - Encodings.UTF8, - forwardDirection, - ); -} - -const _defaultExport = { indexOfBuffer, indexOfNumber }; - -return { - indexOfBuffer, - indexOfNumber, - fill, - indexOfNeedle, - default: _defaultExport, -}; +(function () { +const { op_node_internal_binding_buffer } = __bootstrap.core.ops; +return op_node_internal_binding_buffer(); })(); diff --git a/ext/node/polyfills/internal_binding/string_decoder.ts b/ext/node/polyfills/internal_binding/string_decoder.ts index e29c776a0ab2c9..df952839b23da9 100644 --- a/ext/node/polyfills/internal_binding/string_decoder.ts +++ b/ext/node/polyfills/internal_binding/string_decoder.ts @@ -1,24 +1,5 @@ // Copyright 2018-2026 the Deno authors. MIT license. (function () { -const { core } = __bootstrap; -const { Encodings } = core.loadExtScript( - "ext:deno_node/internal_binding/_node.ts", -); - -const encodings = []; -encodings[Encodings.ASCII] = "ascii"; -encodings[Encodings.BASE64] = "base64"; -encodings[Encodings.BASE64URL] = "base64url"; -encodings[Encodings.BUFFER] = "buffer"; -encodings[Encodings.HEX] = "hex"; -encodings[Encodings.LATIN1] = "latin1"; -encodings[Encodings.UCS2] = "utf16le"; -encodings[Encodings.UTF8] = "utf8"; - -const _defaultExport = { encodings }; - -return { - encodings, - default: _defaultExport, -}; +const { op_node_internal_binding_string_decoder } = __bootstrap.core.ops; +return op_node_internal_binding_string_decoder(); })(); diff --git a/ext/node/polyfills/internal_binding/symbols.ts b/ext/node/polyfills/internal_binding/symbols.ts index 990a923fa0a69f..36c726a6cd7e52 100644 --- a/ext/node/polyfills/internal_binding/symbols.ts +++ b/ext/node/polyfills/internal_binding/symbols.ts @@ -24,14 +24,6 @@ // - https://github.com/nodejs/node/blob/master/src/node_symbols.cc (function () { -const { primordials } = __bootstrap; -const { Symbol } = primordials; - -const asyncIdSymbol: unique symbol = Symbol("asyncIdSymbol"); -const ownerSymbol: unique symbol = Symbol("ownerSymbol"); - -return { - asyncIdSymbol, - ownerSymbol, -}; +const { op_node_internal_binding_symbols } = __bootstrap.core.ops; +return op_node_internal_binding_symbols(); })(); diff --git a/ext/node/polyfills/internal_binding/util.ts b/ext/node/polyfills/internal_binding/util.ts index 61763106dd78f5..f681a6528e787e 100644 --- a/ext/node/polyfills/internal_binding/util.ts +++ b/ext/node/polyfills/internal_binding/util.ts @@ -26,118 +26,6 @@ // - https://github.com/nodejs/node/blob/master/src/util.h (function () { -const { core, primordials } = __bootstrap; -const { - op_node_get_own_non_index_properties, - op_node_guess_handle_type, - op_node_parse_env, - op_node_view_has_buffer, -} = core.ops; -const { - StringPrototypeCharCodeAt, - SymbolFor, -} = primordials; - -const handleTypes = ["TCP", "TTY", "UDP", "FILE", "PIPE", "UNKNOWN"]; -function guessHandleType(fd: number): string { - const type = op_node_guess_handle_type(fd); - return handleTypes[type]; -} - -const ALL_PROPERTIES = 0; -const ONLY_WRITABLE = 1; -const ONLY_ENUMERABLE = 2; -const ONLY_CONFIGURABLE = 4; -const ONLY_ENUM_WRITABLE = 6; -const SKIP_STRINGS = 8; -const SKIP_SYMBOLS = 16; - -/** - * Efficiently determine whether the provided property key is numeric - * (and thus could be an array indexer) or not. - * - * Always returns true for values of type `'number'`. - * - * Otherwise, only returns true for strings that consist only of positive integers. - * - * Results are cached. - */ -const isNumericLookup: Record = {}; -function isArrayIndex(value: unknown): value is number | string { - switch (typeof value) { - case "number": - return value >= 0 && (value | 0) === value; - case "string": { - const result = isNumericLookup[value]; - if (result !== void 0) { - return result; - } - const length = value.length; - if (length === 0) { - return isNumericLookup[value] = false; - } - let ch = 0; - let i = 0; - for (; i < length; ++i) { - ch = StringPrototypeCharCodeAt(value, i); - if ( - i === 0 && ch === 0x30 && length > 1 /* must not start with 0 */ || - ch < 0x30 /* 0 */ || ch > 0x39 /* 9 */ - ) { - return isNumericLookup[value] = false; - } - } - return isNumericLookup[value] = true; - } - default: - return false; - } -} - -function getOwnNonIndexProperties( - obj: object, - filter: number, -): (string | symbol)[] { - return op_node_get_own_non_index_properties(obj, filter); -} - -function arrayBufferViewHasBuffer( - view: ArrayBufferView, -): boolean { - return op_node_view_has_buffer(view); -} - -const parseEnv = op_node_parse_env as ( - env: string, -) => Record; - -const untransferableSymbol = SymbolFor( - "nodejs.worker_threads.untransferable", -); - -const _defaultExport = { - guessHandleType, - isArrayIndex, - getOwnNonIndexProperties, - arrayBufferViewHasBuffer, - parseEnv, - untransferableSymbol, -}; - -return { - guessHandleType, - isArrayIndex, - getOwnNonIndexProperties, - arrayBufferViewHasBuffer, - ALL_PROPERTIES, - ONLY_WRITABLE, - ONLY_ENUMERABLE, - ONLY_CONFIGURABLE, - ONLY_ENUM_WRITABLE, - SKIP_STRINGS, - SKIP_SYMBOLS, - parseEnv, - untransferableSymbol, - default: _defaultExport, -}; +const { op_node_internal_binding_util } = __bootstrap.core.ops; +return op_node_internal_binding_util(); })(); From 2b27e3e25eee38426e3e8ac6242fc732a7cab59a Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 11:45:37 -0700 Subject: [PATCH 04/33] port block list internal binding to rust --- ext/node/lib.rs | 4 + ext/node/ops/blocklist.rs | 173 ++++++++++++++++++ .../polyfills/internal_binding/block_list.ts | 54 +----- 3 files changed, 179 insertions(+), 52 deletions(-) diff --git a/ext/node/lib.rs b/ext/node/lib.rs index c7791ebfc046ad..4e270764cf7c89 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -200,6 +200,7 @@ deno_core::extension!(deno_node, ops::blocklist::op_socket_address_parse, ops::blocklist::op_socket_address_get_serialization, + ops::blocklist::op_node_internal_binding_block_list, ops::blocklist::op_blocklist_new, ops::blocklist::op_blocklist_add_address, @@ -908,6 +909,9 @@ deno_core::extension!(deno_node, ext.external_references.to_mut().extend( ops::buffer::external_references(), ); + ext.external_references.to_mut().extend( + ops::blocklist::internal_binding_external_references(), + ); }, ); diff --git a/ext/node/ops/blocklist.rs b/ext/node/ops/blocklist.rs index 4bb06c37170b26..0a166c75ab24cd 100644 --- a/ext/node/ops/blocklist.rs +++ b/ext/node/ops/blocklist.rs @@ -10,6 +10,9 @@ use std::net::SocketAddr; use deno_core::OpState; use deno_core::ToV8; use deno_core::op2; +use deno_core::v8; +use deno_core::v8::ExternalReference; +use deno_core::v8::MapFnTo; use ipnet::IpNet; use ipnet::Ipv4Net; use ipnet::Ipv6Net; @@ -43,6 +46,176 @@ pub enum BlocklistError { IpVersionMismatch, } +fn set_value( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: v8::Local, +) { + let key = v8::String::new(scope, name).unwrap(); + obj.set(scope, key.into(), value); +} + +fn set_i32( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: i32, +) { + let value = v8::Integer::new(scope, value); + set_value(scope, obj, name, value.into()); +} + +fn set_function( + scope: &mut v8::PinScope, + obj: v8::Local, + export_name: &str, + function: v8::Local, +) { + let name = v8::String::new(scope, export_name).unwrap(); + function.set_name(name); + set_value(scope, obj, export_name, function.into()); +} + +fn private_key<'s>( + scope: &mut v8::PinScope<'s, '_>, + name: &str, +) -> v8::Local<'s, v8::Private> { + let name = v8::String::new(scope, name).unwrap(); + v8::Private::for_api(scope, Some(name)) +} + +fn set_private( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: v8::Local, +) { + let key = private_key(scope, name); + obj.set_private(scope, key, value); +} + +fn get_private<'s>( + scope: &mut v8::PinScope<'s, '_>, + obj: v8::Local<'s, v8::Object>, + name: &str, +) -> v8::Local<'s, v8::Value> { + let key = private_key(scope, name); + obj + .get_private(scope, key) + .unwrap_or_else(|| v8::undefined(scope).into()) +} + +fn socket_address_constructor<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, +) { + let this = args.this(); + set_private(scope, this, "address", args.get(0)); + set_private(scope, this, "port", args.get(1)); + set_private(scope, this, "family", args.get(2)); + set_private(scope, this, "flowlabel", args.get(3)); +} + +fn socket_address_address<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'s>, +) { + rv.set(get_private(scope, args.this(), "address")); +} + +fn socket_address_port<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'s>, +) { + rv.set(get_private(scope, args.this(), "port")); +} + +fn socket_address_family<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'s>, +) { + rv.set(get_private(scope, args.this(), "family")); +} + +fn socket_address_flowlabel<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'s>, +) { + rv.set(get_private(scope, args.this(), "flowlabel")); +} + +pub(crate) fn internal_binding_external_references() -> [ExternalReference; 5] { + [ + ExternalReference { + function: socket_address_constructor.map_fn_to(), + }, + ExternalReference { + function: socket_address_address.map_fn_to(), + }, + ExternalReference { + function: socket_address_port.map_fn_to(), + }, + ExternalReference { + function: socket_address_family.map_fn_to(), + }, + ExternalReference { + function: socket_address_flowlabel.map_fn_to(), + }, + ] +} + +#[op2] +pub fn op_node_internal_binding_block_list<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + set_i32(scope, obj, "AF_INET", 2); + set_i32(scope, obj, "AF_INET6", 10); + + let constructor_template = + v8::FunctionTemplate::new(scope, socket_address_constructor); + let constructor = constructor_template.get_function(scope).unwrap(); + let constructor_name = v8::String::new(scope, "SocketAddress").unwrap(); + constructor.set_name(constructor_name); + let prototype_key = v8::String::new(scope, "prototype").unwrap(); + let prototype = constructor.get(scope, prototype_key.into()).unwrap(); + let prototype = v8::Local::::try_from(prototype).unwrap(); + + let address = v8::FunctionTemplate::new(scope, socket_address_address) + .get_function(scope) + .unwrap(); + set_function(scope, prototype, "address", address); + let port = v8::FunctionTemplate::new(scope, socket_address_port) + .get_function(scope) + .unwrap(); + set_function(scope, prototype, "port", port); + let family = v8::FunctionTemplate::new(scope, socket_address_family) + .get_function(scope) + .unwrap(); + set_function(scope, prototype, "family", family); + let flowlabel = v8::FunctionTemplate::new(scope, socket_address_flowlabel) + .get_function(scope) + .unwrap(); + set_function(scope, prototype, "flowlabel", flowlabel); + + set_value(scope, obj, "SocketAddress", constructor.into()); + + let default_obj = v8::Object::new(scope); + for name in ["AF_INET", "AF_INET6", "SocketAddress"] { + let key = v8::String::new(scope, name).unwrap(); + let value = obj.get(scope, key.into()).unwrap(); + set_value(scope, default_obj, name, value); + } + set_value(scope, obj, "default", default_obj.into()); + obj +} + #[op2(fast)] pub fn op_socket_address_parse( state: &mut OpState, diff --git a/ext/node/polyfills/internal_binding/block_list.ts b/ext/node/polyfills/internal_binding/block_list.ts index 1fa08343e34df3..2e5ae4d70a4f80 100644 --- a/ext/node/polyfills/internal_binding/block_list.ts +++ b/ext/node/polyfills/internal_binding/block_list.ts @@ -1,56 +1,6 @@ // Copyright 2018-2026 the Deno authors. MIT license. -// Copyright Joyent and Node contributors. All rights reserved. MIT license. - -// Mirrors Node's `internalBinding('block_list')`. Exposes a low-level -// SocketAddress handle and the AF_INET/AF_INET6 family constants used by the -// internal SocketAddress implementation. The handle is used by -// `internal/socketaddress.InternalSocketAddress` to wrap an already-validated -// address tuple into a SocketAddress without re-running validation. (function () { -// Match POSIX values for AF_INET / AF_INET6 on Linux. The exact values are -// not observable through the public API, only through this internal binding. -const AF_INET = 2; -const AF_INET6 = 10; - -class SocketAddress { - #address; - #port; - #family; - #flowlabel; - - constructor(address, port, family, flowlabel) { - this.#address = address; - this.#port = port; - this.#family = family; - this.#flowlabel = flowlabel; - } - - address() { - return this.#address; - } - - port() { - return this.#port; - } - - family() { - return this.#family; - } - - flowlabel() { - return this.#flowlabel; - } -} - -const exports = { - AF_INET, - AF_INET6, - SocketAddress, -}; - -return { - ...exports, - default: exports, -}; +const { op_node_internal_binding_block_list } = __bootstrap.core.ops; +return op_node_internal_binding_block_list(); })(); From 1a77209140ab5e0ac86788201d88be7d042f9a98 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 11:49:37 -0700 Subject: [PATCH 05/33] port node file internal binding to rust --- ext/node/lib.rs | 1 + ext/node/ops/fs.rs | 71 ++++++++++++ .../polyfills/internal_binding/node_file.ts | 106 +----------------- 3 files changed, 74 insertions(+), 104 deletions(-) diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 4e270764cf7c89..992b352648af0a 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -242,6 +242,7 @@ deno_core::extension!(deno_node, ops::fs::op_node_fs_write_sync, ops::fs::op_node_fs_write_deferred, ops::fs::op_node_fs_seek_sync, + ops::fs::op_node_file_write_buffer, ops::fs::op_node_fs_seek, ops::fs::op_node_fs_fstat_sync, ops::fs::op_node_fs_fstat, diff --git a/ext/node/ops/fs.rs b/ext/node/ops/fs.rs index 9b8e49b3a4a07c..cfafe5bdb9dbf9 100644 --- a/ext/node/ops/fs.rs +++ b/ext/node/ops/fs.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use std::cell::RefCell; +use std::error::Error; use std::path::Path; use std::path::PathBuf; use std::rc::Rc; @@ -1068,6 +1069,76 @@ pub fn op_node_fs_seek_sync( Ok(pos) } +fn fs_error_errno(error: &FsError) -> i32 { + match error { + FsError::Io(error) => error.raw_os_error().unwrap_or(255), + FsError::Fs(error) => error + .source() + .and_then(|error| error.downcast_ref::()) + .and_then(std::io::Error::raw_os_error) + .unwrap_or(255), + _ => 255, + } +} + +fn set_ctx_errno( + scope: &mut v8::PinScope, + ctx: v8::Local, + errno: i32, +) { + let key = v8::String::new(scope, "errno").unwrap(); + let value = v8::Integer::new(scope, errno); + ctx.set(scope, key.into(), value.into()); +} + +#[op2(fast)] +#[smi] +pub fn op_node_file_write_buffer( + state: &mut OpState, + scope: &mut v8::PinScope<'_, '_>, + fd: i32, + buffer: v8::Local, + #[smi] offset: u32, + #[smi] length: u32, + position: v8::Local, + ctx: v8::Local, +) -> Result { + let offset = offset as usize; + let length = length as usize; + let byte_length = buffer.byte_length(); + if offset > byte_length || offset + length > byte_length { + return Err(FsError::Io(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!( + "buffer doesn't have enough data: byteLength = {byte_length}, offset + length = {}", + offset + length + ), + ))); + } + + let file = file_for_fd(state, fd)?; + if !position.is_null_or_undefined() + && !position.is_false() + && let Some(position) = position.integer_value(scope) + && position != 0 + { + file + .clone() + .seek_sync(std::io::SeekFrom::Current(position))?; + } + + let mut bytes = vec![0; byte_length]; + let copied = buffer.copy_contents(&mut bytes); + debug_assert_eq!(copied, bytes.len()); + match write_with_position(file, &bytes[offset..offset + length], -1) { + Ok(written) => Ok(written), + Err(error) => { + set_ctx_errno(scope, ctx, fs_error_errno(&error)); + Ok(0) + } + } +} + #[op2] #[number] pub async fn op_node_fs_seek( diff --git a/ext/node/polyfills/internal_binding/node_file.ts b/ext/node/polyfills/internal_binding/node_file.ts index 3f4c8d053a1883..4c07fd4d1f839d 100644 --- a/ext/node/polyfills/internal_binding/node_file.ts +++ b/ext/node/polyfills/internal_binding/node_file.ts @@ -1,108 +1,6 @@ // Copyright 2018-2026 the Deno authors. MIT license. -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. -// This module ports: -// - https://github.com/nodejs/node/blob/master/src/node_file-inl.h -// - https://github.com/nodejs/node/blob/master/src/node_file.cc -// - https://github.com/nodejs/node/blob/master/src/node_file.h (function () { -const { core, primordials } = __bootstrap; -const { op_node_fs_seek_sync, op_node_fs_write_sync } = core.ops; - -let assert: typeof nodeAssert.default; -const lazyLoadAssert = () => { - return core.createLazyLoader( - "node:assert", - )().default; -}; - -const { - ErrorPrototype, - ObjectPrototypeIsPrototypeOf, - SafeRegExp, - StringPrototypeMatch, - TypedArrayPrototypeGetByteLength, - TypedArrayPrototypeSubarray, -} = primordials; -/** - * Write to the given file from the given buffer synchronously. - * - * Implements sync part of WriteBuffer in src/node_file.cc - * See: https://github.com/nodejs/node/blob/e9ed113/src/node_file.cc#L1818 - * - * @param fs file descriptor - * @param buffer the data to write - * @param offset where in the buffer to start from - * @param length how much to write - * @param position if integer, position to write at in the file. if null, write from the current position - * @param context context object for passing error number - */ -function writeBuffer( - fd: number, - buffer: Uint8Array, - offset: number, - length: number, - position: number | null, - ctx: { errno?: number }, -) { - assert ??= lazyLoadAssert(); - assert(offset >= 0, "offset should be greater or equal to 0"); - assert( - offset + length <= TypedArrayPrototypeGetByteLength(buffer), - `buffer doesn't have enough data: byteLength = ${ - TypedArrayPrototypeGetByteLength(buffer) - }, offset + length = ${ - offset + - length - }`, - ); - - if (position) { - op_node_fs_seek_sync(fd, position, 1); // SeekMode.Current = 1 - } - - const subarray = TypedArrayPrototypeSubarray(buffer, offset, offset + length); - - try { - return op_node_fs_write_sync(fd, subarray, -1); - } catch (e) { - ctx.errno = extractOsErrorNumberFromErrorMessage(e); - return 0; - } -} - -function extractOsErrorNumberFromErrorMessage(e: unknown): number { - const match = ObjectPrototypeIsPrototypeOf(ErrorPrototype, e) - ? StringPrototypeMatch(e.message, new SafeRegExp(/\(os error (\d+)\)/)) - : false; - - if (match) { - return +match[1]; - } - - return 255; // Unknown error -} - -return { - writeBuffer, -}; +const { op_node_file_write_buffer } = __bootstrap.core.ops; +return { writeBuffer: op_node_file_write_buffer }; })(); From df0ec7db44850619edfd5493f3bdeb93bd7dc0bf Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 12:56:17 -0700 Subject: [PATCH 06/33] port node options internal binding to rust --- ext/node/lib.rs | 4 + ext/node/ops/internal_binding_node_options.rs | 300 ++++++++++++++++++ ext/node/ops/mod.rs | 1 + .../internal_binding/node_options.ts | 183 +---------- 4 files changed, 313 insertions(+), 175 deletions(-) create mode 100644 ext/node/ops/internal_binding_node_options.rs diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 992b352648af0a..457d4977354015 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -339,6 +339,9 @@ deno_core::extension!(deno_node, ops::internal_binding_crypto::op_node_crypto_timing_safe_equal, ops::internal_binding_crypto::op_node_internal_binding_crypto, ops::internal_binding_crypto::op_node_internal_binding_timing_safe_equal, + ops::internal_binding_node_options::op_node_options_get_exec_argv_options, + ops::internal_binding_node_options::op_node_options_get_options, + ops::internal_binding_node_options::op_node_options_set_exec_argv, ops::internal_binding_uv::op_node_internal_binding_uv, ops::internal_binding_uv::op_node_uv_errname, ops::internal_binding_uv::op_node_uv_get_code_map, @@ -797,6 +800,7 @@ deno_core::extension!(deno_node, state = |state, options| { state.put(options.fs.clone()); state.put(ops::module_hooks::LoaderHookRegistry::default()); + state.put(ops::internal_binding_node_options::NodeOptionsState::default()); if let Some(init) = &options.maybe_init { state.put(init.sys.clone()); diff --git a/ext/node/ops/internal_binding_node_options.rs b/ext/node/ops/internal_binding_node_options.rs new file mode 100644 index 00000000000000..a937ba4f491bf4 --- /dev/null +++ b/ext/node/ops/internal_binding_node_options.rs @@ -0,0 +1,300 @@ +// Copyright 2018-2026 the Deno authors. MIT license. + +use deno_core::OpState; +use deno_core::op2; +use deno_core::v8; + +use crate::ExtNodeSys; + +#[derive(Default)] +pub struct NodeOptionsState { + exec_argv_snapshot: Option>, + options_map: Option>, + exec_argv_options_map: Option>, +} + +enum OptionValue { + Bool(bool), + String(String), +} + +fn set_value( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: v8::Local, +) { + let key = v8::String::new(scope, name).unwrap(); + obj.set(scope, key.into(), value); +} + +fn option_value_object<'s>( + scope: &mut v8::PinScope<'s, '_>, + value: OptionValue, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + let value = match value { + OptionValue::Bool(value) => v8::Boolean::new(scope, value).into(), + OptionValue::String(value) => { + v8::String::new(scope, &value).unwrap().into() + } + }; + set_value(scope, obj, "value", value); + obj +} + +fn set_option( + scope: &mut v8::PinScope, + map: v8::Local, + name: &str, + value: OptionValue, +) { + let key = v8::String::new(scope, name).unwrap(); + let value = option_value_object(scope, value); + map.set(scope, key.into(), value.into()).unwrap(); +} + +fn split_node_options(input: &str) -> Vec { + let mut args = Vec::new(); + let mut current = String::new(); + let mut in_double = false; + let mut in_single = false; + + for ch in input.chars() { + if ch == '"' && !in_single { + in_double = !in_double; + } else if ch == '\'' && !in_double { + in_single = !in_single; + } else if ch.is_ascii_whitespace() && !in_double && !in_single { + if !current.is_empty() { + args.push(std::mem::take(&mut current)); + } + } else { + current.push(ch); + } + } + if !current.is_empty() { + args.push(current); + } + args +} + +fn parse_option( + scope: &mut v8::PinScope, + options: v8::Local, + arg: &str, +) { + if let Some(value) = arg.strip_prefix("--title=") { + set_option( + scope, + options, + "--title", + OptionValue::String(value.to_string()), + ); + return; + } + if let Some(value) = arg.strip_prefix("--tls-cipher-list=") { + set_option( + scope, + options, + "--tls-cipher-list", + OptionValue::String(value.to_string()), + ); + return; + } + match arg { + "--no-warnings" => { + set_option(scope, options, "--warnings", OptionValue::Bool(false)) + } + "--pending-deprecation" => set_option( + scope, + options, + "--pending-deprecation", + OptionValue::Bool(true), + ), + "--expose-internals" | "--expose_internals" => set_option( + scope, + options, + "--expose-internals", + OptionValue::Bool(true), + ), + "--tls-min-v1.0" | "--tls-min-v1.1" | "--tls-min-v1.2" + | "--tls-min-v1.3" | "--tls-max-v1.2" | "--tls-max-v1.3" + | "--use-bundled-ca" | "--use-openssl-ca" | "--use-system-ca" => { + set_option(scope, options, arg, OptionValue::Bool(true)); + } + "--no-tls-min-v1.0" => { + set_option(scope, options, "--tls-min-v1.0", OptionValue::Bool(false)) + } + "--no-tls-min-v1.1" => { + set_option(scope, options, "--tls-min-v1.1", OptionValue::Bool(false)) + } + "--no-tls-min-v1.2" => { + set_option(scope, options, "--tls-min-v1.2", OptionValue::Bool(false)) + } + "--no-tls-min-v1.3" => { + set_option(scope, options, "--tls-min-v1.3", OptionValue::Bool(false)) + } + "--no-tls-max-v1.2" => { + set_option(scope, options, "--tls-max-v1.2", OptionValue::Bool(false)) + } + "--no-tls-max-v1.3" => { + set_option(scope, options, "--tls-max-v1.3", OptionValue::Bool(false)) + } + "--no-use-bundled-ca" => { + set_option(scope, options, "--use-bundled-ca", OptionValue::Bool(false)) + } + "--no-use-openssl-ca" => { + set_option(scope, options, "--use-openssl-ca", OptionValue::Bool(false)) + } + "--no-use-system-ca" => { + set_option(scope, options, "--use-system-ca", OptionValue::Bool(false)) + } + _ => { + if let Some(value) = arg.strip_prefix("--dns-result-order=") { + set_option( + scope, + options, + "--dns-result-order", + OptionValue::String(value.to_string()), + ); + } + } + } +} + +fn create_default_options<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Map> { + let options = v8::Map::new(scope); + set_option(scope, options, "--warnings", OptionValue::Bool(true)); + set_option( + scope, + options, + "--pending-deprecation", + OptionValue::Bool(false), + ); + set_option( + scope, + options, + "--expose-internals", + OptionValue::Bool(false), + ); + set_option( + scope, + options, + "--title", + OptionValue::String(String::new()), + ); + options +} + +fn process_object<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> Option> { + let context = scope.get_current_context(); + let global = context.global(scope); + let key = v8::String::new(scope, "process").unwrap(); + let process = global.get(scope, key.into())?; + v8::Local::::try_from(process).ok() +} + +fn process_exec_argv(scope: &mut v8::PinScope, state: &OpState) -> Vec { + if let Some(snapshot) = &state.borrow::().exec_argv_snapshot + { + return snapshot.clone(); + } + let Some(process) = process_object(scope) else { + return Vec::new(); + }; + let key = v8::String::new(scope, "execArgv").unwrap(); + let Some(value) = process.get(scope, key.into()) else { + return Vec::new(); + }; + let Ok(array) = v8::Local::::try_from(value) else { + return Vec::new(); + }; + let mut args = Vec::with_capacity(array.length() as usize); + for index in 0..array.length() { + let Some(value) = array.get_index(scope, index) else { + continue; + }; + let Some(value) = value.to_string(scope) else { + continue; + }; + args.push(value.to_rust_string_lossy(scope)); + } + args +} + +fn options_result<'s>( + scope: &mut v8::PinScope<'s, '_>, + options: v8::Local<'s, v8::Map>, +) -> v8::Local<'s, v8::Object> { + let result = v8::Object::new(scope); + set_value(scope, result, "options", options.into()); + result +} + +#[op2] +pub fn op_node_options_set_exec_argv( + state: &mut OpState, + #[serde] exec_argv: Vec, +) { + let state = state.borrow_mut::(); + state.exec_argv_snapshot = Some(exec_argv); + state.options_map = None; + state.exec_argv_options_map = None; +} + +#[op2] +pub fn op_node_options_get_options<'s, TSys: ExtNodeSys + 'static>( + state: &mut OpState, + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let cached = state.borrow::().options_map.clone(); + if let Some(cached) = cached { + let options = v8::Local::new(scope, &cached); + return options_result(scope, options); + } + + let options = create_default_options(scope); + let node_options = { + let sys = state.borrow::(); + sys.env_var("NODE_OPTIONS").ok() + }; + if let Some(node_options) = node_options { + for arg in split_node_options(&node_options) { + parse_option(scope, options, &arg); + } + } + for arg in process_exec_argv(scope, state) { + parse_option(scope, options, &arg); + } + state.borrow_mut::().options_map = + Some(v8::Global::new(scope, options)); + options_result(scope, options) +} + +#[op2] +pub fn op_node_options_get_exec_argv_options<'s>( + state: &mut OpState, + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let cached = state + .borrow::() + .exec_argv_options_map + .clone(); + if let Some(cached) = cached { + let options = v8::Local::new(scope, &cached); + return options_result(scope, options); + } + + let options = v8::Map::new(scope); + for arg in process_exec_argv(scope, state) { + parse_option(scope, options, &arg); + } + state.borrow_mut::().exec_argv_options_map = + Some(v8::Global::new(scope, options)); + options_result(scope, options) +} diff --git a/ext/node/ops/mod.rs b/ext/node/ops/mod.rs index 74f57aa8b5e441..4014277cdb54ed 100644 --- a/ext/node/ops/mod.rs +++ b/ext/node/ops/mod.rs @@ -13,6 +13,7 @@ pub mod inspector; pub mod internal_binding; pub mod internal_binding_constants; pub mod internal_binding_crypto; +pub mod internal_binding_node_options; pub mod internal_binding_uv; pub mod ipc; pub mod llhttp; diff --git a/ext/node/polyfills/internal_binding/node_options.ts b/ext/node/polyfills/internal_binding/node_options.ts index d27e086cb1d11c..24fee7fc799813 100644 --- a/ext/node/polyfills/internal_binding/node_options.ts +++ b/ext/node/polyfills/internal_binding/node_options.ts @@ -1,182 +1,15 @@ // Copyright 2018-2026 the Deno authors. MIT license. + (function () { -const { primordials } = __bootstrap; const { - SafeMap, - ArrayPrototypeForEach, - ArrayPrototypePush, - ArrayPrototypeConcat, - ArrayPrototypeSlice, - StringPrototypeSlice, - StringPrototypeStartsWith, -} = primordials; - -// This module ports: -// - https://github.com/nodejs/node/blob/master/src/node_options-inl.h -// - https://github.com/nodejs/node/blob/master/src/node_options.cc -// - https://github.com/nodejs/node/blob/master/src/node_options.h - -// Quote-aware tokenizer for NODE_OPTIONS. Node.js uses a shell-like parser -// that respects single and double quotes, so `--title="hello world"` is a -// single token whose value is `hello world`, not two tokens. -function splitNodeOptions(input: string): string[] { - const args: string[] = []; - let current = ""; - let inDouble = false; - let inSingle = false; - - for (let i = 0; i < input.length; i++) { - const ch = input[i]; - if (ch === '"' && !inSingle) { - inDouble = !inDouble; - } else if (ch === "'" && !inDouble) { - inSingle = !inSingle; - } else if ( - (ch === " " || ch === "\t" || ch === "\n" || ch === "\r") && !inDouble && - !inSingle - ) { - if (current.length > 0) { - ArrayPrototypePush(args, current); - current = ""; - } - } else { - current += ch; - } - } - if (current.length > 0) { - ArrayPrototypePush(args, current); - } - return args; -} - -/** Gets the all options for Node.js - * This function is expensive to execute. `getOptionValue` in `internal/options.ts` - * should be used instead to get a specific option. */ -type OptionValue = { value: string | boolean }; - -let optionsMap: Map | undefined; -let execArgvOptionsMap: Map | undefined; -let execArgvSnapshot: string[] | undefined; - -function setOptionSourceExecArgv(execArgv: string[]) { - execArgvSnapshot = ArrayPrototypeSlice(execArgv); - optionsMap = undefined; - execArgvOptionsMap = undefined; -} - -function createDefaultOptions() { - return new SafeMap([ - ["--warnings", { value: true }], - ["--pending-deprecation", { value: false }], - ["--expose-internals", { value: false }], - ["--title", { value: "" }], - ]); -} - -function parseOption(options: Map, arg: string) { - if (StringPrototypeStartsWith(arg, "--title=")) { - options.set("--title", { value: StringPrototypeSlice(arg, 8) }); - return; - } - if (StringPrototypeStartsWith(arg, "--tls-cipher-list=")) { - options.set("--tls-cipher-list", { - value: StringPrototypeSlice(arg, "--tls-cipher-list=".length), - }); - return; - } - switch (arg) { - case "--no-warnings": - options.set("--warnings", { value: false }); - break; - case "--pending-deprecation": - options.set("--pending-deprecation", { value: true }); - break; - case "--expose-internals": - case "--expose_internals": - options.set("--expose-internals", { value: true }); - break; - case "--tls-min-v1.0": - case "--tls-min-v1.1": - case "--tls-min-v1.2": - case "--tls-min-v1.3": - case "--tls-max-v1.2": - case "--tls-max-v1.3": - case "--use-bundled-ca": - case "--use-openssl-ca": - case "--use-system-ca": - options.set(arg, { value: true }); - break; - case "--no-tls-min-v1.0": - options.set("--tls-min-v1.0", { value: false }); - break; - case "--no-tls-min-v1.1": - options.set("--tls-min-v1.1", { value: false }); - break; - case "--no-tls-min-v1.2": - options.set("--tls-min-v1.2", { value: false }); - break; - case "--no-tls-min-v1.3": - options.set("--tls-min-v1.3", { value: false }); - break; - case "--no-tls-max-v1.2": - options.set("--tls-max-v1.2", { value: false }); - break; - case "--no-tls-max-v1.3": - options.set("--tls-max-v1.3", { value: false }); - break; - case "--no-use-bundled-ca": - options.set("--use-bundled-ca", { value: false }); - break; - case "--no-use-openssl-ca": - options.set("--use-openssl-ca", { value: false }); - break; - case "--no-use-system-ca": - options.set("--use-system-ca", { value: false }); - break; - default: - if (StringPrototypeStartsWith(arg, "--dns-result-order=")) { - const value = StringPrototypeSlice( - arg, - "--dns-result-order=".length, - ); - options.set("--dns-result-order", { value }); - } - break; - } -} - -function getExecArgv() { - return execArgvSnapshot ?? globalThis.process?.execArgv ?? []; -} - -function getOptions() { - if (optionsMap) { - return { options: optionsMap }; - } - - const options = createDefaultOptions(); - const nodeOptions = Deno.env.get("NODE_OPTIONS"); - const envArgs = nodeOptions ? splitNodeOptions(nodeOptions) : []; - const execArgv = getExecArgv(); - const args = ArrayPrototypeConcat(envArgs, execArgv); - ArrayPrototypeForEach(args, (arg) => parseOption(options, arg)); - optionsMap = options; - return { options }; -} - -function getExecArgvOptions() { - if (execArgvOptionsMap) { - return { options: execArgvOptionsMap }; - } - const options = new SafeMap(); - ArrayPrototypeForEach(getExecArgv(), (arg) => parseOption(options, arg)); - execArgvOptionsMap = options; - return { options }; -} + op_node_options_get_exec_argv_options, + op_node_options_get_options, + op_node_options_set_exec_argv, +} = __bootstrap.core.ops; return { - getExecArgvOptions, - getOptions, - setOptionSourceExecArgv, + getExecArgvOptions: op_node_options_get_exec_argv_options, + getOptions: op_node_options_get_options, + setOptionSourceExecArgv: op_node_options_set_exec_argv, }; })(); From 58f36cdc911bbed4b0a782d0ac0485ba9a63c7d2 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 13:03:00 -0700 Subject: [PATCH 07/33] port node utils internal binding to rust --- ext/node/lib.rs | 4 + ext/node/ops/internal_binding_utils.rs | 305 ++++++++++++++++++ ext/node/ops/mod.rs | 1 + ext/node/polyfills/internal_binding/_utils.ts | 165 +--------- 4 files changed, 312 insertions(+), 163 deletions(-) create mode 100644 ext/node/ops/internal_binding_utils.rs diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 457d4977354015..3fc29233d326b0 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -342,6 +342,7 @@ deno_core::extension!(deno_node, ops::internal_binding_node_options::op_node_options_get_exec_argv_options, ops::internal_binding_node_options::op_node_options_get_options, ops::internal_binding_node_options::op_node_options_set_exec_argv, + ops::internal_binding_utils::op_node_internal_binding_utils, ops::internal_binding_uv::op_node_internal_binding_uv, ops::internal_binding_uv::op_node_uv_errname, ops::internal_binding_uv::op_node_uv_get_code_map, @@ -917,6 +918,9 @@ deno_core::extension!(deno_node, ext.external_references.to_mut().extend( ops::blocklist::internal_binding_external_references(), ); + ext.external_references.to_mut().extend( + ops::internal_binding_utils::external_references(), + ); }, ); diff --git a/ext/node/ops/internal_binding_utils.rs b/ext/node/ops/internal_binding_utils.rs new file mode 100644 index 00000000000000..36232427d45ab8 --- /dev/null +++ b/ext/node/ops/internal_binding_utils.rs @@ -0,0 +1,305 @@ +// Copyright 2018-2026 the Deno authors. MIT license. + +use deno_core::op2; +use deno_core::v8; +use deno_core::v8::ExternalReference; +use deno_core::v8::MapFnTo; + +const UNHEX_TABLE: [i8; 256] = { + let mut table = [-1; 256]; + let mut i = 0; + while i < 10 { + table[b'0' as usize + i] = i as i8; + i += 1; + } + i = 0; + while i < 6 { + table[b'A' as usize + i] = (10 + i) as i8; + table[b'a' as usize + i] = (10 + i) as i8; + i += 1; + } + table +}; + +fn throw_type_error(scope: &mut v8::PinScope, message: &str) { + let message = v8::String::new(scope, message).unwrap(); + let exception = v8::Exception::type_error(scope, message); + scope.throw_exception(exception); +} + +fn throw_error(scope: &mut v8::PinScope, message: &str) { + let message = v8::String::new(scope, message).unwrap(); + let exception = v8::Exception::error(scope, message); + scope.throw_exception(exception); +} + +fn set_value( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: v8::Local, +) { + let key = v8::String::new(scope, name).unwrap(); + obj.set(scope, key.into(), value); +} + +fn set_function( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + function: v8::Local, +) { + set_value(scope, obj, name, function.into()); +} + +fn arg_string( + scope: &mut v8::PinScope, + args: &v8::FunctionCallbackArguments, +) -> Option { + let value = args.get(0); + let value = value.to_string(scope)?; + Some(value.to_rust_string_lossy(scope)) +} + +fn uint8_array<'s>( + scope: &mut v8::PinScope<'s, '_>, + bytes: Vec, +) -> v8::Local<'s, v8::Uint8Array> { + let len = bytes.len(); + let store = v8::ArrayBuffer::new_backing_store_from_vec(bytes).make_shared(); + let array_buffer = v8::ArrayBuffer::with_backing_store(scope, &store); + v8::Uint8Array::new(scope, array_buffer, 0, len).unwrap() +} + +fn int8_array<'s>( + scope: &mut v8::PinScope<'s, '_>, + bytes: Vec, +) -> v8::Local<'s, v8::Int8Array> { + let len = bytes.len(); + let store = v8::ArrayBuffer::new_backing_store_from_vec(bytes).make_shared(); + let array_buffer = v8::ArrayBuffer::with_backing_store(scope, &store); + v8::Int8Array::new(scope, array_buffer, 0, len).unwrap() +} + +fn simdutf_base64_decode(input: &[u8]) -> Option> { + use v8::simdutf; + + let max_len = simdutf::maximal_binary_length_from_base64(input); + let mut output = vec![0; max_len]; + let result = unsafe { + simdutf::base64_to_binary( + input, + &mut output, + simdutf::Base64Options::Default, + simdutf::LastChunkHandling::Loose, + ) + }; + if result.is_ok() { + output.truncate(result.count); + Some(output) + } else { + None + } +} + +fn base64_clean(input: &str) -> String { + let trimmed = if let Some(eq_index) = input.find('=') { + input[..eq_index].trim_start() + } else { + input.trim() + }; + let mut cleaned = String::with_capacity(trimmed.len() + 2); + for ch in trimmed.chars() { + if matches!(ch, '+' | '/' | '-' | '_') || ch.is_ascii_alphanumeric() { + cleaned.push(ch); + } + } + let len = cleaned.len(); + if len < 2 { + return String::new(); + } + match len % 4 { + 0 => cleaned, + 1 => { + cleaned.pop(); + cleaned + } + 2 => { + cleaned.push_str("=="); + cleaned + } + 3 => { + cleaned.push('='); + cleaned + } + _ => unreachable!(), + } +} + +fn ascii_to_bytes_callback( + scope: &mut v8::PinScope, + args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + let Some(str) = arg_string(scope, &args) else { + throw_type_error(scope, "str must be a string"); + return; + }; + let bytes = str.encode_utf16().map(|c| c as u8).collect::>(); + let bytes = uint8_array(scope, bytes); + rv.set(bytes.into()); +} + +fn base64_to_bytes_callback( + scope: &mut v8::PinScope, + args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + let Some(str) = arg_string(scope, &args) else { + throw_type_error(scope, "str must be a string"); + return; + }; + let bytes = simdutf_base64_decode(str.as_bytes()).or_else(|| { + let standard = str.replace('-', "+").replace('_', "/"); + let cleaned = base64_clean(&standard); + simdutf_base64_decode(cleaned.as_bytes()) + }); + let Some(bytes) = bytes else { + throw_error(scope, "Failed to decode base64"); + return; + }; + let bytes = uint8_array(scope, bytes); + rv.set(bytes.into()); +} + +fn base64_url_to_bytes_callback( + scope: &mut v8::PinScope, + args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + let Some(str) = arg_string(scope, &args) else { + throw_type_error(scope, "str must be a string"); + return; + }; + let cleaned = base64_clean(&str); + let standard = cleaned.replace('-', "+").replace('_', "/"); + let Some(bytes) = simdutf_base64_decode(standard.as_bytes()) else { + throw_error(scope, "Failed to decode base64url"); + return; + }; + let bytes = uint8_array(scope, bytes); + rv.set(bytes.into()); +} + +fn hex_to_bytes_callback( + scope: &mut v8::PinScope, + args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + let Some(str) = arg_string(scope, &args) else { + throw_type_error(scope, "str must be a string"); + return; + }; + let code_units = str.encode_utf16().collect::>(); + let length = code_units.len() >> 1; + let mut bytes = Vec::with_capacity(length); + for i in 0..length { + let a = UNHEX_TABLE[(code_units[i * 2] & 0xff) as usize]; + let b = UNHEX_TABLE[(code_units[i * 2 + 1] & 0xff) as usize]; + if a == -1 || b == -1 { + break; + } + bytes.push(((a << 4) | b) as u8); + } + let bytes = uint8_array(scope, bytes); + rv.set(bytes.into()); +} + +fn utf16le_to_bytes_callback( + scope: &mut v8::PinScope, + args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + let Some(str) = arg_string(scope, &args) else { + throw_type_error(scope, "str must be a string"); + return; + }; + let mut length = str.encode_utf16().count() * 2; + let units = args.get(1); + if !units.is_undefined() && units.boolean_value(scope) { + let units = units.uint32_value(scope).unwrap_or(0) as usize; + length = length.min((units >> 1) * 2); + } + let mut bytes = Vec::with_capacity(length); + for code_unit in str.encode_utf16().take(length / 2) { + bytes.push(code_unit as u8); + bytes.push((code_unit >> 8) as u8); + } + let bytes = uint8_array(scope, bytes); + rv.set(bytes.into()); +} + +pub(crate) fn external_references() -> [ExternalReference; 5] { + [ + ExternalReference { + function: ascii_to_bytes_callback.map_fn_to(), + }, + ExternalReference { + function: base64_to_bytes_callback.map_fn_to(), + }, + ExternalReference { + function: base64_url_to_bytes_callback.map_fn_to(), + }, + ExternalReference { + function: hex_to_bytes_callback.map_fn_to(), + }, + ExternalReference { + function: utf16le_to_bytes_callback.map_fn_to(), + }, + ] +} + +#[op2] +pub fn op_node_internal_binding_utils<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + + let ascii_to_bytes = + v8::FunctionTemplate::new(scope, ascii_to_bytes_callback) + .get_function(scope) + .unwrap(); + set_function(scope, obj, "asciiToBytes", ascii_to_bytes); + + let base64_to_bytes = + v8::FunctionTemplate::new(scope, base64_to_bytes_callback) + .get_function(scope) + .unwrap(); + set_function(scope, obj, "base64ToBytes", base64_to_bytes); + + let base64_url_to_bytes = + v8::FunctionTemplate::new(scope, base64_url_to_bytes_callback) + .get_function(scope) + .unwrap(); + set_function(scope, obj, "base64UrlToBytes", base64_url_to_bytes); + + let hex_to_bytes = v8::FunctionTemplate::new(scope, hex_to_bytes_callback) + .get_function(scope) + .unwrap(); + set_function(scope, obj, "hexToBytes", hex_to_bytes); + + let utf16le_to_bytes = + v8::FunctionTemplate::new(scope, utf16le_to_bytes_callback) + .get_function(scope) + .unwrap(); + set_function(scope, obj, "utf16leToBytes", utf16le_to_bytes); + + let table = UNHEX_TABLE + .iter() + .map(|value| *value as u8) + .collect::>(); + let table = int8_array(scope, table); + set_value(scope, obj, "unhexTable", table.into()); + + obj +} diff --git a/ext/node/ops/mod.rs b/ext/node/ops/mod.rs index 4014277cdb54ed..eebc5666174ad6 100644 --- a/ext/node/ops/mod.rs +++ b/ext/node/ops/mod.rs @@ -14,6 +14,7 @@ pub mod internal_binding; pub mod internal_binding_constants; pub mod internal_binding_crypto; pub mod internal_binding_node_options; +pub mod internal_binding_utils; pub mod internal_binding_uv; pub mod ipc; pub mod llhttp; diff --git a/ext/node/polyfills/internal_binding/_utils.ts b/ext/node/polyfills/internal_binding/_utils.ts index 73fb219b3c408a..4800d30671ddc6 100644 --- a/ext/node/polyfills/internal_binding/_utils.ts +++ b/ext/node/polyfills/internal_binding/_utils.ts @@ -1,167 +1,6 @@ // Copyright 2018-2026 the Deno authors. MIT license. (function () { -const { core, primordials } = __bootstrap; -const { - forgivingBase64Decode, - forgivingBase64UrlDecode, -} = core.loadExtScript("ext:deno_web/00_infra.js"); -const { - DataView, - DataViewPrototypeSetUint16, - Error, - Int8Array, - MathMin, - NumberPOSITIVE_INFINITY, - SafeRegExp, - StringPrototypeCharCodeAt, - StringPrototypeIndexOf, - StringPrototypeReplace, - StringPrototypeReplaceAll, - StringPrototypeSubstring, - StringPrototypeTrim, - StringPrototypeTrimStart, - TypedArrayPrototypeGetBuffer, - TypedArrayPrototypeSubarray, - Uint8Array, -} = primordials; - -function asciiToBytes(str: string) { - const length = str.length; - const byteArray = new Uint8Array(length); - for (let i = 0; i < length; ++i) { - byteArray[i] = StringPrototypeCharCodeAt(str, i) & 255; - } - return byteArray; -} - -function base64ToBytes(str: string) { - try { - return forgivingBase64Decode(str); - } catch { - // Convert base64url characters to standard base64 before cleaning, - // so that the padding logic in base64clean works correctly. - str = StringPrototypeReplaceAll( - StringPrototypeReplaceAll(str, "-", "+"), - "_", - "/", - ); - str = base64clean(str); - return forgivingBase64Decode(str); - } -} - -const INVALID_BASE64_RE = new SafeRegExp(/[^+/0-9A-Za-z-_]/g); -function base64clean(str: string) { - // Node takes equal signs as end of the Base64 encoding - const eqIndex = StringPrototypeIndexOf(str, "="); - str = eqIndex !== -1 - ? StringPrototypeTrimStart(StringPrototypeSubstring(str, 0, eqIndex)) - : StringPrototypeTrim(str); - // Node strips out invalid characters like \n and \t from the string, std/base64 does not - str = StringPrototypeReplace(str, INVALID_BASE64_RE, ""); - // Node converts strings with length < 2 to '' - const length = str.length; - if (length < 2) return ""; - // Node allows for non-padded base64 strings (missing trailing ===), std/base64 does not - switch (length % 4) { - case 0: - return str; - case 1: - // A single base64 char can't encode a full byte; drop it like Node does - return StringPrototypeSubstring(str, 0, length - 1); - case 2: - return `${str}==`; - case 3: - return `${str}=`; - default: - throw new Error("Unexpected NaN value for string length"); - } -} - -function base64UrlToBytes(str: string) { - str = base64clean(str); - str = StringPrototypeReplaceAll( - StringPrototypeReplaceAll(str, "+", "-"), - "/", - "_", - ); - return forgivingBase64UrlDecode(str); -} - -// https://github.com/nodejs/node/blob/591ba692bfe30408e6a67397e7d18bfa1b9c3561/deps/nbytes/src/nbytes.cpp#L144-L158 -// deno-fmt-ignore -const unhexTable = new Int8Array([ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0 - 15 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16 - 31 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 32 - 47 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 48 - 63 - -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 64 - 79 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80 - 95 - -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 96 - 111 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 112 - 127 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 128 ... - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // ... 255 - ]); - -function hexToBytes(str: string) { - const length = str.length >>> 1; - const byteArray = new Uint8Array(length); - let i: number; - for (i = 0; i < length; i++) { - const a = unhexTable[StringPrototypeCharCodeAt(str, i * 2) & 0xff]; - const b = unhexTable[StringPrototypeCharCodeAt(str, i * 2 + 1) & 0xff]; - if (!~a || !~b) { - break; - } - byteArray[i] = (a << 4) | b; - } - // Returning a buffer subarray is okay: This API's return value - // is never exposed to users and is only ever used for its length - // and the data within the subarray. - return i === length - ? byteArray - : TypedArrayPrototypeSubarray(byteArray, 0, i); -} - -function utf16leToBytes(str: string, units?: number) { - // If units is defined, round it to even values for 16 byte "steps" - // and use it as an upper bound value for our string byte array's length. - const length = MathMin( - str.length * 2, - units ? (units >>> 1) * 2 : NumberPOSITIVE_INFINITY, - ); - const byteArray = new Uint8Array(length); - const view = new DataView(TypedArrayPrototypeGetBuffer(byteArray)); - let i: number; - for (i = 0; i * 2 < length; i++) { - DataViewPrototypeSetUint16( - view, - i * 2, - StringPrototypeCharCodeAt(str, i), - true, - ); - } - // Returning a buffer subarray is okay: This API's return value - // is never exposed to users and is only ever used for its length - // and the data within the subarray. - return i * 2 === length - ? byteArray - : TypedArrayPrototypeSubarray(byteArray, 0, i * 2); -} - -return { - asciiToBytes, - base64ToBytes, - base64UrlToBytes, - hexToBytes, - utf16leToBytes, - unhexTable, -}; +const { op_node_internal_binding_utils } = __bootstrap.core.ops; +return op_node_internal_binding_utils(); })(); From 1c7a60d224eefb1782ad25771fa4464aefdcc8ba Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 13:09:43 -0700 Subject: [PATCH 08/33] port async wrap internal binding to rust --- ext/node/lib.rs | 4 + ext/node/ops/internal_binding_async_wrap.rs | 178 ++++++++++++++++++ ext/node/ops/mod.rs | 1 + .../polyfills/internal_binding/async_wrap.ts | 139 +------------- 4 files changed, 188 insertions(+), 134 deletions(-) create mode 100644 ext/node/ops/internal_binding_async_wrap.rs diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 3fc29233d326b0..91a92006d5f5f0 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -333,6 +333,7 @@ deno_core::extension!(deno_node, ops::internal_binding::op_node_internal_binding_tty_wrap, ops::internal_binding::op_node_internal_binding_types, ops::internal_binding::op_node_ares_strerror, + ops::internal_binding_async_wrap::op_node_internal_binding_async_wrap, ops::internal_binding_constants::op_node_internal_binding_constants, ops::internal_binding_crypto::op_node_crypto_get_fips, ops::internal_binding_crypto::op_node_crypto_set_fips, @@ -921,6 +922,9 @@ deno_core::extension!(deno_node, ext.external_references.to_mut().extend( ops::internal_binding_utils::external_references(), ); + ext.external_references.to_mut().extend( + ops::internal_binding_async_wrap::external_references(), + ); }, ); diff --git a/ext/node/ops/internal_binding_async_wrap.rs b/ext/node/ops/internal_binding_async_wrap.rs new file mode 100644 index 00000000000000..de1cf04a5a53a3 --- /dev/null +++ b/ext/node/ops/internal_binding_async_wrap.rs @@ -0,0 +1,178 @@ +// Copyright 2018-2026 the Deno authors. MIT license. + +use deno_core::op2; +use deno_core::v8; +use deno_core::v8::ExternalReference; +use deno_core::v8::MapFnTo; + +const CONSTANTS: &[&str] = &[ + "kInit", + "kBefore", + "kAfter", + "kDestroy", + "kPromiseResolve", + "kTotals", + "kCheck", + "kExecutionAsyncId", + "kTriggerAsyncId", + "kAsyncIdCounter", + "kDefaultTriggerAsyncId", + "kUsesExecutionAsyncResource", + "kStackLength", +]; + +const UID_FIELDS: &[&str] = &[ + "kExecutionAsyncId", + "kTriggerAsyncId", + "kDefaultTriggerAsyncId", + "kUidFieldsCount", +]; + +const PROVIDER_TYPES: &[&str] = &[ + "NONE", + "DIRHANDLE", + "DNSCHANNEL", + "ELDHISTOGRAM", + "FILEHANDLE", + "FILEHANDLECLOSEREQ", + "FIXEDSIZEBLOBCOPY", + "FSEVENTWRAP", + "FSREQCALLBACK", + "FSREQPROMISE", + "GETADDRINFOREQWRAP", + "GETNAMEINFOREQWRAP", + "HEAPSNAPSHOT", + "HTTP2SESSION", + "HTTP2STREAM", + "HTTP2PING", + "HTTP2SETTINGS", + "HTTPINCOMINGMESSAGE", + "HTTPCLIENTREQUEST", + "JSSTREAM", + "JSUDPWRAP", + "MESSAGEPORT", + "PIPECONNECTWRAP", + "PIPESERVERWRAP", + "PIPEWRAP", + "PROCESSWRAP", + "PROMISE", + "QUERYWRAP", + "SHUTDOWNWRAP", + "SIGNALWRAP", + "STATWATCHER", + "STREAMPIPE", + "TCPCONNECTWRAP", + "TCPSERVERWRAP", + "TCPWRAP", + "TTYWRAP", + "UDPSENDWRAP", + "UDPWRAP", + "SIGINTWATCHDOG", + "WORKER", + "WORKERHEAPSNAPSHOT", + "WRITEWRAP", + "ZLIB", +]; + +fn set_value( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: v8::Local, +) { + let key = v8::String::new(scope, name).unwrap(); + obj.set(scope, key.into(), value); +} + +fn set_number( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: u32, +) { + let value = v8::Integer::new_from_unsigned(scope, value).into(); + set_value(scope, obj, name, value); +} + +fn create_enum_object<'s>( + scope: &mut v8::PinScope<'s, '_>, + names: &[&str], +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + for (index, name) in names.iter().enumerate() { + set_number(scope, obj, name, index as u32); + let key = v8::Integer::new_from_unsigned(scope, index as u32); + let value = v8::String::new(scope, name).unwrap(); + obj.set(scope, key.into(), value.into()); + } + obj +} + +fn create_uint32_array<'s>( + scope: &mut v8::PinScope<'s, '_>, + length: usize, +) -> v8::Local<'s, v8::Uint32Array> { + let array_buffer = v8::ArrayBuffer::new(scope, length * 4); + v8::Uint32Array::new(scope, array_buffer, 0, length).unwrap() +} + +fn create_float64_array<'s>( + scope: &mut v8::PinScope<'s, '_>, + length: usize, +) -> v8::Local<'s, v8::Float64Array> { + let array_buffer = v8::ArrayBuffer::new(scope, length * 8); + v8::Float64Array::new(scope, array_buffer, 0, length).unwrap() +} + +fn register_destroy_hook_callback( + _scope: &mut v8::PinScope, + _args: v8::FunctionCallbackArguments, + _rv: v8::ReturnValue, +) { +} + +pub(crate) fn external_references() -> [ExternalReference; 1] { + [ExternalReference { + function: register_destroy_hook_callback.map_fn_to(), + }] +} + +#[op2] +pub fn op_node_internal_binding_async_wrap<'s>( + scope: &mut v8::PinScope<'s, '_>, + async_wrap: v8::Local<'s, v8::Value>, + new_async_id: v8::Local<'s, v8::Value>, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + + let async_hook_fields = create_uint32_array(scope, CONSTANTS.len() * 2); + set_value(scope, obj, "async_hook_fields", async_hook_fields.into()); + + let async_id_fields = create_float64_array(scope, UID_FIELDS.len() * 2); + let default_trigger_async_id = v8::Number::new(scope, -1.0); + async_id_fields.set_index(scope, 2, default_trigger_async_id.into()); + set_value(scope, obj, "asyncIdFields", async_id_fields.into()); + + set_value(scope, obj, "AsyncWrap", async_wrap); + + let register_destroy_hook = + v8::FunctionTemplate::new(scope, register_destroy_hook_callback) + .get_function(scope) + .unwrap(); + set_value( + scope, + obj, + "registerDestroyHook", + register_destroy_hook.into(), + ); + + set_value(scope, obj, "newAsyncId", new_async_id); + + let constants = create_enum_object(scope, CONSTANTS); + set_value(scope, obj, "constants", constants.into()); + let uid_fields = create_enum_object(scope, UID_FIELDS); + set_value(scope, obj, "UidFields", uid_fields.into()); + let provider_type = create_enum_object(scope, PROVIDER_TYPES); + set_value(scope, obj, "providerType", provider_type.into()); + obj +} diff --git a/ext/node/ops/mod.rs b/ext/node/ops/mod.rs index eebc5666174ad6..94f14f4e718151 100644 --- a/ext/node/ops/mod.rs +++ b/ext/node/ops/mod.rs @@ -11,6 +11,7 @@ pub mod http2; pub mod idna; pub mod inspector; pub mod internal_binding; +pub mod internal_binding_async_wrap; pub mod internal_binding_constants; pub mod internal_binding_crypto; pub mod internal_binding_node_options; diff --git a/ext/node/polyfills/internal_binding/async_wrap.ts b/ext/node/polyfills/internal_binding/async_wrap.ts index a4eaa311e7c549..fb871e4be4807c 100644 --- a/ext/node/polyfills/internal_binding/async_wrap.ts +++ b/ext/node/polyfills/internal_binding/async_wrap.ts @@ -1,140 +1,11 @@ // Copyright 2018-2026 the Deno authors. MIT license. -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -// This module ports: -// - https://github.com/nodejs/node/blob/master/src/async_wrap-inl.h -// - https://github.com/nodejs/node/blob/master/src/async_wrap.cc -// - https://github.com/nodejs/node/blob/master/src/async_wrap.h (function () { -const { core, primordials } = __bootstrap; -const { AsyncWrap, op_node_new_async_id } = core.ops; const { - Float64Array, - ObjectKeys, - Uint32Array, -} = primordials; - -function registerDestroyHook( - // deno-lint-ignore no-explicit-any - _target: any, - _asyncId: number, - _prop: { destroyed: boolean }, -) { - // TODO(kt3k): implement actual procedures -} - -enum constants { - kInit, - kBefore, - kAfter, - kDestroy, - kPromiseResolve, - kTotals, - kCheck, - kExecutionAsyncId, - kTriggerAsyncId, - kAsyncIdCounter, - kDefaultTriggerAsyncId, - kUsesExecutionAsyncResource, - kStackLength, -} - -const asyncHookFields = new Uint32Array(ObjectKeys(constants).length); - -// Increment the internal id counter and return the value. -function newAsyncId() { - return op_node_new_async_id(); -} - -enum UidFields { - kExecutionAsyncId, - kTriggerAsyncId, - kDefaultTriggerAsyncId, - kUidFieldsCount, -} - -const asyncIdFields = new Float64Array(ObjectKeys(UidFields).length); - -// `kDefaultTriggerAsyncId` should be `-1`, this indicates that there is no -// specified default value and it should fallback to the executionAsyncId. -// 0 is not used as the magic value, because that indicates a missing -// context which is different from a default context. -asyncIdFields[UidFields.kDefaultTriggerAsyncId] = -1; - -enum providerType { - NONE, - DIRHANDLE, - DNSCHANNEL, - ELDHISTOGRAM, - FILEHANDLE, - FILEHANDLECLOSEREQ, - FIXEDSIZEBLOBCOPY, - FSEVENTWRAP, - FSREQCALLBACK, - FSREQPROMISE, - GETADDRINFOREQWRAP, - GETNAMEINFOREQWRAP, - HEAPSNAPSHOT, - HTTP2SESSION, - HTTP2STREAM, - HTTP2PING, - HTTP2SETTINGS, - HTTPINCOMINGMESSAGE, - HTTPCLIENTREQUEST, - JSSTREAM, - JSUDPWRAP, - MESSAGEPORT, - PIPECONNECTWRAP, - PIPESERVERWRAP, - PIPEWRAP, - PROCESSWRAP, - PROMISE, - QUERYWRAP, - SHUTDOWNWRAP, - SIGNALWRAP, - STATWATCHER, - STREAMPIPE, - TCPCONNECTWRAP, - TCPSERVERWRAP, - TCPWRAP, - TTYWRAP, - UDPSENDWRAP, - UDPWRAP, - SIGINTWATCHDOG, - WORKER, - WORKERHEAPSNAPSHOT, - WRITEWRAP, - ZLIB, -} - -return { - async_hook_fields: asyncHookFields, - asyncIdFields, AsyncWrap, - registerDestroyHook, - newAsyncId, - constants, - UidFields, - providerType, -}; + op_node_internal_binding_async_wrap, + op_node_new_async_id, +} = __bootstrap.core.ops; + +return op_node_internal_binding_async_wrap(AsyncWrap, op_node_new_async_id); })(); From 2e45aebd0da2ee2ee949c11f6d866177f908ece1 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 13:13:33 -0700 Subject: [PATCH 09/33] port stream wrap internal binding to rust --- ext/node/lib.rs | 3 + ext/node/ops/stream_wrap.rs | 83 +++++++++++++++++ .../polyfills/internal_binding/stream_wrap.ts | 93 +------------------ 3 files changed, 91 insertions(+), 88 deletions(-) diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 91a92006d5f5f0..1c4f4f876fc3d7 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -456,6 +456,7 @@ deno_core::extension!(deno_node, ops::udp::op_node_udp_recv, ops::udp::op_node_udp_fd_for_ipc, ops::udp::op_node_udp_open, + ops::stream_wrap::op_node_internal_binding_stream_wrap, ops::stream_wrap::op_stream_base_register_state, ops::tty_wrap::op_tty_check_fd_permission, ], @@ -465,6 +466,8 @@ deno_core::extension!(deno_node, ops::handle_wrap::AsyncWrap, ops::handle_wrap::HandleWrap, ops::wasi::WasiContext, + ops::stream_wrap::WriteWrap, + ops::stream_wrap::ShutdownWrap, ops::stream_wrap::LibUvStreamWrap, ops::tty_wrap::TTY, ops::zlib::BrotliDecoder, diff --git a/ext/node/ops/stream_wrap.rs b/ext/node/ops/stream_wrap.rs index 0a921dcffaa5df..5d80c6bf5a2fe2 100644 --- a/ext/node/ops/stream_wrap.rs +++ b/ext/node/ops/stream_wrap.rs @@ -164,6 +164,15 @@ impl WriteWrap { } } +#[op2(base, inherit = AsyncWrap)] +impl WriteWrap { + #[constructor] + #[cppgc] + fn constructor(state: &mut OpState) -> WriteWrap { + WriteWrap::new(state) + } +} + // --------------------------------------------------------------------------- // ShutdownWrap — cppgc object that wraps a uv_shutdown_t request. // --------------------------------------------------------------------------- @@ -198,6 +207,80 @@ impl ShutdownWrap { } } +#[op2(base, inherit = AsyncWrap)] +impl ShutdownWrap { + #[constructor] + #[cppgc] + fn constructor(state: &mut OpState) -> ShutdownWrap { + ShutdownWrap::new(state) + } +} + +fn set_value( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: v8::Local, +) { + let key = v8::String::new(scope, name).unwrap(); + obj.set(scope, key.into(), value); +} + +fn create_int32_array<'s>( + scope: &mut v8::PinScope<'s, '_>, + length: usize, +) -> v8::Local<'s, v8::Int32Array> { + let array_buffer = v8::ArrayBuffer::new(scope, length * 4); + v8::Int32Array::new(scope, array_buffer, 0, length).unwrap() +} + +#[op2] +pub fn op_node_internal_binding_stream_wrap<'s>( + scope: &mut v8::PinScope<'s, '_>, + write_wrap: v8::Local<'s, v8::Value>, + shutdown_wrap: v8::Local<'s, v8::Value>, +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + set_value(scope, obj, "WriteWrap", write_wrap); + set_value(scope, obj, "ShutdownWrap", shutdown_wrap); + set_value( + scope, + obj, + "kReadBytesOrError", + v8::Integer::new(scope, StreamBaseStateFields::ReadBytesOrError as i32) + .into(), + ); + set_value( + scope, + obj, + "kArrayBufferOffset", + v8::Integer::new(scope, StreamBaseStateFields::ArrayBufferOffset as i32) + .into(), + ); + set_value( + scope, + obj, + "kBytesWritten", + v8::Integer::new(scope, StreamBaseStateFields::BytesWritten as i32).into(), + ); + set_value( + scope, + obj, + "kLastWriteWasAsync", + v8::Integer::new(scope, StreamBaseStateFields::LastWriteWasAsync as i32) + .into(), + ); + set_value( + scope, + obj, + "kNumStreamBaseStateFields", + v8::Integer::new(scope, 4).into(), + ); + let stream_base_state = create_int32_array(scope, 5); + set_value(scope, obj, "streamBaseState", stream_base_state.into()); + obj +} + // --------------------------------------------------------------------------- // LibUvStreamWrap — the core stream handle, mirrors Node's LibuvStreamWrap. // diff --git a/ext/node/polyfills/internal_binding/stream_wrap.ts b/ext/node/polyfills/internal_binding/stream_wrap.ts index d93ddf344a0221..55f747f3d8e2a8 100644 --- a/ext/node/polyfills/internal_binding/stream_wrap.ts +++ b/ext/node/polyfills/internal_binding/stream_wrap.ts @@ -1,94 +1,11 @@ // Copyright 2018-2026 the Deno authors. MIT license. -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. -// This module ports: -// - https://github.com/nodejs/node/blob/master/src/stream_base-inl.h -// - https://github.com/nodejs/node/blob/master/src/stream_base.h -// - https://github.com/nodejs/node/blob/master/src/stream_base.cc -// - https://github.com/nodejs/node/blob/master/src/stream_wrap.h -// - https://github.com/nodejs/node/blob/master/src/stream_wrap.cc (function () { -const { core, primordials } = __bootstrap; const { - Int32Array, -} = primordials; - -// deno-lint-ignore no-unused-vars -const { HandleWrap } = core.loadExtScript( - "ext:deno_node/internal_binding/handle_wrap.ts", -); -const { AsyncWrap, providerType } = core.loadExtScript( - "ext:deno_node/internal_binding/async_wrap.ts", -); - -const enum StreamBaseStateFields { - kReadBytesOrError, - kArrayBufferOffset, - kBytesWritten, - kLastWriteWasAsync, - kNumStreamBaseStateFields, -} - -const kReadBytesOrError = StreamBaseStateFields.kReadBytesOrError; -const kArrayBufferOffset = StreamBaseStateFields.kArrayBufferOffset; -const kBytesWritten = StreamBaseStateFields.kBytesWritten; -const kLastWriteWasAsync = StreamBaseStateFields.kLastWriteWasAsync; -const kNumStreamBaseStateFields = - StreamBaseStateFields.kNumStreamBaseStateFields; - -const streamBaseState = new Int32Array(5); - -class WriteWrap extends AsyncWrap { - handle!: H; - oncomplete!: (status: number) => void; - async!: boolean; - bytes!: number; - buffer!: unknown; - callback!: unknown; - _chunks!: unknown[]; - - constructor() { - super(providerType.WRITEWRAP); - } -} - -class ShutdownWrap extends AsyncWrap { - handle!: H; - oncomplete!: (status: number) => void; - callback!: () => void; - - constructor() { - super(providerType.SHUTDOWNWRAP); - } -} - -return { - WriteWrap, + op_node_internal_binding_stream_wrap, ShutdownWrap, - kReadBytesOrError, - kArrayBufferOffset, - kBytesWritten, - kLastWriteWasAsync, - kNumStreamBaseStateFields, - streamBaseState, -}; + WriteWrap, +} = __bootstrap.core.ops; + +return op_node_internal_binding_stream_wrap(WriteWrap, ShutdownWrap); })(); From 8e514c145308a83886b1639ab4454f09e4101138 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 13:16:32 -0700 Subject: [PATCH 10/33] port tcp wrap internal binding to rust --- ext/node/lib.rs | 2 + ext/node/ops/tcp_wrap.rs | 100 ++++++++++++++++++ .../polyfills/internal_binding/tcp_wrap.ts | 90 +--------------- 3 files changed, 107 insertions(+), 85 deletions(-) diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 1c4f4f876fc3d7..c12484de8dd09e 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -456,6 +456,7 @@ deno_core::extension!(deno_node, ops::udp::op_node_udp_recv, ops::udp::op_node_udp_fd_for_ipc, ops::udp::op_node_udp_open, + ops::tcp_wrap::op_node_internal_binding_tcp_wrap, ops::stream_wrap::op_node_internal_binding_stream_wrap, ops::stream_wrap::op_stream_base_register_state, ops::tty_wrap::op_tty_check_fd_permission, @@ -475,6 +476,7 @@ deno_core::extension!(deno_node, ops::zlib::Zlib, ops::zlib::ZstdCompress, ops::zlib::ZstdDecompress, + ops::tcp_wrap::TCPConnectWrap, ops::tcp_wrap::TCPWrap, ops::pipe_wrap::PipeWrap, ops::tls_wrap::TLSWrap, diff --git a/ext/node/ops/tcp_wrap.rs b/ext/node/ops/tcp_wrap.rs index fbd36eaaac938b..d8d946dc2dc957 100644 --- a/ext/node/ops/tcp_wrap.rs +++ b/ext/node/ops/tcp_wrap.rs @@ -10,6 +10,7 @@ use std::cell::RefCell; use std::net::ToSocketAddrs; use std::rc::Rc; +use deno_core::CppgcBase; use deno_core::CppgcInherits; use deno_core::GarbageCollected; use deno_core::OpState; @@ -41,6 +42,105 @@ enum SocketType { Server = 1, } +#[derive(CppgcBase, CppgcInherits)] +#[cppgc_inherits_from(AsyncWrap)] +#[repr(C)] +pub struct TCPConnectWrap { + base: AsyncWrap, +} + +unsafe impl GarbageCollected for TCPConnectWrap { + fn get_name(&self) -> &'static std::ffi::CStr { + c"TCPConnectWrap" + } + + fn trace(&self, _visitor: &mut v8::cppgc::Visitor) {} +} + +#[op2(base, inherit = AsyncWrap)] +impl TCPConnectWrap { + #[constructor] + #[cppgc] + fn constructor(state: &mut OpState) -> TCPConnectWrap { + TCPConnectWrap { + base: AsyncWrap::create(state, ProviderType::TcpConnectWrap as i32), + } + } +} + +fn set_value( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: v8::Local, +) { + let key = v8::String::new(scope, name).unwrap(); + obj.set(scope, key.into(), value); +} + +fn set_number( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + number: u32, +) { + let value = v8::Integer::new_from_unsigned(scope, number).into(); + set_value(scope, obj, name, value); + let key = v8::Integer::new_from_unsigned(scope, number); + let reverse = v8::String::new(scope, name).unwrap(); + obj.set(scope, key.into(), reverse.into()); +} + +fn mark_stream_base( + scope: &mut v8::PinScope, + constructor: v8::Local, +) { + let Ok(constructor) = v8::Local::::try_from(constructor) else { + return; + }; + let prototype_key = v8::String::new(scope, "prototype").unwrap(); + let Some(prototype) = constructor.get(scope, prototype_key.into()) else { + return; + }; + let Ok(prototype) = v8::Local::::try_from(prototype) else { + return; + }; + let value = v8::Boolean::new(scope, true).into(); + set_value(scope, prototype, "isStreamBase", value); +} + +#[op2] +pub fn op_node_internal_binding_tcp_wrap<'s>( + scope: &mut v8::PinScope<'s, '_>, + tcp_wrap: v8::Local<'s, v8::Value>, + tcp_connect_wrap: v8::Local<'s, v8::Value>, +) -> v8::Local<'s, v8::Object> { + mark_stream_base(scope, tcp_wrap); + + let socket_type = v8::Object::new(scope); + set_number(scope, socket_type, "SOCKET", 0); + set_number(scope, socket_type, "SERVER", 1); + + let constants = v8::Object::new(scope); + set_number(scope, constants, "SOCKET", 0); + set_number(scope, constants, "SERVER", 1); + set_number(scope, constants, "UV_TCP_IPV6ONLY", 2); + set_number(scope, constants, "UV_TCP_REUSEPORT", 4); + + let default = v8::Object::new(scope); + set_value(scope, default, "TCPConnectWrap", tcp_connect_wrap); + set_value(scope, default, "constants", constants.into()); + set_value(scope, default, "TCP", tcp_wrap); + + let obj = v8::Object::new(scope); + set_value(scope, obj, "TCP", tcp_wrap); + set_value(scope, obj, "TCPConnectWrap", tcp_connect_wrap); + set_value(scope, obj, "socketType", socket_type.into()); + set_value(scope, obj, "constants", constants.into()); + set_value(scope, obj, "default", default.into()); + obj +} + // -- libuv callbacks (called from the event loop) -- /// Macro to set up a v8 scope from a uv stream's handle data and call a JS diff --git a/ext/node/polyfills/internal_binding/tcp_wrap.ts b/ext/node/polyfills/internal_binding/tcp_wrap.ts index 6256b8ab2c0149..157e289dcce788 100644 --- a/ext/node/polyfills/internal_binding/tcp_wrap.ts +++ b/ext/node/polyfills/internal_binding/tcp_wrap.ts @@ -1,91 +1,11 @@ // Copyright 2018-2026 the Deno authors. MIT license. -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -// This module ports: -// - https://github.com/nodejs/node/blob/master/src/tcp_wrap.cc -// - https://github.com/nodejs/node/blob/master/src/tcp_wrap.h - -// TCPWrap is a Rust CppGC object that inherits from LibUvStreamWrap. -// Read/write/shutdown ops come from the LibUvStreamWrap base class. -// TCP-specific ops (bind, listen, connect, accept, etc.) are on TCPWrap itself. -// -// This module adds thin JS wrappers for listen (to create client handles on -// accept) and for re-exporting types. (function () { -const { core } = __bootstrap; -const { TCPWrap } = core.ops; -const { AsyncWrap, providerType } = core.loadExtScript( - "ext:deno_node/internal_binding/async_wrap.ts", -); - -// Mark TCPWrap as a StreamBase handle, matching Node's StreamBase::AddMethods. -// This allows parser.consume(socket._handle) to detect it as consumable. -TCPWrap.prototype.isStreamBase = true; - -/** The type of TCP socket. */ -enum socketType { - SOCKET, - SERVER, -} - -class TCPConnectWrap extends AsyncWrap { - oncomplete!: ( - status: number, - handle: unknown, - req: TCPConnectWrap, - readable: boolean, - writeable: boolean, - ) => void; - address!: string; - port!: number; - localAddress!: string; - localPort!: number; - - constructor() { - super(providerType.TCPCONNECTWRAP); - } -} - -enum constants { - SOCKET = socketType.SOCKET, - SERVER = socketType.SERVER, - UV_TCP_IPV6ONLY, - UV_TCP_REUSEPORT = 4, -} - -// Re-export the Rust TCPWrap as TCP. - -const _defaultExport = { +const { + op_node_internal_binding_tcp_wrap, TCPConnectWrap, - constants, - TCP: TCPWrap, -}; + TCPWrap, +} = __bootstrap.core.ops; -return { - TCP: TCPWrap, - TCPConnectWrap, - socketType, - constants, - default: _defaultExport, -}; +return op_node_internal_binding_tcp_wrap(TCPWrap, TCPConnectWrap); })(); From c463680c252ea8031809a9aa1940540030d5b98b Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 13:19:19 -0700 Subject: [PATCH 11/33] port pipe wrap internal binding to rust --- ext/node/lib.rs | 2 + ext/node/ops/pipe_wrap.rs | 105 ++++++++++++++++++ .../polyfills/internal_binding/pipe_wrap.ts | 96 ++-------------- 3 files changed, 116 insertions(+), 87 deletions(-) diff --git a/ext/node/lib.rs b/ext/node/lib.rs index c12484de8dd09e..b57b641918017a 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -235,6 +235,7 @@ deno_core::extension!(deno_node, ops::fs::op_node_statfs_sync, ops::fs::op_node_statfs, ops::fs::op_node_create_pipe, + ops::pipe_wrap::op_node_internal_binding_pipe_wrap, ops::fs::op_node_fd_set_blocking, ops::fs::op_node_fs_close, ops::fs::op_node_fs_read_sync, @@ -478,6 +479,7 @@ deno_core::extension!(deno_node, ops::zlib::ZstdDecompress, ops::tcp_wrap::TCPConnectWrap, ops::tcp_wrap::TCPWrap, + ops::pipe_wrap::PipeConnectWrap, ops::pipe_wrap::PipeWrap, ops::tls_wrap::TLSWrap, ops::llhttp::binding::HTTPParser, diff --git a/ext/node/ops/pipe_wrap.rs b/ext/node/ops/pipe_wrap.rs index 63f8987f69508e..a3869920019542 100644 --- a/ext/node/ops/pipe_wrap.rs +++ b/ext/node/ops/pipe_wrap.rs @@ -11,6 +11,7 @@ use std::cell::Cell; use std::cell::RefCell; use std::rc::Rc; +use deno_core::CppgcBase; use deno_core::CppgcInherits; use deno_core::GarbageCollected; use deno_core::OpState; @@ -41,6 +42,110 @@ enum PipeType { Ipc = 2, } +#[derive(CppgcBase, CppgcInherits)] +#[cppgc_inherits_from(AsyncWrap)] +#[repr(C)] +pub struct PipeConnectWrap { + base: AsyncWrap, +} + +unsafe impl GarbageCollected for PipeConnectWrap { + fn get_name(&self) -> &'static std::ffi::CStr { + c"PipeConnectWrap" + } + + fn trace(&self, _visitor: &mut v8::cppgc::Visitor) {} +} + +#[op2(base, inherit = AsyncWrap)] +impl PipeConnectWrap { + #[constructor] + #[cppgc] + fn constructor(state: &mut OpState) -> PipeConnectWrap { + PipeConnectWrap { + base: AsyncWrap::create(state, ProviderType::PipeConnectWrap as i32), + } + } +} + +fn set_value( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: v8::Local, +) { + let key = v8::String::new(scope, name).unwrap(); + obj.set(scope, key.into(), value); +} + +fn set_number( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + number: u32, +) { + let value = v8::Integer::new_from_unsigned(scope, number).into(); + set_value(scope, obj, name, value); + let key = v8::Integer::new_from_unsigned(scope, number); + let reverse = v8::String::new(scope, name).unwrap(); + obj.set(scope, key.into(), reverse.into()); +} + +fn mark_stream_base( + scope: &mut v8::PinScope, + constructor: v8::Local, +) { + let Ok(constructor) = v8::Local::::try_from(constructor) else { + return; + }; + let prototype_key = v8::String::new(scope, "prototype").unwrap(); + let Some(prototype) = constructor.get(scope, prototype_key.into()) else { + return; + }; + let Ok(prototype) = v8::Local::::try_from(prototype) else { + return; + }; + let value = v8::Boolean::new(scope, true).into(); + set_value(scope, prototype, "isStreamBase", value); +} + +#[op2] +pub fn op_node_internal_binding_pipe_wrap<'s>( + scope: &mut v8::PinScope<'s, '_>, + pipe_wrap: v8::Local<'s, v8::Value>, + pipe_connect_wrap: v8::Local<'s, v8::Value>, + create_pipe: v8::Local<'s, v8::Value>, +) -> v8::Local<'s, v8::Object> { + mark_stream_base(scope, pipe_wrap); + + let socket_type = v8::Object::new(scope); + set_number(scope, socket_type, "SOCKET", 0); + set_number(scope, socket_type, "SERVER", 1); + set_number(scope, socket_type, "IPC", 2); + + let constants = v8::Object::new(scope); + set_number(scope, constants, "SOCKET", 0); + set_number(scope, constants, "SERVER", 1); + set_number(scope, constants, "IPC", 2); + set_number(scope, constants, "UV_READABLE", 1); + set_number(scope, constants, "UV_WRITABLE", 2); + + let default = v8::Object::new(scope); + set_value(scope, default, "Pipe", pipe_wrap); + set_value(scope, default, "PipeConnectWrap", pipe_connect_wrap); + set_value(scope, default, "constants", constants.into()); + set_value(scope, default, "createPipe", create_pipe); + + let obj = v8::Object::new(scope); + set_value(scope, obj, "Pipe", pipe_wrap); + set_value(scope, obj, "createPipe", create_pipe); + set_value(scope, obj, "PipeConnectWrap", pipe_connect_wrap); + set_value(scope, obj, "socketType", socket_type.into()); + set_value(scope, obj, "constants", constants.into()); + set_value(scope, obj, "default", default.into()); + obj +} + // -- libuv callbacks (called from the event loop) -- /// Macro to set up a v8 scope from a uv stream's handle data and call a JS diff --git a/ext/node/polyfills/internal_binding/pipe_wrap.ts b/ext/node/polyfills/internal_binding/pipe_wrap.ts index 6eec6ec686a739..b56dd9ff6e4a97 100644 --- a/ext/node/polyfills/internal_binding/pipe_wrap.ts +++ b/ext/node/polyfills/internal_binding/pipe_wrap.ts @@ -1,94 +1,16 @@ // Copyright 2018-2026 the Deno authors. MIT license. -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -// This module ports: -// - https://github.com/nodejs/node/blob/master/src/pipe_wrap.cc -// - https://github.com/nodejs/node/blob/master/src/pipe_wrap.h -// -// PipeWrap is a Rust CppGC object that inherits from LibUvStreamWrap. -// Read/write/shutdown ops come from the LibUvStreamWrap base class. -// Pipe-specific ops (bind, listen, connect, accept, open, fchmod, -// setPendingInstances) are on PipeWrap itself. (function () { -const { core } = __bootstrap; -const { op_node_create_pipe, PipeWrap } = core.ops; -const { AsyncWrap, providerType } = core.loadExtScript( - "ext:deno_node/internal_binding/async_wrap.ts", -); - -// Mark PipeWrap as a StreamBase handle, matching Node's StreamBase::AddMethods. -PipeWrap.prototype.isStreamBase = true; - -/** The type of pipe socket. */ -enum socketType { - SOCKET, - SERVER, - IPC, -} - -enum constants { - SOCKET = socketType.SOCKET, - SERVER = socketType.SERVER, - IPC = socketType.IPC, - UV_READABLE = 1, - UV_WRITABLE = 2, -} - -class PipeConnectWrap extends AsyncWrap { - oncomplete!: ( - status: number, - handle: unknown, - req: PipeConnectWrap, - readable: boolean, - writeable: boolean, - ) => void; - address!: string; - - constructor() { - super(providerType.PIPECONNECTWRAP); - } -} - -// Re-export the Rust PipeWrap as Pipe. - -/** Create an anonymous pipe pair. Returns [readFd, writeFd]. */ -function createPipe(): [number, number] { - return op_node_create_pipe(); -} - -const _defaultExport = { - Pipe: PipeWrap, +const { + op_node_create_pipe, + op_node_internal_binding_pipe_wrap, PipeConnectWrap, - constants, - createPipe, -}; + PipeWrap, +} = __bootstrap.core.ops; -return { - Pipe: PipeWrap, - createPipe, +return op_node_internal_binding_pipe_wrap( + PipeWrap, PipeConnectWrap, - socketType, - constants, - default: _defaultExport, -}; + op_node_create_pipe, +); })(); From 9d25bb37a0a3379347f0c0a041603020d01de8b6 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 13:38:43 -0700 Subject: [PATCH 12/33] port http2 internal binding to rust --- ext/node/lib.rs | 8 +- ext/node/ops/http2/mod.rs | 168 +++++++++++++++++++ ext/node/polyfills/internal_binding/http2.ts | 78 +-------- 3 files changed, 179 insertions(+), 75 deletions(-) diff --git a/ext/node/lib.rs b/ext/node/lib.rs index b57b641918017a..9c1a3dbcb0fba7 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -356,11 +356,10 @@ deno_core::extension!(deno_node, ops::zlib::op_zlib_crc32_string, ops::handle_wrap::op_node_new_async_id, ops::http2::op_http2_callbacks, + ops::http2::op_node_internal_binding_http2, // Keep the HTTP/2 error-string op wired so `internal/test/binding` // can mirror Node's `internalBinding('http2').nghttp2ErrorString()` - // in node_compat tests; the JS side also exposes `respond` / - // `pushPromise` shims on `Http2Stream` so tests can monkey-patch the - // prototype to inject NGHTTP2 error codes. + // in node_compat tests. ops::http2::op_http2_error_string, ops::http2::op_http2_http_state, ops::os::op_node_os_get_priority, @@ -932,6 +931,9 @@ deno_core::extension!(deno_node, ext.external_references.to_mut().extend( ops::internal_binding_async_wrap::external_references(), ); + ext.external_references.to_mut().extend( + ops::http2::internal_binding_external_references(), + ); }, ); diff --git a/ext/node/ops/http2/mod.rs b/ext/node/ops/http2/mod.rs index 94d4223935cea5..c75a4c958a4967 100644 --- a/ext/node/ops/http2/mod.rs +++ b/ext/node/ops/http2/mod.rs @@ -4,8 +4,176 @@ mod session; mod stream; mod types; +use deno_core::op2; +use deno_core::v8; +use deno_core::v8::ExternalReference; +use deno_core::v8::MapFnTo; + pub use session::Http2Session; pub use session::op_http2_callbacks; pub use session::op_http2_error_string; pub use session::op_http2_http_state; pub use stream::Http2Stream; + +fn set_value( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: v8::Local, +) { + let key = v8::String::new(scope, name).unwrap(); + obj.set(scope, key.into(), value); +} + +fn set_function( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + function: v8::Local, +) { + set_value(scope, obj, name, function.into()); +} + +fn forward_to_receiver_method( + scope: &mut v8::PinScope, + args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, + name: &str, +) { + let this = args.this(); + let key = v8::String::new(scope, name).unwrap(); + let Some(method) = this.get(scope, key.into()) else { + return; + }; + let Ok(method) = v8::Local::::try_from(method) else { + return; + }; + let call_args = (0..args.length()) + .map(|index| args.get(index)) + .collect::>(); + if let Some(result) = method.call(scope, this.into(), &call_args) { + rv.set(result); + } +} + +fn http2_stream_constructor_callback( + _scope: &mut v8::PinScope, + _args: v8::FunctionCallbackArguments, + _rv: v8::ReturnValue, +) { +} + +fn http2_session_constructor_callback( + _scope: &mut v8::PinScope, + _args: v8::FunctionCallbackArguments, + _rv: v8::ReturnValue, +) { +} + +fn http2_stream_respond_callback( + scope: &mut v8::PinScope, + args: v8::FunctionCallbackArguments, + rv: v8::ReturnValue, +) { + forward_to_receiver_method(scope, args, rv, "respond"); +} + +fn http2_stream_push_promise_callback( + scope: &mut v8::PinScope, + args: v8::FunctionCallbackArguments, + rv: v8::ReturnValue, +) { + forward_to_receiver_method(scope, args, rv, "pushPromise"); +} + +fn http2_session_request_callback( + scope: &mut v8::PinScope, + args: v8::FunctionCallbackArguments, + rv: v8::ReturnValue, +) { + forward_to_receiver_method(scope, args, rv, "request"); +} + +fn create_constructor<'s>( + scope: &mut v8::PinScope<'s, '_>, + name: &str, + constructor: impl MapFnTo, +) -> v8::Local<'s, v8::Function> { + let template = v8::FunctionTemplate::new(scope, constructor); + let class_name = v8::String::new(scope, name).unwrap(); + template.set_class_name(class_name); + template.get_function(scope).unwrap() +} + +pub(crate) fn internal_binding_external_references() -> [ExternalReference; 5] { + [ + ExternalReference { + function: http2_stream_constructor_callback.map_fn_to(), + }, + ExternalReference { + function: http2_session_constructor_callback.map_fn_to(), + }, + ExternalReference { + function: http2_stream_respond_callback.map_fn_to(), + }, + ExternalReference { + function: http2_stream_push_promise_callback.map_fn_to(), + }, + ExternalReference { + function: http2_session_request_callback.map_fn_to(), + }, + ] +} + +#[op2] +pub fn op_node_internal_binding_http2<'s>( + scope: &mut v8::PinScope<'s, '_>, + constants: v8::Local<'s, v8::Value>, + nghttp2_error_string: v8::Local<'s, v8::Value>, +) -> v8::Local<'s, v8::Object> { + let http2_stream = + create_constructor(scope, "Http2Stream", http2_stream_constructor_callback); + let prototype_key = v8::String::new(scope, "prototype").unwrap(); + let prototype = http2_stream + .get(scope, prototype_key.into()) + .and_then(|value| v8::Local::::try_from(value).ok()) + .unwrap(); + let respond = v8::FunctionTemplate::new(scope, http2_stream_respond_callback) + .get_function(scope) + .unwrap(); + set_function(scope, prototype, "respond", respond); + let push_promise = + v8::FunctionTemplate::new(scope, http2_stream_push_promise_callback) + .get_function(scope) + .unwrap(); + set_function(scope, prototype, "pushPromise", push_promise); + + let http2_session = create_constructor( + scope, + "Http2Session", + http2_session_constructor_callback, + ); + let prototype = http2_session + .get(scope, prototype_key.into()) + .and_then(|value| v8::Local::::try_from(value).ok()) + .unwrap(); + let request = + v8::FunctionTemplate::new(scope, http2_session_request_callback) + .get_function(scope) + .unwrap(); + set_function(scope, prototype, "request", request); + + let default = v8::Object::new(scope); + set_value(scope, default, "constants", constants); + set_value(scope, default, "Http2Session", http2_session.into()); + set_value(scope, default, "Http2Stream", http2_stream.into()); + set_value(scope, default, "nghttp2ErrorString", nghttp2_error_string); + + let obj = v8::Object::new(scope); + set_value(scope, obj, "constants", constants); + set_value(scope, obj, "Http2Session", http2_session.into()); + set_value(scope, obj, "Http2Stream", http2_stream.into()); + set_value(scope, obj, "nghttp2ErrorString", nghttp2_error_string); + set_value(scope, obj, "default", default.into()); + obj +} diff --git a/ext/node/polyfills/internal_binding/http2.ts b/ext/node/polyfills/internal_binding/http2.ts index 7d40874c7e4036..c85d8569689358 100644 --- a/ext/node/polyfills/internal_binding/http2.ts +++ b/ext/node/polyfills/internal_binding/http2.ts @@ -1,80 +1,14 @@ // Copyright 2018-2026 the Deno authors. MIT license. + (function () { const { core } = __bootstrap; -const { op_http2_error_string } = core.ops; +const { + op_http2_error_string, + op_node_internal_binding_http2, +} = core.ops; const constants = core.loadExtScript( "ext:deno_node/internal/http2/constants.ts", ); -class Http2Stream { - respond( - this: { - respond(headers: string, count: number, options: number): number; - }, - headers: string, - count: number, - options: number, - ): number { - // Tests replace this prototype method; otherwise `this` is the native - // handle, so dispatch falls through to the handle's own `respond` op. - return this.respond(headers, count, options); - } - - pushPromise( - this: { - pushPromise(headers: string, count: number, options: number): number; - }, - headers: string, - count: number, - options: number, - ): number { - // Tests replace this prototype method; otherwise `this` is the native - // handle, so dispatch falls through to the handle's own `pushPromise` op. - return this.pushPromise(headers, count, options); - } -} - -class Http2Session { - request( - this: { - request( - headers: string, - count: number, - options: number, - parent: number, - weight: number, - exclusive: boolean, - ): unknown; - }, - headers: string, - count: number, - options: number, - parent: number, - weight: number, - exclusive: boolean, - ): unknown { - // Tests replace this prototype method; otherwise `this` is the native - // handle, so dispatch falls through to the handle's own `request` op. - return this.request(headers, count, options, parent, weight, exclusive); - } -} - -function nghttp2ErrorString(integerCode: number) { - return op_http2_error_string(integerCode); -} - -const _defaultExport = { - constants, - Http2Session, - Http2Stream, - nghttp2ErrorString, -}; - -return { - constants, - Http2Session, - Http2Stream, - nghttp2ErrorString, - default: _defaultExport, -}; +return op_node_internal_binding_http2(constants, op_http2_error_string); })(); From ff4f4e4ed4966621e8b558bf06d98588d08b1406 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 13:51:51 -0700 Subject: [PATCH 13/33] port tls wrap internal binding to rust --- ext/node/lib.rs | 4 + ext/node/ops/tls_wrap.rs | 724 ++++++++++++++++++ .../polyfills/internal_binding/tls_wrap.ts | 179 +---- 3 files changed, 732 insertions(+), 175 deletions(-) diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 9c1a3dbcb0fba7..d6b6b4f552dd69 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -459,6 +459,7 @@ deno_core::extension!(deno_node, ops::tcp_wrap::op_node_internal_binding_tcp_wrap, ops::stream_wrap::op_node_internal_binding_stream_wrap, ops::stream_wrap::op_stream_base_register_state, + ops::tls_wrap::op_node_internal_binding_tls_wrap, ops::tty_wrap::op_tty_check_fd_permission, ], objects = [ @@ -934,6 +935,9 @@ deno_core::extension!(deno_node, ext.external_references.to_mut().extend( ops::http2::internal_binding_external_references(), ); + ext.external_references.to_mut().extend( + ops::tls_wrap::internal_binding_external_references(), + ); }, ); diff --git a/ext/node/ops/tls_wrap.rs b/ext/node/ops/tls_wrap.rs index 8a3e37f48f7f8d..31661f5ef77886 100644 --- a/ext/node/ops/tls_wrap.rs +++ b/ext/node/ops/tls_wrap.rs @@ -45,6 +45,8 @@ use deno_core::uv_compat::uv_buf_t; use deno_core::uv_compat::uv_stream_t; use deno_core::uv_compat::uv_write_t; use deno_core::v8; +use deno_core::v8::ExternalReference; +use deno_core::v8::MapFnTo; use deno_core::v8_static_strings; use deno_node_crypto::x509::Certificate; use deno_node_crypto::x509::CertificateObject; @@ -1943,6 +1945,728 @@ impl TLSWrap { } } +fn string<'s>( + scope: &mut v8::PinScope<'s, '_>, + value: &str, +) -> v8::Local<'s, v8::String> { + v8::String::new(scope, value).unwrap() +} + +fn set_value( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: v8::Local, +) { + let key = string(scope, name); + obj.set(scope, key.into(), value); +} + +fn get_value<'s>( + scope: &mut v8::PinScope<'s, '_>, + obj: v8::Local<'s, v8::Object>, + name: &str, +) -> Option> { + let key = string(scope, name); + obj.get(scope, key.into()) +} + +fn get_function<'s>( + scope: &mut v8::PinScope<'s, '_>, + obj: v8::Local<'s, v8::Object>, + name: &str, +) -> Option> { + get_value(scope, obj, name) + .and_then(|value| v8::Local::::try_from(value).ok()) +} + +fn call_method<'s>( + scope: &mut v8::PinScope<'s, '_>, + obj: v8::Local<'s, v8::Object>, + name: &str, + args: &[v8::Local], +) -> Option> { + let function = get_function(scope, obj, name)?; + function.call(scope, obj.into(), args) +} + +fn callback_data_object<'s>( + args: &v8::FunctionCallbackArguments<'s>, +) -> Option> { + v8::Local::::try_from(args.data()).ok() +} + +fn callback_data_value<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: &v8::FunctionCallbackArguments<'s>, + name: &str, +) -> Option> { + let data = callback_data_object(args)?; + get_value(scope, data, name) +} + +fn callback_data_function<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: &v8::FunctionCallbackArguments<'s>, + name: &str, +) -> Option> { + callback_data_value(scope, args, name) + .and_then(|value| v8::Local::::try_from(value).ok()) +} + +fn callback_data_tls_wrap<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: &v8::FunctionCallbackArguments<'s>, +) -> Option> { + callback_data_value(scope, args, "res") + .and_then(|value| v8::Local::::try_from(value).ok()) +} + +fn throw_error(scope: &mut v8::PinScope, message: &str) { + let message = string(scope, message); + let exception = v8::Exception::error(scope, message); + scope.throw_exception(exception); +} + +fn create_error<'s>( + scope: &mut v8::PinScope<'s, '_>, + message: &str, +) -> v8::Local<'s, v8::Object> { + let message = string(scope, message); + let error = v8::Exception::error(scope, message); + v8::Local::::try_from(error).unwrap() +} + +fn data_object<'s>( + scope: &mut v8::PinScope<'s, '_>, + values: &[(&str, v8::Local<'s, v8::Value>)], +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + for (key, value) in values { + set_value(scope, obj, key, *value); + } + obj +} + +fn function_with_data<'s>( + scope: &mut v8::PinScope<'s, '_>, + callback: impl MapFnTo, + data: v8::Local<'s, v8::Object>, +) -> v8::Local<'s, v8::Function> { + v8::Function::builder(callback) + .data(data.into()) + .build(scope) + .unwrap() +} + +fn call_tls_method<'s>( + scope: &mut v8::PinScope<'s, '_>, + tls_wrap: v8::Local<'s, v8::Object>, + name: &str, + args: &[v8::Local], +) -> Option> { + call_method(scope, tls_wrap, name, args) +} + +fn attach_native_handle<'s>( + scope: &mut v8::PinScope<'s, '_>, + tls_wrap: v8::Local<'s, v8::Object>, + native_handle: v8::Local<'s, v8::Object>, + pipe_wrap: v8::Local<'s, v8::Object>, + stream_base_state: v8::Local<'s, v8::Value>, +) -> bool { + let method = if native_handle + .cast::() + .instance_of(scope, pipe_wrap) + .unwrap_or(false) + { + "attachPipe" + } else { + "attach" + }; + let Some(result) = + call_tls_method(scope, tls_wrap, method, &[native_handle.into()]) + else { + return false; + }; + let attach_result = result.int32_value(scope).unwrap_or(-1); + if attach_result != 0 { + throw_error(scope, &format!("TLS wrap attach failed: {attach_result}")); + return false; + } + + install_native_onread(scope, tls_wrap, native_handle, stream_base_state); + set_value(scope, tls_wrap, "_nativeTcpHandle", native_handle.into()); + true +} + +fn install_native_onread<'s>( + scope: &mut v8::PinScope<'s, '_>, + tls_wrap: v8::Local<'s, v8::Object>, + native_handle: v8::Local<'s, v8::Object>, + stream_base_state: v8::Local<'s, v8::Value>, +) { + let data = data_object( + scope, + &[ + ("res", tls_wrap.into()), + ("streamBaseState", stream_base_state), + ], + ); + let onread = function_with_data(scope, native_onread_callback, data); + set_value(scope, native_handle, "onread", onread.into()); +} + +fn js_stream_owner<'s>( + scope: &mut v8::PinScope<'s, '_>, + native_handle: v8::Local<'s, v8::Object>, +) -> Option> { + let owner_symbol_name = string(scope, "kJSStreamOwner"); + let owner_symbol = v8::Symbol::for_key(scope, owner_symbol_name); + native_handle + .get(scope, owner_symbol.into()) + .and_then(|value| v8::Local::::try_from(value).ok()) +} + +fn is_js_stream_handle( + scope: &mut v8::PinScope, + native_handle: v8::Local, +) -> bool { + let handle_symbol_name = string(scope, "kJSStreamHandle"); + let handle_symbol = v8::Symbol::for_key(scope, handle_symbol_name); + native_handle + .get(scope, handle_symbol.into()) + .map(|value| value.boolean_value(scope)) + .unwrap_or(false) +} + +fn value_byte_length<'s>( + scope: &mut v8::PinScope<'s, '_>, + value: v8::Local<'s, v8::Value>, +) -> usize { + if let Ok(view) = v8::Local::::try_from(value) { + return view.byte_length(); + } + if let Ok(buffer) = v8::Local::::try_from(value) { + return buffer.byte_length(); + } + let Ok(obj) = v8::Local::::try_from(value) else { + return 0; + }; + get_value(scope, obj, "byteLength") + .and_then(|value| value.uint32_value(scope)) + .unwrap_or(0) as usize +} + +fn empty_uint8_array<'s>( + scope: &mut v8::PinScope<'s, '_>, +) -> v8::Local<'s, v8::Uint8Array> { + let buffer = v8::ArrayBuffer::new(scope, 0); + v8::Uint8Array::new(scope, buffer, 0, 0).unwrap() +} + +fn uint8_array_from_value<'s>( + scope: &mut v8::PinScope<'s, '_>, + value: v8::Local<'s, v8::Value>, +) -> Option> { + let global = scope.get_current_context().global(scope); + let ctor = get_function(scope, global, "Uint8Array")?; + ctor.new_instance(scope, &[value]) +} + +fn call_pump<'s>( + scope: &mut v8::PinScope<'s, '_>, + data: v8::Local<'s, v8::Object>, +) { + let Some(pump) = get_function(scope, data, "pump") else { + return; + }; + let undefined = v8::undefined(scope).into(); + pump.call(scope, undefined, &[]); +} + +fn setup_js_stream<'s>( + scope: &mut v8::PinScope<'s, '_>, + tls_wrap: v8::Local<'s, v8::Object>, + native_handle: v8::Local<'s, v8::Object>, +) -> bool { + let Some(result) = call_tls_method(scope, tls_wrap, "attachJsStream", &[]) + else { + return false; + }; + let attach_result = result.int32_value(scope).unwrap_or(-1); + if attach_result != 0 { + throw_error( + scope, + &format!("TLS wrap attach (JS stream) failed: {attach_result}"), + ); + return false; + } + + let Some(owner) = js_stream_owner(scope, native_handle) else { + return true; + }; + + let data = data_object( + scope, + &[ + ("res", tls_wrap.into()), + ("nativeHandle", native_handle.into()), + ("jsStreamOwner", owner.into()), + ("flushPending", v8::Boolean::new(scope, false).into()), + ("writeInFlight", v8::Boolean::new(scope, false).into()), + ], + ); + let pump = function_with_data(scope, js_stream_pump_callback, data); + set_value(scope, data, "pump", pump.into()); + let flush = function_with_data(scope, js_stream_flush_callback, data); + set_value(scope, data, "flush", flush.into()); + set_value(scope, tls_wrap, "_flushEncOut", flush.into()); + + let read_buffer = + function_with_data(scope, js_stream_read_buffer_callback, data); + set_value(scope, native_handle, "readBuffer", read_buffer.into()); + let emit_eof = function_with_data(scope, js_stream_emit_eof_callback, data); + set_value(scope, native_handle, "emitEOF", emit_eof.into()); + true +} + +fn native_onread_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, +) { + let Some(tls_wrap) = callback_data_tls_wrap(scope, &args) else { + return; + }; + let Some(state_value) = callback_data_value(scope, &args, "streamBaseState") + else { + return; + }; + let Ok(state) = v8::Local::::try_from(state_value) else { + return; + }; + let Some(buffer) = state.buffer(scope) else { + return; + }; + let Some(data) = buffer.data() else { + return; + }; + let values = + unsafe { std::slice::from_raw_parts(data.as_ptr() as *const i32, 1) }; + let nread = values[0]; + let native_handle = args.this(); + if nread > 0 && args.length() > 0 { + let buf = args.get(0); + if buf.is_array_buffer() { + let Ok(buffer) = v8::Local::::try_from(buf) else { + return; + }; + let len = nread as usize; + let Some(view) = v8::Uint8Array::new(scope, buffer, 0, len) else { + return; + }; + call_tls_method(scope, tls_wrap, "receive", &[view.into()]); + } else if let Ok(view) = v8::Local::::try_from(buf) { + let Some(buffer) = view.buffer(scope) else { + return; + }; + let len = nread as usize; + let Some(view) = + v8::Uint8Array::new(scope, buffer, view.byte_offset(), len) + else { + return; + }; + call_tls_method(scope, tls_wrap, "receive", &[view.into()]); + } + } else if nread < 0 { + call_method(scope, native_handle, "readStop", &[]); + call_method(scope, native_handle, "unref", &[]); + call_tls_method(scope, tls_wrap, "emitEof", &[]); + } +} + +fn attach_native_handle_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, +) { + let Some(tls_wrap) = callback_data_tls_wrap(scope, &args) else { + return; + }; + let Some(pipe_wrap) = callback_data_value(scope, &args, "PipeWrap") + .and_then(|value| v8::Local::::try_from(value).ok()) + else { + return; + }; + let Some(stream_base_state) = + callback_data_value(scope, &args, "streamBaseState") + else { + return; + }; + let native_handle = args.get(0); + let Ok(native_handle) = v8::Local::::try_from(native_handle) + else { + return; + }; + attach_native_handle( + scope, + tls_wrap, + native_handle, + pipe_wrap, + stream_base_state, + ); +} + +fn install_native_onread_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, +) { + let Some(tls_wrap) = callback_data_tls_wrap(scope, &args) else { + return; + }; + let native_handle = args.get(0); + let Ok(native_handle) = v8::Local::::try_from(native_handle) + else { + return; + }; + let Some(stream_base_state) = + callback_data_value(scope, &args, "streamBaseState") + else { + return; + }; + install_native_onread(scope, tls_wrap, native_handle, stream_base_state); +} + +fn js_stream_pump_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, +) { + let Some(data) = callback_data_object(&args) else { + return; + }; + let write_in_flight = get_value(scope, data, "writeInFlight") + .map(|value| value.boolean_value(scope)) + .unwrap_or(false); + if write_in_flight { + return; + } + let Some(owner) = get_value(scope, data, "jsStreamOwner") + .and_then(|value| v8::Local::::try_from(value).ok()) + else { + return; + }; + let Some(stream) = get_value(scope, owner, "stream") + .and_then(|value| v8::Local::::try_from(value).ok()) + else { + return; + }; + let Some(tls_wrap) = get_value(scope, data, "res") + .and_then(|value| v8::Local::::try_from(value).ok()) + else { + return; + }; + + let mut enc_data = call_tls_method(scope, tls_wrap, "drainEncOut", &[]); + let mut data_len = enc_data + .map(|value| value_byte_length(scope, value)) + .unwrap_or(0); + if data_len == 0 { + let empty = empty_uint8_array(scope); + call_tls_method(scope, tls_wrap, "readBuffer", &[empty.into()]); + enc_data = call_tls_method(scope, tls_wrap, "drainEncOut", &[]); + data_len = enc_data + .map(|value| value_byte_length(scope, value)) + .unwrap_or(0); + if data_len == 0 { + return; + } + } + + let Some(enc_data) = enc_data else { + return; + }; + let Some(data_for_write) = uint8_array_from_value(scope, enc_data) else { + return; + }; + set_value( + scope, + data, + "writeInFlight", + v8::Boolean::new(scope, true).into(), + ); + let write_done = + function_with_data(scope, js_stream_write_done_callback, data); + call_method( + scope, + stream, + "write", + &[data_for_write.into(), write_done.into()], + ); +} + +fn js_stream_flush_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, +) { + let Some(data) = callback_data_object(&args) else { + return; + }; + let flush_pending = get_value(scope, data, "flushPending") + .map(|value| value.boolean_value(scope)) + .unwrap_or(false); + if flush_pending { + return; + } + set_value( + scope, + data, + "flushPending", + v8::Boolean::new(scope, true).into(), + ); + let global = scope.get_current_context().global(scope); + let Some(promise_ctor) = get_value(scope, global, "Promise") + .and_then(|value| v8::Local::::try_from(value).ok()) + else { + return; + }; + let Some(resolve) = get_function(scope, promise_ctor, "resolve") else { + return; + }; + let Some(promise) = resolve + .call(scope, promise_ctor.into(), &[]) + .and_then(|value| v8::Local::::try_from(value).ok()) + else { + return; + }; + let callback = function_with_data(scope, js_stream_flush_then_callback, data); + call_method(scope, promise, "then", &[callback.into()]); +} + +fn js_stream_flush_then_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, +) { + let Some(data) = callback_data_object(&args) else { + return; + }; + set_value( + scope, + data, + "flushPending", + v8::Boolean::new(scope, false).into(), + ); + call_pump(scope, data); +} + +fn js_stream_write_done_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, +) { + let Some(data) = callback_data_object(&args) else { + return; + }; + set_value( + scope, + data, + "writeInFlight", + v8::Boolean::new(scope, false).into(), + ); + call_pump(scope, data); +} + +fn js_stream_read_buffer_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, +) { + let Some(tls_wrap) = callback_data_tls_wrap(scope, &args) else { + return; + }; + let Some(flush) = callback_data_function(scope, &args, "flush") else { + return; + }; + let chunk = args.get(0); + call_tls_method(scope, tls_wrap, "readBuffer", &[chunk]); + let undefined = v8::undefined(scope).into(); + flush.call(scope, undefined, &[]); +} + +fn js_stream_emit_eof_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, +) { + let Some(tls_wrap) = callback_data_tls_wrap(scope, &args) else { + return; + }; + let Some(flush) = callback_data_function(scope, &args, "flush") else { + return; + }; + call_tls_method(scope, tls_wrap, "emitEof", &[]); + let undefined = v8::undefined(scope).into(); + flush.call(scope, undefined, &[]); +} + +fn tls_wrap_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'s>, +) { + let Some(data) = callback_data_object(&args) else { + return; + }; + let Some(tls_wrap_ctor) = get_value(scope, data, "TLSWrap") + .and_then(|value| v8::Local::::try_from(value).ok()) + else { + return; + }; + let Some(pipe_wrap) = get_value(scope, data, "PipeWrap") + .and_then(|value| v8::Local::::try_from(value).ok()) + else { + return; + }; + let Some(stream_base_state) = get_value(scope, data, "streamBaseState") + else { + return; + }; + let handle = args.get(0); + let Ok(native_handle) = v8::Local::::try_from(handle) else { + return; + }; + let context = args.get(1); + let is_server = args.get(2).boolean_value(scope); + let kind = if is_server { 1 } else { 0 }; + let wrap_args = [ + v8::Integer::new(scope, kind).into(), + v8::Integer::new(scope, 0).into(), + ]; + let Some(tls_wrap) = tls_wrap_ctor.new_instance(scope, &wrap_args) else { + return; + }; + + let init_result = if is_server { + call_tls_method(scope, tls_wrap, "initServerTls", &[context]) + } else { + let servername = if args.length() > 3 { + args.get(3) + } else { + string(scope, "").into() + }; + call_tls_method(scope, tls_wrap, "initClientTls", &[servername, context]) + }; + let init_result = init_result + .and_then(|value| value.int32_value(scope)) + .unwrap_or(-1); + if init_result != 0 { + let error = create_error(scope, "unsupported protocol"); + let code = string(scope, "ERR_SSL_UNSUPPORTED_PROTOCOL"); + set_value(scope, error, "code", code.into()); + set_value(scope, tls_wrap, "_initError", error.into()); + rv.set(tls_wrap.into()); + return; + } + + let data = data_object( + scope, + &[ + ("res", tls_wrap.into()), + ("PipeWrap", pipe_wrap.into()), + ("streamBaseState", stream_base_state), + ], + ); + let attach = function_with_data(scope, attach_native_handle_callback, data); + set_value(scope, tls_wrap, "_attachNativeHandle", attach.into()); + let install = function_with_data(scope, install_native_onread_callback, data); + set_value(scope, tls_wrap, "_installNativeOnread", install.into()); + + if is_js_stream_handle(scope, native_handle) { + if !setup_js_stream(scope, tls_wrap, native_handle) { + return; + } + } else if !attach_native_handle( + scope, + tls_wrap, + native_handle, + pipe_wrap, + stream_base_state, + ) { + return; + } + + call_tls_method(scope, tls_wrap, "setHandle", &[tls_wrap.into()]); + rv.set(tls_wrap.into()); +} + +pub(crate) fn internal_binding_external_references() -> [ExternalReference; 10] +{ + [ + ExternalReference { + function: tls_wrap_callback.map_fn_to(), + }, + ExternalReference { + function: native_onread_callback.map_fn_to(), + }, + ExternalReference { + function: attach_native_handle_callback.map_fn_to(), + }, + ExternalReference { + function: install_native_onread_callback.map_fn_to(), + }, + ExternalReference { + function: js_stream_pump_callback.map_fn_to(), + }, + ExternalReference { + function: js_stream_flush_callback.map_fn_to(), + }, + ExternalReference { + function: js_stream_flush_then_callback.map_fn_to(), + }, + ExternalReference { + function: js_stream_write_done_callback.map_fn_to(), + }, + ExternalReference { + function: js_stream_read_buffer_callback.map_fn_to(), + }, + ExternalReference { + function: js_stream_emit_eof_callback.map_fn_to(), + }, + ] +} + +#[op2] +pub fn op_node_internal_binding_tls_wrap<'s>( + scope: &mut v8::PinScope<'s, '_>, + tls_wrap: v8::Local<'s, v8::Value>, + pipe_wrap: v8::Local<'s, v8::Value>, + stream_base_state: v8::Local<'s, v8::Value>, +) -> v8::Local<'s, v8::Object> { + let data = data_object( + scope, + &[ + ("TLSWrap", tls_wrap), + ("PipeWrap", pipe_wrap), + ("streamBaseState", stream_base_state), + ], + ); + let wrap = function_with_data(scope, tls_wrap_callback, data); + + let default = v8::Object::new(scope); + set_value(scope, default, "TLSWrap", tls_wrap); + set_value(scope, default, "wrap", wrap.into()); + + let obj = v8::Object::new(scope); + set_value(scope, obj, "TLSWrap", tls_wrap); + set_value(scope, obj, "wrap", wrap.into()); + set_value(scope, obj, "default", default.into()); + obj +} + #[op2(inherit = LibUvStreamWrap)] impl TLSWrap { /// Create a new TLSWrap around a SecureContext. diff --git a/ext/node/polyfills/internal_binding/tls_wrap.ts b/ext/node/polyfills/internal_binding/tls_wrap.ts index aca116792f9908..96836c6605b2db 100644 --- a/ext/node/polyfills/internal_binding/tls_wrap.ts +++ b/ext/node/polyfills/internal_binding/tls_wrap.ts @@ -1,182 +1,11 @@ // Copyright 2018-2026 the Deno authors. MIT license. -// deno-lint-ignore-file no-explicit-any prefer-primordials + (function () { const { core } = __bootstrap; -const { PipeWrap, TLSWrap } = core.ops; -const { kReadBytesOrError, streamBaseState } = core.loadExtScript( +const { PipeWrap, TLSWrap, op_node_internal_binding_tls_wrap } = core.ops; +const { streamBaseState } = core.loadExtScript( "ext:deno_node/internal_binding/stream_wrap.ts", ); -// Use Symbol.for to access symbols from js_stream_socket.js -// without importing it (avoids circular dependency). -const kJSStreamHandle = Symbol.for("kJSStreamHandle"); -const kOwner = Symbol.for("kJSStreamOwner"); - -function installNativeOnread(res: TLSWrap, nativeHandle: any) { - nativeHandle.onread = function ( - buf: ArrayBuffer | Uint8Array | undefined, - ) { - const nread = streamBaseState[kReadBytesOrError]; - if (nread > 0 && buf) { - // LibUvStreamWrap passes an ArrayBuffer; convert to Uint8Array for receive() - const data = buf instanceof ArrayBuffer - ? new Uint8Array(buf, 0, nread) - : buf.subarray(0, nread); - res.receive(data); - } else if (nread < 0) { - // EOF or error - stop native TCP reads and unref the handle. - // Without this, the libuv handle keeps a ref on the event loop - // and prevents process exit after the TLS connection ends. - nativeHandle.readStop(); - nativeHandle.unref(); - res.emitEof(); - } - }; -} - -function attachNativeHandle(res: TLSWrap, nativeHandle: any) { - const attachResult = nativeHandle instanceof PipeWrap - ? res.attachPipe(nativeHandle) - : res.attach(nativeHandle); - if (attachResult !== 0) { - throw new Error(`TLS wrap attach failed: ${attachResult}`); - } - - installNativeOnread(res, nativeHandle); - res._nativeTcpHandle = nativeHandle; -} - -/** - * Create a TLSWrap that intercepts an underlying stream handle. - * Mirrors Node's `internalBinding('tls_wrap').wrap(handle, context, isServer)`. - * - * @param handle - The underlying stream handle (TCP CppGC object or JSStreamSocket handle) - * @param context - SecureContext object { ca, cert, key, rejectUnauthorized } - * @param isServer - Whether this is a server-side TLS connection - * @param servername - SNI hostname for client connections - */ -function wrap( - handle: any, - context: any, - isServer: boolean, - servername?: string, -): TLSWrap { - const kind = isServer ? 1 : 0; - // TODO(@nathanwhit): use a proper async_id from async_hooks - const asyncId = 0; - const res = new TLSWrap(kind, asyncId); - - // initClientTls/initServerTls take the SecureContext JS object directly - // and build the rustls config from its { ca, cert, key } properties. - // The actual TLS connection is deferred until start() so that - // setALPNProtocols can modify the config first. - const initResult = isServer - ? res.initServerTls(context) - : res.initClientTls(servername || "", context); - if (initResult !== 0) { - const err = new Error("unsupported protocol"); - // rustls cannot negotiate TLSv1.0/TLSv1.1, so surface the closest - // OpenSSL-style error instead of hanging the socket. - (err as Error & { code?: string }).code = "ERR_SSL_UNSUPPORTED_PROTOCOL"; - // Store the init error on the handle so the caller can detect and - // report it instead of throwing synchronously. A throw from here - // surfaces as an uncaught exception when the TLSSocket is created - // inside a native libuv callback (e.g. server_connection_cb) because - // there is no native TryCatch scope on the call stack. - res._initError = err; - return res; - } - - const nativeHandle = handle; - res._attachNativeHandle = (nativeHandle: any) => - attachNativeHandle(res, nativeHandle); - res._installNativeOnread = (nativeHandle: any) => - installNativeOnread(res, nativeHandle); - - if (nativeHandle[kJSStreamHandle]) { - // JS-backed stream (e.g. JSStreamSocket wrapping a Duplex). - // Use attachJsStream instead of attach -I/O goes through JS callbacks. - const attachResult = res.attachJsStream(); - if (attachResult !== 0) { - throw new Error(`TLS wrap attach (JS stream) failed: ${attachResult}`); - } - - // Pull-based encrypted output: instead of Rust calling a JS - // callback (which causes reentrancy issues), Rust buffers encrypted - // data and JS drains it after each operation that may produce output. - // - // The pump mirrors Node's TLSWrap::EncOut + OnStreamAfterWrite loop - // (src/crypto/crypto_tls.cc): hand encrypted bytes to the underlying - // stream, wait for that write to complete, then drive cycle() (the - // ClearIn + EncOut analog) to push any remaining cleartext, write - // more encrypted bytes if produced, and finally fire InvokeQueued for - // the cleartext WriteWrap's oncomplete. For JS-backed streams there - // is no enc_write_cb (libuv write completion) so cycle() is only - // driven here; without it the cleartext write callback never fires - // and writes deadlock (issue #33907). Gating cycle() on the - // underlying Duplex's _write callback also propagates backpressure, - // matching Node's behavior. - const jsStreamOwner = nativeHandle[kOwner]; - let flushPending = false; - let writeInFlight = false; - const pump = () => { - if (writeInFlight || !jsStreamOwner?.stream) return; - let data = res.drainEncOut(); - if (!data || data.byteLength === 0) { - // No buffered encrypted output. Drive cycle() to fire - // InvokeQueued for any queued cleartext write and to produce - // more encrypted output if clear_in() was rate-limited. - res.readBuffer(new Uint8Array(0)); - data = res.drainEncOut(); - if (!data || data.byteLength === 0) return; - } - writeInFlight = true; - jsStreamOwner.stream.write(new Uint8Array(data), () => { - writeInFlight = false; - pump(); - }); - }; - const flushEncOut = () => { - if (flushPending) return; - flushPending = true; - // Defer to a microtask so synchronous feedback loops through - // DuplexPair don't reenter cycle() while it's still running. - Promise.resolve().then(() => { - flushPending = false; - pump(); - }); - }; - - // Wire up readBuffer/emitEOF: JSStreamSocket calls these when the - // underlying Duplex stream produces data or ends. - // After each call, schedule a drain of encrypted output. - nativeHandle.readBuffer = (chunk: Uint8Array) => { - res.readBuffer(chunk); - flushEncOut(); - }; - nativeHandle.emitEOF = () => { - res.emitEof(); - flushEncOut(); - }; - - // Store flush function on the TLSWrap so _start() can drain - // after initiating the TLS handshake. - res._flushEncOut = flushEncOut; - } else { - // Native stream (TCP or Pipe handle). - attachNativeHandle(res, nativeHandle); - } - - // Store the JS handle reference so Rust can call JS callbacks (onhandshakedone, etc.) - res.setHandle(res); - - return res; -} - -const _defaultExport = { TLSWrap, wrap }; -return { - TLSWrap, - wrap, - default: _defaultExport, -}; +return op_node_internal_binding_tls_wrap(TLSWrap, PipeWrap, streamBaseState); })(); From 2d34c92f23ba218b986063d43b4f2fd4c0146649 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 14:00:35 -0700 Subject: [PATCH 14/33] port http parser internal binding to rust --- ext/node/lib.rs | 4 + ext/node/ops/llhttp/binding.rs | 790 ++++++++++++++++++ .../polyfills/internal_binding/http_parser.ts | 316 +------ 3 files changed, 799 insertions(+), 311 deletions(-) diff --git a/ext/node/lib.rs b/ext/node/lib.rs index d6b6b4f552dd69..5e5cabecc67a1c 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -244,6 +244,7 @@ deno_core::extension!(deno_node, ops::fs::op_node_fs_write_deferred, ops::fs::op_node_fs_seek_sync, ops::fs::op_node_file_write_buffer, + ops::llhttp::binding::op_node_internal_binding_http_parser, ops::fs::op_node_fs_seek, ops::fs::op_node_fs_fstat_sync, ops::fs::op_node_fs_fstat, @@ -938,6 +939,9 @@ deno_core::extension!(deno_node, ext.external_references.to_mut().extend( ops::tls_wrap::internal_binding_external_references(), ); + ext.external_references.to_mut().extend( + ops::llhttp::binding::internal_binding_external_references(), + ); }, ); diff --git a/ext/node/ops/llhttp/binding.rs b/ext/node/ops/llhttp/binding.rs index c796244da57f89..c35cbd08742b89 100644 --- a/ext/node/ops/llhttp/binding.rs +++ b/ext/node/ops/llhttp/binding.rs @@ -31,6 +31,8 @@ use deno_core::op2; use deno_core::uv_compat::uv_buf_t; use deno_core::uv_compat::uv_stream_t; use deno_core::v8; +use deno_core::v8::ExternalReference; +use deno_core::v8::MapFnTo; use super::sys; use crate::ops::stream_wrap::LibUvStreamWrap; @@ -167,6 +169,794 @@ impl HTTPParser { } } +const METHODS: &[&str] = &[ + "DELETE", + "GET", + "HEAD", + "POST", + "PUT", + "CONNECT", + "OPTIONS", + "TRACE", + "COPY", + "LOCK", + "MKCOL", + "MOVE", + "PROPFIND", + "PROPPATCH", + "SEARCH", + "UNLOCK", + "BIND", + "REBIND", + "UNBIND", + "ACL", + "REPORT", + "MKACTIVITY", + "CHECKOUT", + "MERGE", + "M-SEARCH", + "NOTIFY", + "SUBSCRIBE", + "UNSUBSCRIBE", + "PATCH", + "PURGE", + "MKCALENDAR", + "LINK", + "UNLINK", + "SOURCE", + "QUERY", +]; + +const ALL_METHODS: &[&str] = &[ + "DELETE", + "GET", + "HEAD", + "POST", + "PUT", + "CONNECT", + "OPTIONS", + "TRACE", + "COPY", + "LOCK", + "MKCOL", + "MOVE", + "PROPFIND", + "PROPPATCH", + "SEARCH", + "UNLOCK", + "BIND", + "REBIND", + "UNBIND", + "ACL", + "REPORT", + "MKACTIVITY", + "CHECKOUT", + "MERGE", + "M-SEARCH", + "NOTIFY", + "SUBSCRIBE", + "UNSUBSCRIBE", + "PATCH", + "PURGE", + "MKCALENDAR", + "LINK", + "UNLINK", + "SOURCE", + "PRI", + "DESCRIBE", + "ANNOUNCE", + "SETUP", + "PLAY", + "PAUSE", + "TEARDOWN", + "GET_PARAMETER", + "SET_PARAMETER", + "REDIRECT", + "RECORD", + "FLUSH", + "QUERY", +]; + +fn string<'s>( + scope: &mut v8::PinScope<'s, '_>, + value: &str, +) -> v8::Local<'s, v8::String> { + v8::String::new(scope, value).unwrap() +} + +fn set_value( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: v8::Local, +) { + let key = string(scope, name); + obj.set(scope, key.into(), value); +} + +fn get_value<'s>( + scope: &mut v8::PinScope<'s, '_>, + obj: v8::Local<'s, v8::Object>, + name: &str, +) -> Option> { + let key = string(scope, name); + obj.get(scope, key.into()) +} + +fn get_function<'s>( + scope: &mut v8::PinScope<'s, '_>, + obj: v8::Local<'s, v8::Object>, + name: &str, +) -> Option> { + get_value(scope, obj, name) + .and_then(|value| v8::Local::::try_from(value).ok()) +} + +fn call_method<'s>( + scope: &mut v8::PinScope<'s, '_>, + obj: v8::Local<'s, v8::Object>, + name: &str, + args: &[v8::Local], +) -> Option> { + let function = get_function(scope, obj, name)?; + function.call(scope, obj.into(), args) +} + +fn data_object<'s>( + scope: &mut v8::PinScope<'s, '_>, + values: &[(&str, v8::Local<'s, v8::Value>)], +) -> v8::Local<'s, v8::Object> { + let obj = v8::Object::new(scope); + for (key, value) in values { + set_value(scope, obj, key, *value); + } + obj +} + +fn callback_data_object<'s>( + args: &v8::FunctionCallbackArguments<'s>, +) -> Option> { + v8::Local::::try_from(args.data()).ok() +} + +fn callback_data_value<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: &v8::FunctionCallbackArguments<'s>, + name: &str, +) -> Option> { + let data = callback_data_object(args)?; + get_value(scope, data, name) +} + +fn function_with_data<'s>( + scope: &mut v8::PinScope<'s, '_>, + callback: impl MapFnTo, + data: v8::Local<'s, v8::Object>, +) -> v8::Local<'s, v8::Function> { + v8::Function::builder(callback) + .data(data.into()) + .build(scope) + .unwrap() +} + +fn function_no_data<'s>( + scope: &mut v8::PinScope<'s, '_>, + callback: impl MapFnTo, +) -> v8::Local<'s, v8::Function> { + v8::Function::builder(callback).build(scope).unwrap() +} + +fn parser_constructor_object<'s>( + scope: &mut v8::PinScope<'s, '_>, + parser: v8::Local<'s, v8::Object>, +) -> Option> { + get_value(scope, parser, "constructor") + .and_then(|value| v8::Local::::try_from(value).ok()) +} + +fn parser_constructor_value<'s>( + scope: &mut v8::PinScope<'s, '_>, + parser: v8::Local<'s, v8::Object>, + name: &str, +) -> Option> { + let constructor = parser_constructor_object(scope, parser)?; + get_value(scope, constructor, name) +} + +fn create_string_array<'s>( + scope: &mut v8::PinScope<'s, '_>, + values: &[&str], +) -> v8::Local<'s, v8::Array> { + let array = v8::Array::new(scope, values.len() as i32); + for (index, value) in values.iter().enumerate() { + let value = string(scope, value); + array.set_index(scope, index as u32, value.into()); + } + array +} + +fn set_int( + scope: &mut v8::PinScope, + obj: v8::Local, + name: &str, + value: i32, +) { + let value = v8::Integer::new(scope, value); + set_value(scope, obj, name, value.into()); +} + +fn get_native<'s>( + scope: &mut v8::PinScope<'s, '_>, + parser: v8::Local<'s, v8::Object>, +) -> Option> { + get_value(scope, parser, "_native") + .and_then(|value| v8::Local::::try_from(value).ok()) +} + +fn create_uint8_array_view<'s>( + scope: &mut v8::PinScope<'s, '_>, + buffer: v8::Local<'s, v8::Value>, + offset: usize, + length: usize, +) -> Option> { + if let Ok(array_buffer) = v8::Local::::try_from(buffer) { + return v8::Uint8Array::new(scope, array_buffer, offset, length); + } + let view = v8::Local::::try_from(buffer).ok()?; + let array_buffer = view.buffer(scope)?; + v8::Uint8Array::new(scope, array_buffer, view.byte_offset() + offset, length) +} + +fn make_body_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + parser: v8::Local<'s, v8::Object>, + callback: v8::Local<'s, v8::Value>, + buffer: v8::Local<'s, v8::Value>, +) -> v8::Local<'s, v8::Function> { + let data = data_object( + scope, + &[ + ("parser", parser.into()), + ("callback", callback), + ("Buffer", buffer), + ], + ); + function_with_data(scope, http_parser_on_body_callback, data) +} + +fn make_parse_error<'s>( + scope: &mut v8::PinScope<'s, '_>, + native: v8::Local<'s, v8::Object>, +) -> v8::Local<'s, v8::Object> { + let message = string(scope, "Parse Error"); + let error = v8::Exception::error(scope, message); + let error = v8::Local::::try_from(error).unwrap(); + let overflow = call_method(scope, native, "hasHeaderOverflow", &[]) + .map(|value| value.boolean_value(scope)) + .unwrap_or(false); + if overflow { + let code = string(scope, "HPE_HEADER_OVERFLOW"); + set_value(scope, error, "code", code.into()); + let reason = string(scope, "Header overflow"); + set_value(scope, error, "reason", reason.into()); + let message = string(scope, "Parse Error: Header overflow"); + set_value(scope, error, "message", message.into()); + } else { + let code = call_method(scope, native, "getLastErrorCode", &[]) + .unwrap_or_else(|| v8::undefined(scope).into()); + let reason = call_method(scope, native, "getLastErrorReason", &[]) + .unwrap_or_else(|| v8::undefined(scope).into()); + let code = code.to_rust_string_lossy(scope); + let code_string = if !code.is_empty() && code != "HPE_OK" { + code + } else { + "HPE_ERROR".to_string() + }; + let reason_string = reason.to_rust_string_lossy(scope); + let code = string(scope, &code_string); + set_value(scope, error, "code", code.into()); + let reason_value = string(scope, &reason_string); + set_value(scope, error, "reason", reason_value.into()); + if !reason_string.is_empty() { + let message = string(scope, &format!("Parse Error: {reason_string}")); + set_value(scope, error, "message", message.into()); + } + } + let bytes = call_method(scope, native, "getLastBytesParsed", &[]) + .unwrap_or_else(|| v8::Integer::new(scope, 0).into()); + set_value(scope, error, "bytesParsed", bytes); + error +} + +fn http_parser_constructor<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, +) { + let this = args.this(); + let Some(native_ctor) = + parser_constructor_value(scope, this, "_NativeHTTPParser") + .and_then(|value| v8::Local::::try_from(value).ok()) + else { + return; + }; + let Some(native) = native_ctor.new_instance(scope, &[]) else { + return; + }; + set_value(scope, this, "_native", native.into()); + if args.length() > 0 && !args.get(0).is_undefined() { + let zero = v8::Integer::new(scope, 0); + call_method( + scope, + native, + "initialize", + &[args.get(0), zero.into(), zero.into()], + ); + } +} + +fn http_parser_initialize<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, +) { + let this = args.this(); + let Some(native) = get_native(scope, this) else { + return; + }; + if args.length() > 1 && !args.get(1).is_undefined() { + let Some(async_resource_ctor) = + parser_constructor_value(scope, this, "_AsyncResource") + .and_then(|value| v8::Local::::try_from(value).ok()) + else { + return; + }; + let resource = args.get(1); + let resource_type = v8::Local::::try_from(resource) + .ok() + .and_then(|resource| get_value(scope, resource, "type")) + .filter(|value| !value.is_undefined()) + .unwrap_or_else(|| string(scope, "HTTPPARSER").into()); + if let Some(async_resource) = + async_resource_ctor.new_instance(scope, &[resource_type]) + { + set_value(scope, this, "_asyncResource", async_resource.into()); + } + } + + let max_header_size = if args.length() > 2 && !args.get(2).is_undefined() { + args.get(2) + } else { + v8::Integer::new(scope, 16384).into() + }; + let lenient_flags = if args.length() > 3 && !args.get(3).is_undefined() { + args.get(3) + } else { + v8::Integer::new(scope, 0).into() + }; + call_method( + scope, + native, + "initialize", + &[args.get(0), max_header_size, lenient_flags], + ); +} + +fn http_parser_do_execute<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'s>, +) { + let Some(parser) = callback_data_value(scope, &args, "parser") + .and_then(|value| v8::Local::::try_from(value).ok()) + else { + return; + }; + let Some(native) = get_native(scope, parser) else { + return; + }; + let Some(data) = callback_data_value(scope, &args, "data") else { + return; + }; + if let Some(result) = + call_method(scope, native, "execute", &[parser.into(), data]) + { + rv.set(result); + } +} + +fn http_parser_execute<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'s>, +) { + let parser = args.this(); + let Some(native) = get_native(scope, parser) else { + return; + }; + let Some(buffer_ctor) = parser_constructor_value(scope, parser, "_Buffer") + else { + return; + }; + let mut data = args.get(0); + if args.length() > 2 + && !args.get(1).is_undefined() + && !args.get(2).is_undefined() + { + let offset = args.get(1).uint32_value(scope).unwrap_or(0) as usize; + let length = args.get(2).uint32_value(scope).unwrap_or(0) as usize; + if offset != 0 + || v8::Local::::try_from(data) + .map(|view| view.byte_length() != length) + .unwrap_or(false) + { + if let Some(view) = create_uint8_array_view(scope, data, offset, length) { + data = view.into(); + } + } + } + + let original_on_body = parser + .get_index(scope, K_ON_BODY) + .unwrap_or_else(|| v8::undefined(scope).into()); + if !original_on_body.is_undefined() && !original_on_body.is_null() { + let wrapped = + make_body_callback(scope, parser, original_on_body, buffer_ctor); + parser.set_index(scope, K_ON_BODY, wrapped.into()); + } + + let exec_data = + data_object(scope, &[("parser", parser.into()), ("data", data)]); + let do_execute = function_with_data(scope, http_parser_do_execute, exec_data); + let result = get_value(scope, parser, "_asyncResource") + .and_then(|value| v8::Local::::try_from(value).ok()) + .and_then(|resource| { + let undefined = v8::undefined(scope); + call_method( + scope, + resource, + "runInAsyncScope", + &[do_execute.into(), undefined.into()], + ) + }) + .or_else(|| { + let undefined = v8::undefined(scope); + do_execute.call(scope, undefined.into(), &[]) + }); + + parser.set_index(scope, K_ON_BODY, original_on_body); + + let last_exception = get_value(scope, parser, "__lastException") + .unwrap_or_else(|| v8::undefined(scope).into()); + if !last_exception.is_undefined() { + set_value( + scope, + parser, + "__lastException", + v8::undefined(scope).into(), + ); + scope.throw_exception(last_exception); + return; + } + + let Some(result) = result else { + return; + }; + if result.int32_value(scope).unwrap_or(0) < 0 { + let error = make_parse_error(scope, native); + rv.set(error.into()); + } else { + rv.set(result); + } +} + +fn http_parser_on_body_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'s>, +) { + let Some(parser) = callback_data_value(scope, &args, "parser") + .and_then(|value| v8::Local::::try_from(value).ok()) + else { + return; + }; + let Some(callback) = callback_data_value(scope, &args, "callback") + .and_then(|value| v8::Local::::try_from(value).ok()) + else { + return; + }; + let Some(buffer_ctor) = callback_data_value(scope, &args, "Buffer") + .and_then(|value| v8::Local::::try_from(value).ok()) + else { + return; + }; + let chunk = args.get(0); + let Ok(view) = v8::Local::::try_from(chunk) else { + return; + }; + let Some(array_buffer) = view.buffer(scope) else { + return; + }; + let Some(from) = get_function(scope, buffer_ctor, "from") else { + return; + }; + let byte_offset = + v8::Integer::new_from_unsigned(scope, view.byte_offset() as u32); + let byte_length = + v8::Integer::new_from_unsigned(scope, view.byte_length() as u32); + let Some(buffer) = from.call( + scope, + buffer_ctor.into(), + &[array_buffer.into(), byte_offset.into(), byte_length.into()], + ) else { + return; + }; + if let Some(result) = callback.call(scope, parser.into(), &[buffer]) { + rv.set(result); + } +} + +fn http_parser_finish<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'s>, +) { + let this = args.this(); + let Some(native) = get_native(scope, this) else { + return; + }; + if let Some(result) = call_method(scope, native, "finish", &[this.into()]) { + rv.set(result); + } +} + +fn http_parser_forward_method<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + method: &str, +) { + let this = args.this(); + let Some(native) = get_native(scope, this) else { + return; + }; + call_method(scope, native, method, &[]); +} + +fn http_parser_pause<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, +) { + http_parser_forward_method(scope, args, "pause"); +} + +fn http_parser_resume<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, +) { + http_parser_forward_method(scope, args, "resume"); +} + +fn http_parser_close<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, +) { + http_parser_forward_method(scope, args, "close"); +} + +fn http_parser_free<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, +) { + http_parser_forward_method(scope, args, "free"); +} + +fn http_parser_remove<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, +) { + http_parser_forward_method(scope, args, "remove"); +} + +fn http_parser_get_current_buffer<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'s>, +) { + let this = args.this(); + let Some(native) = get_native(scope, this) else { + return; + }; + if let Some(result) = call_method(scope, native, "getCurrentBuffer", &[]) { + rv.set(result); + } +} + +fn http_parser_consume<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, +) { + let this = args.this(); + let Some(native) = get_native(scope, this) else { + return; + }; + call_method(scope, native, "consume", &[this.into(), args.get(0)]); +} + +fn http_parser_unconsume<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, +) { + let this = args.this(); + let Some(native) = get_native(scope, this) else { + return; + }; + call_method(scope, native, "unconsume", &[]); +} + +fn set_prototype_function<'s>( + scope: &mut v8::PinScope<'s, '_>, + prototype: v8::Local<'s, v8::Object>, + name: &str, + callback: impl MapFnTo, +) { + let function = function_no_data(scope, callback); + set_value(scope, prototype, name, function.into()); +} + +pub(crate) fn internal_binding_external_references() -> [ExternalReference; 14] +{ + [ + ExternalReference { + function: http_parser_constructor.map_fn_to(), + }, + ExternalReference { + function: http_parser_initialize.map_fn_to(), + }, + ExternalReference { + function: http_parser_execute.map_fn_to(), + }, + ExternalReference { + function: http_parser_do_execute.map_fn_to(), + }, + ExternalReference { + function: http_parser_on_body_callback.map_fn_to(), + }, + ExternalReference { + function: http_parser_finish.map_fn_to(), + }, + ExternalReference { + function: http_parser_pause.map_fn_to(), + }, + ExternalReference { + function: http_parser_resume.map_fn_to(), + }, + ExternalReference { + function: http_parser_close.map_fn_to(), + }, + ExternalReference { + function: http_parser_free.map_fn_to(), + }, + ExternalReference { + function: http_parser_remove.map_fn_to(), + }, + ExternalReference { + function: http_parser_get_current_buffer.map_fn_to(), + }, + ExternalReference { + function: http_parser_consume.map_fn_to(), + }, + ExternalReference { + function: http_parser_unconsume.map_fn_to(), + }, + ] +} + +#[op2] +pub fn op_node_internal_binding_http_parser<'s>( + scope: &mut v8::PinScope<'s, '_>, + native_http_parser: v8::Local<'s, v8::Value>, + buffer: v8::Local<'s, v8::Value>, + async_resource: v8::Local<'s, v8::Value>, +) -> v8::Local<'s, v8::Object> { + let constructor = function_no_data(scope, http_parser_constructor); + let name = string(scope, "HTTPParser"); + constructor.set_name(name); + + let prototype_key = string(scope, "prototype"); + let prototype = constructor + .get(scope, prototype_key.into()) + .and_then(|value| v8::Local::::try_from(value).ok()) + .unwrap(); + + set_prototype_function( + scope, + prototype, + "initialize", + http_parser_initialize, + ); + set_prototype_function(scope, prototype, "execute", http_parser_execute); + set_prototype_function(scope, prototype, "finish", http_parser_finish); + set_prototype_function(scope, prototype, "pause", http_parser_pause); + set_prototype_function(scope, prototype, "resume", http_parser_resume); + set_prototype_function(scope, prototype, "close", http_parser_close); + set_prototype_function(scope, prototype, "free", http_parser_free); + set_prototype_function(scope, prototype, "remove", http_parser_remove); + set_prototype_function( + scope, + prototype, + "getCurrentBuffer", + http_parser_get_current_buffer, + ); + set_prototype_function(scope, prototype, "consume", http_parser_consume); + set_prototype_function(scope, prototype, "unconsume", http_parser_unconsume); + + let constructor_obj: v8::Local = constructor.into(); + set_value( + scope, + constructor_obj, + "_NativeHTTPParser", + native_http_parser, + ); + set_value(scope, constructor_obj, "_Buffer", buffer); + set_value(scope, constructor_obj, "_AsyncResource", async_resource); + set_int(scope, constructor_obj, "REQUEST", 1); + set_int(scope, constructor_obj, "RESPONSE", 2); + set_int( + scope, + constructor_obj, + "kOnMessageBegin", + K_ON_MESSAGE_BEGIN as i32, + ); + set_int(scope, constructor_obj, "kOnHeaders", K_ON_HEADERS as i32); + set_int( + scope, + constructor_obj, + "kOnHeadersComplete", + K_ON_HEADERS_COMPLETE as i32, + ); + set_int(scope, constructor_obj, "kOnBody", K_ON_BODY as i32); + set_int( + scope, + constructor_obj, + "kOnMessageComplete", + K_ON_MESSAGE_COMPLETE as i32, + ); + set_int(scope, constructor_obj, "kOnExecute", K_ON_EXECUTE as i32); + set_int(scope, constructor_obj, "kOnTimeout", 6); + set_int(scope, constructor_obj, "kLenientNone", 0); + set_int(scope, constructor_obj, "kLenientHeaders", 1); + set_int(scope, constructor_obj, "kLenientChunkedLength", 2); + set_int(scope, constructor_obj, "kLenientKeepAlive", 4); + set_int(scope, constructor_obj, "kLenientTransferEncoding", 8); + set_int(scope, constructor_obj, "kLenientVersion", 16); + set_int(scope, constructor_obj, "kLenientDataAfterClose", 32); + set_int(scope, constructor_obj, "kLenientOptionalLFAfterCR", 64); + set_int( + scope, + constructor_obj, + "kLenientOptionalCRLFAfterChunk", + 128, + ); + set_int(scope, constructor_obj, "kLenientOptionalCRBeforeLF", 256); + set_int(scope, constructor_obj, "kLenientSpacesAfterChunkSize", 512); + set_int(scope, constructor_obj, "kLenientAll", 1023); + + let obj = v8::Object::new(scope); + let methods = create_string_array(scope, METHODS); + let all_methods = create_string_array(scope, ALL_METHODS); + set_value(scope, obj, "methods", methods.into()); + set_value(scope, obj, "allMethods", all_methods.into()); + set_value(scope, obj, "HTTPParser", constructor.into()); + obj +} + impl Inner { fn init(&mut self, parser_type: i32, lenient_flags: i32) { self.settings.on_message_begin = Some(on_message_begin); diff --git a/ext/node/polyfills/internal_binding/http_parser.ts b/ext/node/polyfills/internal_binding/http_parser.ts index 8d9661979c80db..69f0a80650db9b 100644 --- a/ext/node/polyfills/internal_binding/http_parser.ts +++ b/ext/node/polyfills/internal_binding/http_parser.ts @@ -1,319 +1,13 @@ // Copyright 2018-2026 the Deno authors. MIT license. -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -// deno-lint-ignore-file no-explicit-any prefer-primordials (function () { const { core } = __bootstrap; -const { HTTPParser: NativeHTTPParser } = core.ops; +const { + HTTPParser, + op_node_internal_binding_http_parser, +} = core.ops; const { Buffer } = core.loadExtScript("ext:deno_node/internal/buffer.mjs"); const { AsyncResource } = core.loadExtScript("ext:deno_node/async_hooks.ts"); -// Method names indexed by llhttp method enum values. -// Order must match llhttp_method_t in llhttp.h. -const methods = [ - "DELETE", - "GET", - "HEAD", - "POST", - "PUT", - "CONNECT", - "OPTIONS", - "TRACE", - "COPY", - "LOCK", - "MKCOL", - "MOVE", - "PROPFIND", - "PROPPATCH", - "SEARCH", - "UNLOCK", - "BIND", - "REBIND", - "UNBIND", - "ACL", - "REPORT", - "MKACTIVITY", - "CHECKOUT", - "MERGE", - "M-SEARCH", - "NOTIFY", - "SUBSCRIBE", - "UNSUBSCRIBE", - "PATCH", - "PURGE", - "MKCALENDAR", - "LINK", - "UNLINK", - "SOURCE", - "QUERY", -]; - -const allMethods = [ - "DELETE", - "GET", - "HEAD", - "POST", - "PUT", - "CONNECT", - "OPTIONS", - "TRACE", - "COPY", - "LOCK", - "MKCOL", - "MOVE", - "PROPFIND", - "PROPPATCH", - "SEARCH", - "UNLOCK", - "BIND", - "REBIND", - "UNBIND", - "ACL", - "REPORT", - "MKACTIVITY", - "CHECKOUT", - "MERGE", - "M-SEARCH", - "NOTIFY", - "SUBSCRIBE", - "UNSUBSCRIBE", - "PATCH", - "PURGE", - "MKCALENDAR", - "LINK", - "UNLINK", - "SOURCE", - "PRI", - "DESCRIBE", - "ANNOUNCE", - "SETUP", - "PLAY", - "PAUSE", - "TEARDOWN", - "GET_PARAMETER", - "SET_PARAMETER", - "REDIRECT", - "RECORD", - "FLUSH", - "QUERY", -]; - -// Callback indices - used as indexed properties on the parser instance. -const kOnMessageBegin = 0; -const kOnHeaders = 1; -const kOnHeadersComplete = 2; -const kOnBody = 3; -const kOnMessageComplete = 4; -const kOnExecute = 5; -const kOnTimeout = 6; - -/** - * JS wrapper around the native llhttp-based HTTPParser cppgc object. - * - * Node.js's `_http_common.js` sets callbacks as indexed properties: - * parser[kOnHeaders] = function(headers, url) { ... } - * parser[kOnHeadersComplete] = function(major, minor, headers, ...) { ... } - * - * The native parser reads these during execute() and calls them - * synchronously from the C callbacks. - */ -function HTTPParser(this: any, type?: number) { - // Create the native cppgc parser - this._native = new NativeHTTPParser(); - - // If type is provided in constructor, initialize immediately - if (type !== undefined) { - this._native.initialize(type, 0, 0); - } -} - -HTTPParser.prototype.initialize = function ( - this: any, - type: number, - asyncResource?: any, - maxHeaderSize?: number, - lenientFlags?: number, -) { - // Default to the global maxHeaderSize (16384) when not specified, - // matching Node.js behavior where 0 means "use the default". - const effectiveMaxHeaderSize = maxHeaderSize || 16384; - // Store the async resource so execute() can run callbacks in the - // correct async context (preserves AsyncLocalStorage through the - // native parser, emulating Node's MakeCallback behavior). - if (asyncResource) { - this._asyncResource = new AsyncResource( - asyncResource.type || "HTTPPARSER", - ); - } - this._native.initialize( - type, - effectiveMaxHeaderSize, - lenientFlags ?? 0, - ); -}; - -HTTPParser.prototype.execute = function ( - this: any, - buffer: Uint8Array, - offset?: number, - length?: number, -) { - // Node.js calls execute(buffer) or execute(buffer, offset, length) - let data: Uint8Array; - if ( - offset !== undefined && length !== undefined && - (offset !== 0 || length !== buffer.length) - ) { - data = buffer.subarray(offset, offset + length); - } else { - data = buffer; - } - - // Wrap the kOnBody callback to convert Uint8Array to Buffer. - // Node.js passes Buffer objects to body callbacks. - // deno-lint-ignore no-this-alias - const parser = this; - const origOnBody = this[kOnBody]; - if (origOnBody) { - this[kOnBody] = function (buf: Uint8Array) { - return origOnBody.call( - parser, - Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength), - ); - }; - } - - // Pass `this` (the JS wrapper) directly so callbacks set during - // parsing (e.g. trailer headers set in kOnHeadersComplete) are - // visible to subsequent callbacks. - // Run within the async resource scope to preserve AsyncLocalStorage - // context through native parser callbacks (emulates Node's MakeCallback). - const doExecute = () => - this._native.execute( - this, - new Uint8Array(data.buffer, data.byteOffset, data.byteLength), - ); - const result = this._asyncResource - ? this._asyncResource.runInAsyncScope(doExecute) - : doExecute(); - - // Restore original callback - this[kOnBody] = origOnBody; - - // Re-throw any JS exception that was captured during parsing. - // The #[op2(reentrant)] framework swallows pending v8 exceptions, - // so we capture them in a TryCatch in the Rust callbacks and - // store them on the JS parser object as __lastException. - if (this.__lastException !== undefined) { - const err = this.__lastException; - this.__lastException = undefined; - throw err; - } - - // On parser error, create an Error object with code/reason/bytesParsed - // matching Node.js behavior (node_http_parser.cc returns an Error object). - if (result < 0) { - const err: any = new Error("Parse Error"); - if (this._native.hasHeaderOverflow()) { - err.code = "HPE_HEADER_OVERFLOW"; - err.reason = "Header overflow"; - err.message = "Parse Error: Header overflow"; - } else { - const code = this._native.getLastErrorCode(); - const reason = this._native.getLastErrorReason(); - err.code = code && code !== "HPE_OK" ? code : "HPE_ERROR"; - err.reason = reason || "Parse Error"; - if (reason) { - err.message = `Parse Error: ${reason}`; - } - } - err.bytesParsed = this._native.getLastBytesParsed(); - return err; - } - - return result; -}; - -HTTPParser.prototype.finish = function (this: any) { - return this._native.finish(this); -}; - -HTTPParser.prototype.pause = function (this: any) { - this._native.pause(); -}; - -HTTPParser.prototype.resume = function (this: any) { - this._native.resume(); -}; - -HTTPParser.prototype.close = function (this: any) { - this._native.close(); -}; - -HTTPParser.prototype.free = function (this: any) { - this._native.free(); -}; - -HTTPParser.prototype.remove = function (this: any) { - this._native.remove(); -}; - -HTTPParser.prototype.getCurrentBuffer = function (this: any) { - return this._native.getCurrentBuffer(); -}; - -// consume/unconsume - server optimization: data flows directly from the -// TCP handle to the parser, bypassing the JS readable stream layer. -HTTPParser.prototype.consume = function (this: any, handle: any) { - this._native.consume(this, handle); -}; - -HTTPParser.prototype.unconsume = function (this: any) { - this._native.unconsume(); -}; - -// Static constants -HTTPParser.REQUEST = 1; -HTTPParser.RESPONSE = 2; -HTTPParser.kOnMessageBegin = kOnMessageBegin; -HTTPParser.kOnHeaders = kOnHeaders; -HTTPParser.kOnHeadersComplete = kOnHeadersComplete; -HTTPParser.kOnBody = kOnBody; -HTTPParser.kOnMessageComplete = kOnMessageComplete; -HTTPParser.kOnExecute = kOnExecute; -HTTPParser.kOnTimeout = kOnTimeout; -HTTPParser.kLenientNone = 0; -HTTPParser.kLenientHeaders = 1; -HTTPParser.kLenientChunkedLength = 2; -HTTPParser.kLenientKeepAlive = 4; -HTTPParser.kLenientTransferEncoding = 8; -HTTPParser.kLenientVersion = 16; -HTTPParser.kLenientDataAfterClose = 32; -HTTPParser.kLenientOptionalLFAfterCR = 64; -HTTPParser.kLenientOptionalCRLFAfterChunk = 128; -HTTPParser.kLenientOptionalCRBeforeLF = 256; -HTTPParser.kLenientSpacesAfterChunkSize = 512; -HTTPParser.kLenientAll = 1023; - -return { methods, allMethods, HTTPParser }; +return op_node_internal_binding_http_parser(HTTPParser, Buffer, AsyncResource); })(); From cf208d048ba120cd5554f4a75cf59deb7567d385 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 14:10:52 -0700 Subject: [PATCH 15/33] lazy load http internal bindings --- ext/node/polyfills/internal_binding/mod.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/ext/node/polyfills/internal_binding/mod.ts b/ext/node/polyfills/internal_binding/mod.ts index 7ee4b6b962196b..b27fd0c943feb7 100644 --- a/ext/node/polyfills/internal_binding/mod.ts +++ b/ext/node/polyfills/internal_binding/mod.ts @@ -53,15 +53,11 @@ const util = core.loadExtScript( "ext:deno_node/internal_binding/util.ts", ); const uvNamespace = core.loadExtScript("ext:deno_node/internal_binding/uv.ts"); -const httpParser = core.loadExtScript( - "ext:deno_node/internal_binding/http_parser.ts", -); -const http2Binding = core.loadExtScript( - "ext:deno_node/internal_binding/http2.ts", -); const inspectorBinding = core.loadExtScript( "ext:deno_node/internal_binding/inspector.js", ); +let httpParser; +let http2Binding; // Mutable shallow copy so callers can replace properties (e.g. wrap // `errname` with a deprecation warning when --pending-deprecation is set). @@ -98,8 +94,8 @@ const modules = { "fs_dir": {}, "fs_event_wrap": {}, "heap_utils": {}, - "http_parser": httpParser, - "http2": http2Binding, + "http_parser": {}, + "http2": {}, icu: {}, inspector: inspectorBinding, "js_stream": {}, @@ -146,6 +142,16 @@ const modules = { export type BindingName = keyof typeof modules; export function getBinding(name: BindingName) { + if (name === "http_parser") { + return httpParser ??= core.loadExtScript( + "ext:deno_node/internal_binding/http_parser.ts", + ); + } + if (name === "http2") { + return http2Binding ??= core.loadExtScript( + "ext:deno_node/internal_binding/http2.ts", + ); + } const mod = modules[name]; if (!mod) { throw new Error(`No such module: ${name}`); From 567a03aa8475cfa538f0c72025fbf3e7a72f67de Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 16:57:26 -0700 Subject: [PATCH 16/33] port udp send wrap internal binding to rust --- ext/node/lib.rs | 1 + ext/node/ops/internal_binding_async_wrap.rs | 1 + ext/node/ops/udp.rs | 32 +++++++++++++++++++ .../polyfills/internal_binding/udp_wrap.ts | 28 +++++----------- 4 files changed, 42 insertions(+), 20 deletions(-) diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 5e5cabecc67a1c..171e8e8779429c 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -482,6 +482,7 @@ deno_core::extension!(deno_node, ops::tcp_wrap::TCPWrap, ops::pipe_wrap::PipeConnectWrap, ops::pipe_wrap::PipeWrap, + ops::udp::SendWrap, ops::tls_wrap::TLSWrap, ops::llhttp::binding::HTTPParser, ops::http2::Http2Session, diff --git a/ext/node/ops/internal_binding_async_wrap.rs b/ext/node/ops/internal_binding_async_wrap.rs index de1cf04a5a53a3..d4acebd5daae13 100644 --- a/ext/node/ops/internal_binding_async_wrap.rs +++ b/ext/node/ops/internal_binding_async_wrap.rs @@ -64,6 +64,7 @@ const PROVIDER_TYPES: &[&str] = &[ "TCPCONNECTWRAP", "TCPSERVERWRAP", "TCPWRAP", + "TLSWRAP", "TTYWRAP", "UDPSENDWRAP", "UDPWRAP", diff --git a/ext/node/ops/udp.rs b/ext/node/ops/udp.rs index 65e7c986309991..228e3eea402131 100644 --- a/ext/node/ops/udp.rs +++ b/ext/node/ops/udp.rs @@ -10,6 +10,9 @@ use std::str::FromStr; use deno_core::CancelFuture; use deno_core::CancelHandle; +use deno_core::CppgcBase; +use deno_core::CppgcInherits; +use deno_core::GarbageCollected; use deno_core::JsBuffer; use deno_core::OpState; use deno_core::RcRef; @@ -23,6 +26,35 @@ use socket2::Socket; use socket2::Type; use tokio::net::UdpSocket; +use crate::ops::handle_wrap::AsyncWrap; +use crate::ops::handle_wrap::ProviderType; + +#[derive(CppgcBase, CppgcInherits)] +#[cppgc_inherits_from(AsyncWrap)] +#[repr(C)] +pub struct SendWrap { + base: AsyncWrap, +} + +unsafe impl GarbageCollected for SendWrap { + fn get_name(&self) -> &'static std::ffi::CStr { + c"SendWrap" + } + + fn trace(&self, _visitor: &mut deno_core::v8::cppgc::Visitor) {} +} + +#[op2(base, inherit = AsyncWrap)] +impl SendWrap { + #[constructor] + #[cppgc] + fn constructor(state: &mut OpState) -> SendWrap { + SendWrap { + base: AsyncWrap::create(state, ProviderType::UdpSendWrap as i32), + } + } +} + #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum NodeUdpError { #[class(inherit)] diff --git a/ext/node/polyfills/internal_binding/udp_wrap.ts b/ext/node/polyfills/internal_binding/udp_wrap.ts index 83f95b1796efc4..6ee2408227b393 100644 --- a/ext/node/polyfills/internal_binding/udp_wrap.ts +++ b/ext/node/polyfills/internal_binding/udp_wrap.ts @@ -39,6 +39,7 @@ const { op_node_udp_set_multicast_loopback, op_node_udp_set_multicast_ttl, op_node_udp_set_ttl, + SendWrap, } = core.ops; const { ArrayPrototypeMap, @@ -52,13 +53,12 @@ const { const osErrorRegExp = new SafeRegExp(/os error (40|90|10040)/); -const { - AsyncWrap, - providerType, -} = core.loadExtScript("ext:deno_node/internal_binding/async_wrap.ts"); const { HandleWrap } = core.loadExtScript( "ext:deno_node/internal_binding/handle_wrap.ts", ); +const { providerType } = core.loadExtScript( + "ext:deno_node/internal_binding/async_wrap.ts", +); const { ownerSymbol } = core.loadExtScript( "ext:deno_node/internal_binding/symbols.ts", ); @@ -75,6 +75,7 @@ const { os } = core.loadExtScript( ); type MessageType = string | Uint8Array | Buffer | DataView; +type SendWrapInstance = InstanceType; const AF_INET = 2; const AF_INET6 = 10; @@ -108,19 +109,6 @@ function isValidMulticastAddress( } } -class SendWrap extends AsyncWrap { - list!: MessageType[]; - address!: string; - port!: number; - - callback!: (error: Error | null, bytes?: number) => void; - oncomplete!: (err: number | null, sent?: number) => void; - - constructor() { - super(providerType.UDPSENDWRAP); - } -} - class UDP extends HandleWrap { [ownerSymbol]: unknown = null; @@ -458,7 +446,7 @@ class UDP extends HandleWrap { } send( - req: SendWrap, + req: SendWrapInstance, bufs: MessageType[], count: number, ...args: [number, string, boolean] | [boolean] @@ -467,7 +455,7 @@ class UDP extends HandleWrap { } send6( - req: SendWrap, + req: SendWrapInstance, bufs: MessageType[], count: number, ...args: [number, string, boolean] | [boolean] @@ -599,7 +587,7 @@ class UDP extends HandleWrap { } #doSend( - req: SendWrap, + req: SendWrapInstance, bufs: MessageType[], _count: number, args: [number, string, boolean] | [boolean], From baa300f645cf8ce26b2e7c79ea2eec16b6891dd3 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 17:03:16 -0700 Subject: [PATCH 17/33] port dns request wraps to rust --- ext/node/lib.rs | 3 + ext/node/ops/dns.rs | 84 ++++++++++++ .../polyfills/internal_binding/cares_wrap.ts | 122 ++++++++---------- 3 files changed, 144 insertions(+), 65 deletions(-) diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 171e8e8779429c..8cb0c0d110d506 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -468,6 +468,9 @@ deno_core::extension!(deno_node, ops::perf_hooks::BaseHistogram, ops::handle_wrap::AsyncWrap, ops::handle_wrap::HandleWrap, + ops::dns::GetAddrInfoReqWrap, + ops::dns::GetNameInfoReqWrap, + ops::dns::QueryReqWrap, ops::wasi::WasiContext, ops::stream_wrap::WriteWrap, ops::stream_wrap::ShutdownWrap, diff --git a/ext/node/ops/dns.rs b/ext/node/ops/dns.rs index 9d31d8cdb15bd4..13c659eb2cdeef 100644 --- a/ext/node/ops/dns.rs +++ b/ext/node/ops/dns.rs @@ -7,6 +7,9 @@ use std::rc::Rc; #[cfg(target_family = "windows")] use std::sync::OnceLock; +use deno_core::CppgcBase; +use deno_core::CppgcInherits; +use deno_core::GarbageCollected; use deno_core::OpState; use deno_core::op2; use deno_core::unsync::spawn_blocking; @@ -16,6 +19,87 @@ use deno_permissions::PermissionCheckError; use deno_permissions::PermissionsContainer; use socket2::SockAddr; +use crate::ops::handle_wrap::AsyncWrap; +use crate::ops::handle_wrap::ProviderType; + +#[derive(CppgcBase, CppgcInherits)] +#[cppgc_inherits_from(AsyncWrap)] +#[repr(C)] +pub struct GetAddrInfoReqWrap { + base: AsyncWrap, +} + +unsafe impl GarbageCollected for GetAddrInfoReqWrap { + fn get_name(&self) -> &'static std::ffi::CStr { + c"GetAddrInfoReqWrap" + } + + fn trace(&self, _visitor: &mut deno_core::v8::cppgc::Visitor) {} +} + +#[op2(base, inherit = AsyncWrap)] +impl GetAddrInfoReqWrap { + #[constructor] + #[cppgc] + fn constructor(state: &mut OpState) -> GetAddrInfoReqWrap { + GetAddrInfoReqWrap { + base: AsyncWrap::create(state, ProviderType::GetAddrInfoReqWrap as i32), + } + } +} + +#[derive(CppgcBase, CppgcInherits)] +#[cppgc_inherits_from(AsyncWrap)] +#[repr(C)] +pub struct GetNameInfoReqWrap { + base: AsyncWrap, +} + +unsafe impl GarbageCollected for GetNameInfoReqWrap { + fn get_name(&self) -> &'static std::ffi::CStr { + c"GetNameInfoReqWrap" + } + + fn trace(&self, _visitor: &mut deno_core::v8::cppgc::Visitor) {} +} + +#[op2(base, inherit = AsyncWrap)] +impl GetNameInfoReqWrap { + #[constructor] + #[cppgc] + fn constructor(state: &mut OpState) -> GetNameInfoReqWrap { + GetNameInfoReqWrap { + base: AsyncWrap::create(state, ProviderType::GetNameInfoReqWrap as i32), + } + } +} + +#[derive(CppgcBase, CppgcInherits)] +#[cppgc_inherits_from(AsyncWrap)] +#[repr(C)] +pub struct QueryReqWrap { + base: AsyncWrap, +} + +unsafe impl GarbageCollected for QueryReqWrap { + fn get_name(&self) -> &'static std::ffi::CStr { + c"QueryReqWrap" + } + + fn trace(&self, _visitor: &mut deno_core::v8::cppgc::Visitor) {} +} + +#[op2(base, inherit = AsyncWrap)] +impl QueryReqWrap { + #[constructor] + #[cppgc] + fn constructor(state: &mut OpState) -> QueryReqWrap { + QueryReqWrap { + base: AsyncWrap::create(state, ProviderType::QueryWrap as i32), + } + } +} + #[derive(Debug, thiserror::Error, JsError)] pub enum DnsError { #[class(type)] diff --git a/ext/node/polyfills/internal_binding/cares_wrap.ts b/ext/node/polyfills/internal_binding/cares_wrap.ts index 681d33d8f7310d..2dd68738a4ec81 100644 --- a/ext/node/polyfills/internal_binding/cares_wrap.ts +++ b/ext/node/polyfills/internal_binding/cares_wrap.ts @@ -60,6 +60,9 @@ const { op_net_get_system_dns_servers, op_node_getaddrinfo, op_node_getnameinfo, + GetAddrInfoReqWrap, + GetNameInfoReqWrap, + QueryReqWrap, } = core.ops; const { isIPv4, isIPv6 } = core.loadExtScript( "ext:deno_node/internal/net.ts", @@ -92,31 +95,30 @@ const DNS_ORDER_VERBATIM = 0; const DNS_ORDER_IPV4_FIRST = 1; const DNS_ORDER_IPV6_FIRST = 2; -class GetAddrInfoReqWrap extends AsyncWrap { - family!: number; - hostname!: string; - port: number | undefined; +type NativeGetAddrInfoReqWrap = InstanceType; +type NativeGetNameInfoReqWrap = InstanceType; +type NativeQueryReqWrap = InstanceType; - callback!: ( +interface GetAddrInfoReqWrapInstance extends NativeGetAddrInfoReqWrap { + family: number; + hostname: string; + port: number | undefined; + callback: ( err: ErrnoException | null, addressOrAddresses?: string | LookupAddress[] | null, family?: number, ) => void; - resolve!: (addressOrAddresses: LookupAddress | LookupAddress[]) => void; - reject!: (err: ErrnoException | null) => void; - oncomplete!: ( + resolve: (addressOrAddresses: LookupAddress | LookupAddress[]) => void; + reject: (err: ErrnoException | null) => void; + oncomplete: ( err: number | null, addresses: string[], netPermToken: object | undefined, ) => void; - - constructor() { - super(providerType.GETADDRINFOREQWRAP); - } } function getaddrinfo( - req: GetAddrInfoReqWrap, + req: GetAddrInfoReqWrapInstance, hostname: string, family: number, _hints: number, @@ -195,30 +197,25 @@ function getaddrinfo( return 0; } -class GetNameInfoReqWrap extends AsyncWrap { - address!: string; - port!: number; - +interface GetNameInfoReqWrapInstance extends NativeGetNameInfoReqWrap { + address: string; + port: number; callback?: ( err: ErrnoException | null, hostname?: string, service?: string, ) => void; - resolve!: (result: { hostname: string; service: string }) => void; - reject!: (err: ErrnoException | null) => void; - oncomplete!: ( + resolve: (result: { hostname: string; service: string }) => void; + reject: (err: ErrnoException | null) => void; + oncomplete: ( err: Error | null, hostname?: string, service?: string, ) => void; - - constructor() { - super(providerType.GETNAMEINFOREQWRAP); - } } function getnameinfo( - req: GetNameInfoReqWrap, + req: GetNameInfoReqWrapInstance, address: string, port: number, ): number { @@ -233,45 +230,40 @@ function getnameinfo( return 0; } -class QueryReqWrap extends AsyncWrap { - bindingName!: string; - hostname!: string; - ttl!: boolean; - - callback!: ( +interface QueryReqWrapInstance extends NativeQueryReqWrap { + bindingName: string; + hostname: string; + ttl: boolean; + callback: ( err: ErrnoException | null, // deno-lint-ignore no-explicit-any records?: any, ) => void; // deno-lint-ignore no-explicit-any - resolve!: (records: any) => void; - reject!: (err: ErrnoException | null) => void; - oncomplete!: ( + resolve: (records: any) => void; + reject: (err: ErrnoException | null) => void; + oncomplete: ( err: number, // deno-lint-ignore no-explicit-any records: any, ttls?: number[], ) => void; - - constructor() { - super(providerType.QUERYWRAP); - } } interface ChannelWrapQuery { - queryAny(req: QueryReqWrap, name: string): number; - queryA(req: QueryReqWrap, name: string): number; - queryAaaa(req: QueryReqWrap, name: string): number; - queryCaa(req: QueryReqWrap, name: string): number; - queryCname(req: QueryReqWrap, name: string): number; - queryMx(req: QueryReqWrap, name: string): number; - queryNs(req: QueryReqWrap, name: string): number; - queryTxt(req: QueryReqWrap, name: string): number; - querySrv(req: QueryReqWrap, name: string): number; - queryPtr(req: QueryReqWrap, name: string): number; - queryNaptr(req: QueryReqWrap, name: string): number; - querySoa(req: QueryReqWrap, name: string): number; - getHostByAddr(req: QueryReqWrap, name: string): number; + queryAny(req: QueryReqWrapInstance, name: string): number; + queryA(req: QueryReqWrapInstance, name: string): number; + queryAaaa(req: QueryReqWrapInstance, name: string): number; + queryCaa(req: QueryReqWrapInstance, name: string): number; + queryCname(req: QueryReqWrapInstance, name: string): number; + queryMx(req: QueryReqWrapInstance, name: string): number; + queryNs(req: QueryReqWrapInstance, name: string): number; + queryTxt(req: QueryReqWrapInstance, name: string): number; + querySrv(req: QueryReqWrapInstance, name: string): number; + queryPtr(req: QueryReqWrapInstance, name: string): number; + queryNaptr(req: QueryReqWrapInstance, name: string): number; + querySoa(req: QueryReqWrapInstance, name: string): number; + getHostByAddr(req: QueryReqWrapInstance, name: string): number; } const trailingDotRegExp = new SafeRegExp(/\.$/); @@ -296,7 +288,7 @@ class ChannelWrap extends AsyncWrap implements ChannelWrapQuery { #timeout: number; #tries: number; #maxTimeout: number; - #pendingQueries: Set = new SafeSet(); + #pendingQueries: Set = new SafeSet(); #cancelRids: Set = new SafeSet(); constructor(timeout: number, tries: number, maxTimeout: number) { @@ -427,7 +419,7 @@ class ChannelWrap extends AsyncWrap implements ChannelWrapQuery { return { code: codeMap.get("UNKNOWN")!, ret: [] }; } - queryAny(req: QueryReqWrap, name: string): number { + queryAny(req: QueryReqWrapInstance, name: string): number { SetPrototypeAdd(this.#pendingQueries, req); PromisePrototypeThen( @@ -534,7 +526,7 @@ class ChannelWrap extends AsyncWrap implements ChannelWrapQuery { return 0; } - queryA(req: QueryReqWrap, name: string): number { + queryA(req: QueryReqWrapInstance, name: string): number { SetPrototypeAdd(this.#pendingQueries, req); PromisePrototypeThen(this.#query(name, "A", req.ttl), ({ code, ret }) => { @@ -558,7 +550,7 @@ class ChannelWrap extends AsyncWrap implements ChannelWrapQuery { return 0; } - queryAaaa(req: QueryReqWrap, name: string): number { + queryAaaa(req: QueryReqWrapInstance, name: string): number { SetPrototypeAdd(this.#pendingQueries, req); PromisePrototypeThen( @@ -585,7 +577,7 @@ class ChannelWrap extends AsyncWrap implements ChannelWrapQuery { return 0; } - queryCaa(req: QueryReqWrap, name: string): number { + queryCaa(req: QueryReqWrapInstance, name: string): number { SetPrototypeAdd(this.#pendingQueries, req); PromisePrototypeThen(this.#query(name, "CAA"), ({ code, ret }) => { @@ -606,7 +598,7 @@ class ChannelWrap extends AsyncWrap implements ChannelWrapQuery { return 0; } - queryCname(req: QueryReqWrap, name: string): number { + queryCname(req: QueryReqWrapInstance, name: string): number { SetPrototypeAdd(this.#pendingQueries, req); PromisePrototypeThen(this.#query(name, "CNAME"), ({ code, ret }) => { @@ -619,7 +611,7 @@ class ChannelWrap extends AsyncWrap implements ChannelWrapQuery { return 0; } - queryMx(req: QueryReqWrap, name: string): number { + queryMx(req: QueryReqWrapInstance, name: string): number { SetPrototypeAdd(this.#pendingQueries, req); PromisePrototypeThen(this.#query(name, "MX"), ({ code, ret }) => { @@ -640,7 +632,7 @@ class ChannelWrap extends AsyncWrap implements ChannelWrapQuery { return 0; } - queryNaptr(req: QueryReqWrap, name: string): number { + queryNaptr(req: QueryReqWrapInstance, name: string): number { SetPrototypeAdd(this.#pendingQueries, req); PromisePrototypeThen(this.#query(name, "NAPTR"), ({ code, ret }) => { @@ -665,7 +657,7 @@ class ChannelWrap extends AsyncWrap implements ChannelWrapQuery { return 0; } - queryNs(req: QueryReqWrap, name: string): number { + queryNs(req: QueryReqWrapInstance, name: string): number { SetPrototypeAdd(this.#pendingQueries, req); PromisePrototypeThen(this.#query(name, "NS"), ({ code, ret }) => { @@ -683,7 +675,7 @@ class ChannelWrap extends AsyncWrap implements ChannelWrapQuery { return 0; } - queryPtr(req: QueryReqWrap, name: string): number { + queryPtr(req: QueryReqWrapInstance, name: string): number { SetPrototypeAdd(this.#pendingQueries, req); PromisePrototypeThen(this.#query(name, "PTR"), ({ code, ret }) => { @@ -701,7 +693,7 @@ class ChannelWrap extends AsyncWrap implements ChannelWrapQuery { return 0; } - querySoa(req: QueryReqWrap, name: string): number { + querySoa(req: QueryReqWrapInstance, name: string): number { SetPrototypeAdd(this.#pendingQueries, req); PromisePrototypeThen(this.#query(name, "SOA"), ({ code, ret }) => { @@ -731,7 +723,7 @@ class ChannelWrap extends AsyncWrap implements ChannelWrapQuery { return 0; } - querySrv(req: QueryReqWrap, name: string): number { + querySrv(req: QueryReqWrapInstance, name: string): number { SetPrototypeAdd(this.#pendingQueries, req); PromisePrototypeThen(this.#query(name, "SRV"), ({ code, ret }) => { @@ -754,7 +746,7 @@ class ChannelWrap extends AsyncWrap implements ChannelWrapQuery { return 0; } - queryTxt(req: QueryReqWrap, name: string): number { + queryTxt(req: QueryReqWrapInstance, name: string): number { SetPrototypeAdd(this.#pendingQueries, req); PromisePrototypeThen(this.#query(name, "TXT"), ({ code, ret }) => { @@ -767,7 +759,7 @@ class ChannelWrap extends AsyncWrap implements ChannelWrapQuery { return 0; } - getHostByAddr(req: QueryReqWrap, name: string): number { + getHostByAddr(req: QueryReqWrapInstance, name: string): number { let reverseName: string; if (isIPv4(name)) { From 9ff0a17a5022c7d957188d311ddcfc2f6773cb8e Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 17:09:21 -0700 Subject: [PATCH 18/33] port udp handle base to rust --- ext/node/lib.rs | 1 + ext/node/ops/udp.rs | 30 +++++++++++++++++++ .../polyfills/internal_binding/udp_wrap.ts | 12 +++----- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 8cb0c0d110d506..56e4feff1445e1 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -486,6 +486,7 @@ deno_core::extension!(deno_node, ops::pipe_wrap::PipeConnectWrap, ops::pipe_wrap::PipeWrap, ops::udp::SendWrap, + ops::udp::UDP, ops::tls_wrap::TLSWrap, ops::llhttp::binding::HTTPParser, ops::http2::Http2Session, diff --git a/ext/node/ops/udp.rs b/ext/node/ops/udp.rs index 228e3eea402131..6e79c860911f2b 100644 --- a/ext/node/ops/udp.rs +++ b/ext/node/ops/udp.rs @@ -27,6 +27,7 @@ use socket2::Type; use tokio::net::UdpSocket; use crate::ops::handle_wrap::AsyncWrap; +use crate::ops::handle_wrap::HandleWrap; use crate::ops::handle_wrap::ProviderType; #[derive(CppgcBase, CppgcInherits)] @@ -55,6 +56,35 @@ impl SendWrap { } } +#[derive(CppgcBase, CppgcInherits)] +#[cppgc_inherits_from(HandleWrap)] +#[repr(C)] +pub struct UDP { + base: HandleWrap, +} + +unsafe impl GarbageCollected for UDP { + fn get_name(&self) -> &'static std::ffi::CStr { + c"UDP" + } + + fn trace(&self, _visitor: &mut deno_core::v8::cppgc::Visitor) {} +} + +#[op2(base, inherit = HandleWrap)] +impl UDP { + #[constructor] + #[cppgc] + fn constructor(state: &mut OpState) -> UDP { + UDP { + base: HandleWrap::create( + AsyncWrap::create(state, ProviderType::UdpWrap as i32), + None, + ), + } + } +} + #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum NodeUdpError { #[class(inherit)] diff --git a/ext/node/polyfills/internal_binding/udp_wrap.ts b/ext/node/polyfills/internal_binding/udp_wrap.ts index 6ee2408227b393..b8ac181bd22fd6 100644 --- a/ext/node/polyfills/internal_binding/udp_wrap.ts +++ b/ext/node/polyfills/internal_binding/udp_wrap.ts @@ -40,6 +40,7 @@ const { op_node_udp_set_multicast_ttl, op_node_udp_set_ttl, SendWrap, + UDP: NativeUDP, } = core.ops; const { ArrayPrototypeMap, @@ -53,12 +54,7 @@ const { const osErrorRegExp = new SafeRegExp(/os error (40|90|10040)/); -const { HandleWrap } = core.loadExtScript( - "ext:deno_node/internal_binding/handle_wrap.ts", -); -const { providerType } = core.loadExtScript( - "ext:deno_node/internal_binding/async_wrap.ts", -); +core.loadExtScript("ext:deno_node/internal_binding/handle_wrap.ts"); const { ownerSymbol } = core.loadExtScript( "ext:deno_node/internal_binding/symbols.ts", ); @@ -109,7 +105,7 @@ function isValidMulticastAddress( } } -class UDP extends HandleWrap { +class UDP extends NativeUDP { [ownerSymbol]: unknown = null; #address?: string; @@ -151,7 +147,7 @@ class UDP extends HandleWrap { ) => any; constructor() { - super(providerType.UDPWRAP); + super(); } addMembership(multicastAddress: string, interfaceAddress?: string): number { From 58ba1477835b1f95f064cf2574e2457df4447fe3 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 17:12:16 -0700 Subject: [PATCH 19/33] port dns channel wrap base to rust --- ext/node/lib.rs | 1 + ext/node/ops/dns.rs | 26 +++++++++++++++++++ ext/node/polyfills/internal/dns/utils.ts | 4 +-- .../polyfills/internal_binding/cares_wrap.ts | 9 +++---- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 56e4feff1445e1..7000b18e3d3e51 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -471,6 +471,7 @@ deno_core::extension!(deno_node, ops::dns::GetAddrInfoReqWrap, ops::dns::GetNameInfoReqWrap, ops::dns::QueryReqWrap, + ops::dns::ChannelWrap, ops::wasi::WasiContext, ops::stream_wrap::WriteWrap, ops::stream_wrap::ShutdownWrap, diff --git a/ext/node/ops/dns.rs b/ext/node/ops/dns.rs index 13c659eb2cdeef..f4f831d6ad2be5 100644 --- a/ext/node/ops/dns.rs +++ b/ext/node/ops/dns.rs @@ -100,6 +100,32 @@ impl QueryReqWrap { } } +#[derive(CppgcBase, CppgcInherits)] +#[cppgc_inherits_from(AsyncWrap)] +#[repr(C)] +pub struct ChannelWrap { + base: AsyncWrap, +} + +unsafe impl GarbageCollected for ChannelWrap { + fn get_name(&self) -> &'static std::ffi::CStr { + c"ChannelWrap" + } + + fn trace(&self, _visitor: &mut deno_core::v8::cppgc::Visitor) {} +} + +#[op2(base, inherit = AsyncWrap)] +impl ChannelWrap { + #[constructor] + #[cppgc] + fn constructor(state: &mut OpState) -> ChannelWrap { + ChannelWrap { + base: AsyncWrap::create(state, ProviderType::DnsChannel as i32), + } + } +} + #[derive(Debug, thiserror::Error, JsError)] pub enum DnsError { #[class(type)] diff --git a/ext/node/polyfills/internal/dns/utils.ts b/ext/node/polyfills/internal/dns/utils.ts index 5b860ec43574c0..d9bc35711ee638 100644 --- a/ext/node/polyfills/internal/dns/utils.ts +++ b/ext/node/polyfills/internal/dns/utils.ts @@ -420,10 +420,10 @@ class Resolver { } } -let defaultResolver = new Resolver(); +let defaultResolver: Resolver | undefined; function getDefaultResolver(): Resolver { - return defaultResolver; + return defaultResolver ??= new Resolver(); } function setDefaultResolver(resolver: T) { diff --git a/ext/node/polyfills/internal_binding/cares_wrap.ts b/ext/node/polyfills/internal_binding/cares_wrap.ts index 2dd68738a4ec81..fe876c6164d119 100644 --- a/ext/node/polyfills/internal_binding/cares_wrap.ts +++ b/ext/node/polyfills/internal_binding/cares_wrap.ts @@ -63,6 +63,7 @@ const { GetAddrInfoReqWrap, GetNameInfoReqWrap, QueryReqWrap, + ChannelWrap: NativeChannelWrap, } = core.ops; const { isIPv4, isIPv6 } = core.loadExtScript( "ext:deno_node/internal/net.ts", @@ -70,10 +71,6 @@ const { isIPv4, isIPv6 } = core.loadExtScript( const { codeMap } = core.loadExtScript( "ext:deno_node/internal_binding/uv.ts", ); -const { - AsyncWrap, - providerType, -} = core.loadExtScript("ext:deno_node/internal_binding/async_wrap.ts"); const { ares_strerror } = core.loadExtScript( "ext:deno_node/internal_binding/ares.ts", ); @@ -283,7 +280,7 @@ function getSystemDnsServers(): [string, number][] { return systemDnsServers; } -class ChannelWrap extends AsyncWrap implements ChannelWrapQuery { +class ChannelWrap extends NativeChannelWrap implements ChannelWrapQuery { #servers: [string, number][] | null = null; #timeout: number; #tries: number; @@ -292,7 +289,7 @@ class ChannelWrap extends AsyncWrap implements ChannelWrapQuery { #cancelRids: Set = new SafeSet(); constructor(timeout: number, tries: number, maxTimeout: number) { - super(providerType.DNSCHANNEL); + super(); this.#timeout = timeout; this.#tries = tries; From daf7fb2c7c64aa3b231a3361520d0a94483fbe4c Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 17:14:08 -0700 Subject: [PATCH 20/33] lazy load cares internal binding --- ext/node/polyfills/internal_binding/mod.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ext/node/polyfills/internal_binding/mod.ts b/ext/node/polyfills/internal_binding/mod.ts index b27fd0c943feb7..909cb96bd7165c 100644 --- a/ext/node/polyfills/internal_binding/mod.ts +++ b/ext/node/polyfills/internal_binding/mod.ts @@ -18,9 +18,6 @@ const { default: blockList } = core.loadExtScript( const buffer = core.loadExtScript( "ext:deno_node/internal_binding/buffer.ts", ); -const { default: caresWrap } = core.loadExtScript( - "ext:deno_node/internal_binding/cares_wrap.ts", -); const constants = core.loadExtScript( "ext:deno_node/internal_binding/constants.ts", ); @@ -58,6 +55,7 @@ const inspectorBinding = core.loadExtScript( ); let httpParser; let http2Binding; +let caresWrap; // Mutable shallow copy so callers can replace properties (e.g. wrap // `errname` with a deprecation warning when --pending-deprecation is set). @@ -83,7 +81,7 @@ const modules = { "async_wrap": asyncWrap, "block_list": blockList, buffer, - "cares_wrap": caresWrap, + "cares_wrap": {}, config: {}, constants, contextify: {}, @@ -152,6 +150,11 @@ export function getBinding(name: BindingName) { "ext:deno_node/internal_binding/http2.ts", ); } + if (name === "cares_wrap") { + return caresWrap ??= core.loadExtScript( + "ext:deno_node/internal_binding/cares_wrap.ts", + ); + } const mod = modules[name]; if (!mod) { throw new Error(`No such module: ${name}`); From 550e3b814ecb20d91f48755a2e67cdeccd4862e7 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 17:37:59 -0700 Subject: [PATCH 21/33] port udp state and sync methods to rust --- ext/node/ops/udp.rs | 726 +++++++++++++++++- .../polyfills/internal_binding/udp_wrap.ts | 463 ++--------- 2 files changed, 756 insertions(+), 433 deletions(-) diff --git a/ext/node/ops/udp.rs b/ext/node/ops/udp.rs index 6e79c860911f2b..6c581780ffa36c 100644 --- a/ext/node/ops/udp.rs +++ b/ext/node/ops/udp.rs @@ -1,6 +1,7 @@ // Copyright 2018-2026 the Deno authors. MIT license. use std::borrow::Cow; +use std::cell::Cell; use std::cell::RefCell; use std::net::Ipv4Addr; use std::net::Ipv6Addr; @@ -19,6 +20,8 @@ use deno_core::RcRef; use deno_core::Resource; use deno_core::ResourceId; use deno_core::op2; +use deno_core::uv_compat; +use deno_core::v8; use deno_permissions::PermissionsContainer; use socket2::Domain; use socket2::Protocol; @@ -61,6 +64,50 @@ impl SendWrap { #[repr(C)] pub struct UDP { base: HandleWrap, + rid: Cell>, + address: RefCell>, + family: Cell>, + port: Cell>, + remote_address: RefCell>, + remote_family: Cell>, + remote_port: Cell>, + recv_buffer_size: Cell, + send_buffer_size: Cell, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum UdpFamily { + Ipv4, + Ipv6, +} + +impl UdpFamily { + fn from_addr(addr: &str) -> Self { + if addr.contains(':') { + Self::Ipv6 + } else { + Self::Ipv4 + } + } + + fn from_family(family: i32) -> Self { + if family == 10 { Self::Ipv6 } else { Self::Ipv4 } + } + + fn as_str(self) -> &'static str { + match self { + Self::Ipv4 => "IPv4", + Self::Ipv6 => "IPv6", + } + } + + fn is_ipv6(self) -> bool { + self == Self::Ipv6 + } + + fn is_ipv4(self) -> bool { + self == Self::Ipv4 + } } unsafe impl GarbageCollected for UDP { @@ -71,16 +118,650 @@ unsafe impl GarbageCollected for UDP { fn trace(&self, _visitor: &mut deno_core::v8::cppgc::Visitor) {} } -#[op2(base, inherit = HandleWrap)] impl UDP { - #[constructor] - #[cppgc] - fn constructor(state: &mut OpState) -> UDP { + fn new(state: &mut OpState) -> UDP { UDP { base: HandleWrap::create( AsyncWrap::create(state, ProviderType::UdpWrap as i32), None, ), + rid: Cell::new(None), + address: RefCell::new(None), + family: Cell::new(None), + port: Cell::new(None), + remote_address: RefCell::new(None), + remote_family: Cell::new(None), + remote_port: Cell::new(None), + recv_buffer_size: Cell::new(64 * 1024), + send_buffer_size: Cell::new(64 * 1024), + } + } +} + +const UV_UNKNOWN: i32 = -4094; + +fn io_error_to_uv(err: &std::io::Error) -> i32 { + err + .raw_os_error() + .map(|code| -code) + .unwrap_or(uv_compat::UV_EINVAL) +} + +fn udp_error_to_uv(err: &NodeUdpError) -> i32 { + match err { + NodeUdpError::Io(err) => io_error_to_uv(err), + NodeUdpError::Resource(_) => uv_compat::UV_EBADF, + NodeUdpError::AddrParse(_) + | NodeUdpError::NoResolvedAddress + | NodeUdpError::InvalidHostname(_) => uv_compat::UV_EINVAL, + NodeUdpError::Canceled(_) => uv_compat::UV_ECANCELED, + NodeUdpError::Permission(_) => UV_UNKNOWN, + } +} + +fn set_i32<'s>( + scope: &mut v8::PinScope<'s, '_>, + obj: v8::Local<'s, v8::Object>, + name: &str, + value: i32, +) { + let key = v8::String::new(scope, name).unwrap(); + let value = v8::Integer::new(scope, value); + obj.set(scope, key.into(), value.into()); +} + +fn set_str<'s>( + scope: &mut v8::PinScope<'s, '_>, + obj: v8::Local<'s, v8::Object>, + name: &str, + value: &str, +) { + let key = v8::String::new(scope, name).unwrap(); + let value = v8::String::new(scope, value).unwrap(); + obj.set(scope, key.into(), value.into()); +} + +impl UDP { + fn set_bound_state( + &self, + rid: ResourceId, + address: String, + port: u16, + family: UdpFamily, + ) { + self.rid.set(Some(rid)); + *self.address.borrow_mut() = Some(address); + self.port.set(Some(port)); + self.family.set(Some(family)); + } + + fn bind_inner( + &self, + state: &mut OpState, + ip: &str, + port: i32, + flags: i32, + family: UdpFamily, + ) -> Result { + let Ok(port) = u16::try_from(port) else { + return Ok(uv_compat::UV_EINVAL); + }; + match node_udp_bind(state, ip, port, (flags & 4) != 0, (flags & 2) != 0) { + Ok((rid, address, bound_port)) => { + self.set_bound_state(rid, address, bound_port, family); + Ok(0) + } + Err(NodeUdpError::Permission(err)) => Err(err), + Err(err) => Ok(udp_error_to_uv(&err)), + } + } + + fn set_remote(&self, ip: &str, port: i32, family: UdpFamily) -> i32 { + let Ok(port) = u16::try_from(port) else { + return uv_compat::UV_EINVAL; + }; + *self.remote_address.borrow_mut() = Some(ip.to_string()); + self.remote_port.set(Some(port)); + self.remote_family.set(Some(family)); + 0 + } +} + +#[op2(base, inherit = HandleWrap)] +impl UDP { + #[constructor] + #[cppgc] + fn constructor(state: &mut OpState) -> UDP { + UDP::new(state) + } + + #[nofast] + fn bind( + &self, + state: &mut OpState, + #[string] ip: &str, + #[smi] port: i32, + #[smi] flags: i32, + ) -> Result { + self.bind_inner(state, ip, port, flags, UdpFamily::Ipv4) + } + + #[nofast] + fn bind6( + &self, + state: &mut OpState, + #[string] ip: &str, + #[smi] port: i32, + #[smi] flags: i32, + ) -> Result { + self.bind_inner(state, ip, port, flags, UdpFamily::Ipv6) + } + + #[fast] + fn open(&self, state: &mut OpState, #[smi] fd: i32) -> i32 { + match node_udp_open(state, fd) { + Ok((rid, address, port)) => { + let family = UdpFamily::from_addr(&address); + self.set_bound_state(rid, address, port, family); + 0 + } + Err(err) => udp_error_to_uv(&err), + } + } + + #[fast] + #[rename("fdForIpc")] + fn fd_for_ipc(&self, state: &mut OpState) -> i32 { + let Some(rid) = self.rid.get() else { + return -1; + }; + let Ok(resource) = state.resource_table.get::(rid) + else { + return -1; + }; + #[cfg(unix)] + { + use std::os::unix::io::AsRawFd; + let fd = resource.socket.as_raw_fd(); + if fd < 0 { + return -1; + } + // SAFETY: fd is a valid open file descriptor. F_DUPFD_CLOEXEC + // atomically dups and sets CLOEXEC, avoiding a race window. + unsafe { libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, 0) } + } + #[cfg(not(unix))] + { + let _ = resource; + -1 + } + } + + #[fast] + fn connect(&self, #[string] ip: &str, #[smi] port: i32) -> i32 { + self.set_remote(ip, port, UdpFamily::Ipv4) + } + + #[fast] + fn connect6(&self, #[string] ip: &str, #[smi] port: i32) -> i32 { + self.set_remote(ip, port, UdpFamily::Ipv6) + } + + #[fast] + fn disconnect(&self) -> i32 { + *self.remote_address.borrow_mut() = None; + self.remote_port.set(None); + self.remote_family.set(None); + 0 + } + + #[fast] + #[rename("_setRemote")] + fn set_remote_from_js( + &self, + #[string] ip: &str, + #[smi] port: i32, + #[smi] family: i32, + ) -> i32 { + self.set_remote(ip, port, UdpFamily::from_family(family)) + } + + #[nofast] + fn getsockname<'s>( + &self, + scope: &mut v8::PinScope<'s, '_>, + sockname: v8::Local<'s, v8::Object>, + ) -> i32 { + let address = self.address.borrow(); + let Some(address) = address.as_deref() else { + return uv_compat::UV_EBADF; + }; + let Some(port) = self.port.get() else { + return uv_compat::UV_EBADF; + }; + let Some(family) = self.family.get() else { + return uv_compat::UV_EBADF; + }; + set_str(scope, sockname, "address", address); + set_i32(scope, sockname, "port", port.into()); + set_str(scope, sockname, "family", family.as_str()); + 0 + } + + #[nofast] + fn getpeername<'s>( + &self, + scope: &mut v8::PinScope<'s, '_>, + peername: v8::Local<'s, v8::Object>, + ) -> i32 { + let address = self.remote_address.borrow(); + let Some(address) = address.as_deref() else { + return uv_compat::UV_EBADF; + }; + let Some(port) = self.remote_port.get() else { + return uv_compat::UV_EBADF; + }; + let Some(family) = self.remote_family.get() else { + return uv_compat::UV_EBADF; + }; + set_str(scope, peername, "address", address); + set_i32(scope, peername, "port", port.into()); + set_str(scope, peername, "family", family.as_str()); + 0 + } + + #[rename("bufferSize")] + fn buffer_size<'s>( + &self, + scope: &mut v8::PinScope<'s, '_>, + #[smi] size: i32, + buffer: bool, + ctx: v8::Local<'s, v8::Object>, + ) -> v8::Local<'s, v8::Value> { + if self.address.borrow().is_none() { + #[cfg(windows)] + let (code, errno, message) = ( + "ENOTSOCK", + uv_compat::UV_ENOTSOCK, + "socket operation on non-socket", + ); + #[cfg(not(windows))] + let (code, errno, message) = + ("EBADF", uv_compat::UV_EBADF, "bad file descriptor"); + set_i32(scope, ctx, "errno", errno); + set_str(scope, ctx, "code", code); + set_str(scope, ctx, "message", message); + set_str( + scope, + ctx, + "syscall", + if buffer { + "uv_recv_buffer_size" + } else { + "uv_send_buffer_size" + }, + ); + return v8::undefined(scope).into(); + } + + if size != 0 { + let size = if cfg!(target_os = "linux") { + size * 2 + } else { + size + }; + if buffer { + self.recv_buffer_size.set(size as usize); + } else { + self.send_buffer_size.set(size as usize); + } + return v8::Integer::new(scope, size).into(); + } + + let size = if buffer { + self.recv_buffer_size.get() + } else { + self.send_buffer_size.get() + }; + v8::Integer::new(scope, size as i32).into() + } + + #[fast] + #[rename("setBroadcast")] + fn set_broadcast(&self, state: &mut OpState, #[smi] on: i32) -> i32 { + let Some(rid) = self.rid.get() else { + return uv_compat::UV_EBADF; + }; + let result = state + .resource_table + .get::(rid) + .map_err(NodeUdpError::from) + .and_then(|resource| { + resource.socket.set_broadcast(on == 1)?; + Ok(()) + }); + match result { + Ok(()) => 0, + Err(err) => udp_error_to_uv(&err), + } + } + + #[rename("addMembership")] + fn add_membership( + &self, + state: &mut OpState, + #[string] multicast_address: &str, + #[string] interface_address: Option, + ) -> i32 { + let Some(rid) = self.rid.get() else { + return uv_compat::UV_EBADF; + }; + let result = if self.family.get().is_some_and(UdpFamily::is_ipv6) { + state + .resource_table + .get::(rid) + .map_err(NodeUdpError::from) + .and_then(|resource| { + let addr = Ipv6Addr::from_str(multicast_address)?; + let iface = resolve_ipv6_interface(interface_address.as_deref())?; + resource.socket.join_multicast_v6(&addr, iface)?; + Ok(()) + }) + } else { + state + .resource_table + .get::(rid) + .map_err(NodeUdpError::from) + .and_then(|resource| { + let addr = Ipv4Addr::from_str(multicast_address)?; + let iface = interface_address + .as_deref() + .map(Ipv4Addr::from_str) + .transpose()? + .unwrap_or(Ipv4Addr::UNSPECIFIED); + resource.socket.join_multicast_v4(addr, iface)?; + Ok(()) + }) + }; + match result { + Ok(()) => 0, + Err(err) => udp_error_to_uv(&err), + } + } + + #[rename("dropMembership")] + fn drop_membership( + &self, + state: &mut OpState, + #[string] multicast_address: &str, + #[string] interface_address: Option, + ) -> i32 { + let Some(rid) = self.rid.get() else { + return uv_compat::UV_EBADF; + }; + let result = if self.family.get().is_some_and(UdpFamily::is_ipv6) { + state + .resource_table + .get::(rid) + .map_err(NodeUdpError::from) + .and_then(|resource| { + let addr = Ipv6Addr::from_str(multicast_address)?; + let iface = resolve_ipv6_interface(interface_address.as_deref())?; + resource.socket.leave_multicast_v6(&addr, iface)?; + Ok(()) + }) + } else { + state + .resource_table + .get::(rid) + .map_err(NodeUdpError::from) + .and_then(|resource| { + let addr = Ipv4Addr::from_str(multicast_address)?; + let iface = interface_address + .as_deref() + .map(Ipv4Addr::from_str) + .transpose()? + .unwrap_or(Ipv4Addr::UNSPECIFIED); + resource.socket.leave_multicast_v4(addr, iface)?; + Ok(()) + }) + }; + match result { + Ok(()) => 0, + Err(err) => udp_error_to_uv(&err), + } + } + + #[rename("addSourceSpecificMembership")] + fn add_source_specific_membership( + &self, + state: &mut OpState, + #[string] source_address: &str, + #[string] group_address: &str, + #[string] interface_address: Option, + ) -> i32 { + let Some(rid) = self.rid.get() else { + return uv_compat::UV_EBADF; + }; + let result = state + .resource_table + .get::(rid) + .map_err(NodeUdpError::from) + .and_then(|resource| { + source_specific_multicast( + &resource.socket, + Ipv4Addr::from_str(source_address)?, + Ipv4Addr::from_str(group_address)?, + Ipv4Addr::from_str( + interface_address.as_deref().unwrap_or("0.0.0.0"), + )?, + { + #[cfg(unix)] + { + libc::IP_ADD_SOURCE_MEMBERSHIP + } + #[cfg(windows)] + { + windows_sys::Win32::Networking::WinSock::IP_ADD_SOURCE_MEMBERSHIP + } + }, + ) + }); + match result { + Ok(()) => 0, + Err(err) => udp_error_to_uv(&err), + } + } + + #[rename("dropSourceSpecificMembership")] + fn drop_source_specific_membership( + &self, + state: &mut OpState, + #[string] source_address: &str, + #[string] group_address: &str, + #[string] interface_address: Option, + ) -> i32 { + let Some(rid) = self.rid.get() else { + return uv_compat::UV_EBADF; + }; + let result = state + .resource_table + .get::(rid) + .map_err(NodeUdpError::from) + .and_then(|resource| { + source_specific_multicast( + &resource.socket, + Ipv4Addr::from_str(source_address)?, + Ipv4Addr::from_str(group_address)?, + Ipv4Addr::from_str( + interface_address.as_deref().unwrap_or("0.0.0.0"), + )?, + { + #[cfg(unix)] + { + libc::IP_DROP_SOURCE_MEMBERSHIP + } + #[cfg(windows)] + { + windows_sys::Win32::Networking::WinSock::IP_DROP_SOURCE_MEMBERSHIP + } + }, + ) + }); + match result { + Ok(()) => 0, + Err(err) => udp_error_to_uv(&err), + } + } + + #[nofast] + #[rename("setMulticastInterface")] + fn set_multicast_interface( + &self, + state: &mut OpState, + #[string] interface_address: &str, + ) -> i32 { + let Some(rid) = self.rid.get() else { + return uv_compat::UV_EBADF; + }; + let is_ipv6 = self.family.get().is_some_and(UdpFamily::is_ipv6); + let result = state + .resource_table + .get::(rid) + .map_err(NodeUdpError::from) + .and_then(|resource| { + let sock_ref = socket2::SockRef::from(&resource.socket); + if is_ipv6 { + let index = ipv6_interface_index(interface_address)?; + sock_ref.set_multicast_if_v6(index)?; + } else { + let addr: Ipv4Addr = interface_address.parse().map_err(|_| { + NodeUdpError::Io(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "invalid IPv4 address", + )) + })?; + sock_ref.set_multicast_if_v4(&addr)?; + } + Ok(()) + }); + match result { + Ok(()) => 0, + Err(err) => udp_error_to_uv(&err), + } + } + + #[fast] + #[rename("setMulticastLoopback")] + fn set_multicast_loopback(&self, state: &mut OpState, #[smi] on: i32) -> i32 { + let Some(rid) = self.rid.get() else { + return uv_compat::UV_EBADF; + }; + let is_ipv4 = self.family.get().is_some_and(UdpFamily::is_ipv4); + let result = state + .resource_table + .get::(rid) + .map_err(NodeUdpError::from) + .and_then(|resource| { + if is_ipv4 { + resource.socket.set_multicast_loop_v4(on == 1)?; + } else { + resource.socket.set_multicast_loop_v6(on == 1)?; + } + Ok(()) + }); + match result { + Ok(()) => 0, + Err(err) => udp_error_to_uv(&err), + } + } + + #[fast] + #[rename("setMulticastTTL")] + fn set_multicast_ttl(&self, state: &mut OpState, #[smi] ttl: i32) -> i32 { + if !(1..=255).contains(&ttl) { + return uv_compat::UV_EINVAL; + } + let Some(rid) = self.rid.get() else { + return uv_compat::UV_EBADF; + }; + if !self.family.get().is_some_and(UdpFamily::is_ipv4) { + return 0; + } + let result = state + .resource_table + .get::(rid) + .map_err(NodeUdpError::from) + .and_then(|resource| { + resource.socket.set_multicast_ttl_v4(ttl as u32)?; + Ok(()) + }); + match result { + Ok(()) => 0, + Err(err) => udp_error_to_uv(&err), + } + } + + #[fast] + #[rename("setTTL")] + fn set_ttl(&self, state: &mut OpState, #[smi] ttl: i32) -> i32 { + if !(1..=255).contains(&ttl) { + return uv_compat::UV_EINVAL; + } + let Some(rid) = self.rid.get() else { + return uv_compat::UV_EBADF; + }; + let result = state + .resource_table + .get::(rid) + .map_err(NodeUdpError::from) + .and_then(|resource| { + let sock_ref = socket2::SockRef::from(&resource.socket); + sock_ref.set_ttl(ttl as u32)?; + Ok(()) + }); + match result { + Ok(()) => 0, + Err(err) => udp_error_to_uv(&err), + } + } + + #[fast] + #[rename("_rid")] + fn rid(&self) -> i32 { + self.rid.get().map(|rid| rid as i32).unwrap_or(-1) + } + + #[fast] + #[rename("_recvBufferSize")] + fn recv_buffer_size(&self) -> i32 { + self.recv_buffer_size.get() as i32 + } + + #[fast] + #[rename("_remotePort")] + fn remote_port(&self) -> i32 { + self.remote_port.get().map(i32::from).unwrap_or(-1) + } + + #[string] + #[rename("_remoteAddress")] + fn remote_address(&self) -> Option { + self.remote_address.borrow().clone() + } + + #[fast] + #[rename("_closeResource")] + fn close_resource(&self, state: &mut OpState) { + self.address.borrow_mut().take(); + self.family.set(None); + self.port.set(None); + self.remote_address.borrow_mut().take(); + self.remote_family.set(None); + self.remote_port.set(None); + if let Some(rid) = self.rid.take() { + #[allow(deprecated)] + let _ = state.resource_table.close(rid); } } } @@ -125,12 +806,10 @@ impl Resource for NodeUdpSocketResource { } } -#[op2] -#[serde] -pub fn op_node_udp_bind( +fn node_udp_bind( state: &mut OpState, - #[string] hostname: &str, - #[smi] port: u16, + hostname: &str, + port: u16, reuse_address: bool, ipv6_only: bool, ) -> Result<(ResourceId, String, u16), NodeUdpError> { @@ -181,6 +860,18 @@ pub fn op_node_udp_bind( Ok((rid, local_addr.ip().to_string(), local_addr.port())) } +#[op2] +#[serde] +pub fn op_node_udp_bind( + state: &mut OpState, + #[string] hostname: &str, + #[smi] port: u16, + reuse_address: bool, + ipv6_only: bool, +) -> Result<(ResourceId, String, u16), NodeUdpError> { + node_udp_bind(state, hostname, port, reuse_address, ipv6_only) +} + #[op2] pub fn op_node_udp_join_multi_v4( state: &mut OpState, @@ -729,11 +1420,9 @@ pub fn op_node_udp_fd_for_ipc( /// Adopt an existing file descriptor as a UDP socket resource. Used on the /// receiving side of IPC handle passing. -#[op2] -#[serde] -pub fn op_node_udp_open( +fn node_udp_open( state: &mut OpState, - #[smi] fd: i32, + fd: i32, ) -> Result<(ResourceId, String, u16), NodeUdpError> { #[cfg(unix)] { @@ -759,3 +1448,14 @@ pub fn op_node_udp_open( ))) } } + +/// Adopt an existing file descriptor as a UDP socket resource. Used on the +/// receiving side of IPC handle passing. +#[op2] +#[serde] +pub fn op_node_udp_open( + state: &mut OpState, + #[smi] fd: i32, +) -> Result<(ResourceId, String, u16), NodeUdpError> { + node_udp_open(state, fd) +} diff --git a/ext/node/polyfills/internal_binding/udp_wrap.ts b/ext/node/polyfills/internal_binding/udp_wrap.ts index b8ac181bd22fd6..3652ba24a458f0 100644 --- a/ext/node/polyfills/internal_binding/udp_wrap.ts +++ b/ext/node/polyfills/internal_binding/udp_wrap.ts @@ -23,22 +23,8 @@ (function () { const { core, primordials } = __bootstrap; const { - op_node_udp_bind, - op_node_udp_fd_for_ipc, - op_node_udp_join_multi_v4, - op_node_udp_join_multi_v6, - op_node_udp_join_source_specific, - op_node_udp_leave_multi_v4, - op_node_udp_leave_multi_v6, - op_node_udp_leave_source_specific, - op_node_udp_open, op_node_udp_recv, op_node_udp_send, - op_node_udp_set_broadcast, - op_node_udp_set_multicast_interface, - op_node_udp_set_multicast_loopback, - op_node_udp_set_multicast_ttl, - op_node_udp_set_ttl, SendWrap, UDP: NativeUDP, } = core.ops; @@ -47,7 +33,6 @@ const { ErrorPrototype, ObjectPrototypeIsPrototypeOf, SafeRegExp, - StringPrototypeIncludes, StringPrototypeMatch, Uint8Array, } = primordials; @@ -58,17 +43,9 @@ core.loadExtScript("ext:deno_node/internal_binding/handle_wrap.ts"); const { ownerSymbol } = core.loadExtScript( "ext:deno_node/internal_binding/symbols.ts", ); -const { codeMap, errorMap } = core.loadExtScript( - "ext:deno_node/internal_binding/uv.ts", -); +const { codeMap } = core.loadExtScript("ext:deno_node/internal_binding/uv.ts"); const { Buffer } = core.loadExtScript("ext:deno_node/internal/buffer.mjs"); const { isIP } = core.loadExtScript("ext:deno_node/internal/net.ts"); -const { isLinux, isWindows } = core.loadExtScript( - "ext:deno_node/_util/os.ts", -); -const { os } = core.loadExtScript( - "ext:deno_node/internal_binding/constants.ts", -); type MessageType = string | Uint8Array | Buffer | DataView; type SendWrapInstance = InstanceType; @@ -76,54 +53,12 @@ type SendWrapInstance = InstanceType; const AF_INET = 2; const AF_INET6 = 10; -const UDP_DGRAM_MAXSIZE = 64 * 1024; - -/** Validate that the address is a parseable IPv4 address. */ -function isValidIPv4Address(address: string): boolean { - return isIP(address) === 4; -} - -/** Validate multicast address matches the socket family. */ -function isValidMulticastAddress( - multicastAddress: string, - family: string | undefined, - interfaceAddress?: string, -): boolean { - if (family === "IPv6") { - // IPv6 multicast - interface can be address, name, or address%zone - // Validation of interface is done in Rust - return isIP(multicastAddress) === 6; - } else { - // IPv4 multicast - if (!isValidIPv4Address(multicastAddress)) return false; - if ( - interfaceAddress !== undefined && !isValidIPv4Address(interfaceAddress) - ) { - return false; - } - return true; - } -} - class UDP extends NativeUDP { [ownerSymbol]: unknown = null; - #address?: string; - #family?: string; - #port?: number; - - #remoteAddress?: string; - #remoteFamily?: string; - #remotePort?: number; - - #rid?: number; #receiving = false; - #recvPromiseId?: number; #unrefed = false; - #recvBufferSize = UDP_DGRAM_MAXSIZE; - #sendBufferSize = UDP_DGRAM_MAXSIZE; - onmessage!: ( nread: number, handle: UDP, @@ -150,79 +85,13 @@ class UDP extends NativeUDP { super(); } - addMembership(multicastAddress: string, interfaceAddress?: string): number { - if ( - !isValidMulticastAddress( - multicastAddress, - this.#family, - interfaceAddress, - ) - ) { - return codeMap.get("EINVAL")!; - } - - if (this.#rid === undefined) { - return codeMap.get("EBADF")!; - } - - try { - if (this.#family === "IPv6") { - op_node_udp_join_multi_v6( - this.#rid, - multicastAddress, - interfaceAddress ?? null, - ); - } else { - op_node_udp_join_multi_v4( - this.#rid, - multicastAddress, - interfaceAddress ?? null, - ); - } - return 0; - } catch { - return codeMap.get("EINVAL")!; - } - } - - addSourceSpecificMembership( - sourceAddress: string, - groupAddress: string, - interfaceAddress?: string, - ): number { - if ( - !isValidIPv4Address(sourceAddress) || - !isValidIPv4Address(groupAddress) - ) { - return codeMap.get("EINVAL")!; - } - - if (this.#rid === undefined) { - return codeMap.get("EBADF")!; - } - - try { - op_node_udp_join_source_specific( - this.#rid, - sourceAddress, - groupAddress, - interfaceAddress ?? "0.0.0.0", - ); - } catch { - return codeMap.get("EINVAL")!; - } - return 0; - } - /** * Bind to an IPv4 address. * @param ip The hostname to bind to. * @param port The port to bind to * @return An error status code. */ - bind(ip: string, port: number, flags: number): number { - return this.#doBind(ip, port, flags, AF_INET); - } + declare bind: (ip: string, port: number, flags: number) => number; /** * Bind to an IPv6 address. @@ -230,189 +99,62 @@ class UDP extends NativeUDP { * @param port The port to bind to * @return An error status code. */ - bind6(ip: string, port: number, flags: number): number { - return this.#doBind(ip, port, flags, AF_INET6); - } + declare bind6: (ip: string, port: number, flags: number) => number; - bufferSize( + declare bufferSize: ( size: number, buffer: boolean, ctx: Record, - ): number | undefined { - if (!this.#address) { - const err = isWindows ? "ENOTSOCK" : "EBADF"; - ctx.errno = codeMap.get(err)!; - ctx.code = err; - ctx.message = errorMap.get(ctx.errno)![1]; - ctx.syscall = buffer ? "uv_recv_buffer_size" : "uv_send_buffer_size"; - - return; - } - - if (size !== 0) { - size = isLinux ? size * 2 : size; - - if (buffer) { - return (this.#recvBufferSize = size); - } - - return (this.#sendBufferSize = size); - } - - return buffer ? this.#recvBufferSize : this.#sendBufferSize; - } - - connect(ip: string, port: number): number { - return this.#doConnect(ip, port, AF_INET); - } - - connect6(ip: string, port: number): number { - return this.#doConnect(ip, port, AF_INET6); - } - - disconnect(): number { - this.#remoteAddress = undefined; - this.#remotePort = undefined; - this.#remoteFamily = undefined; - - return 0; - } + ) => number | undefined; - dropMembership( + declare connect: (ip: string, port: number) => number; + declare connect6: (ip: string, port: number) => number; + declare disconnect: () => number; + declare addMembership: ( multicastAddress: string, interfaceAddress?: string, - ): number { - if ( - !isValidMulticastAddress( - multicastAddress, - this.#family, - interfaceAddress, - ) - ) { - return codeMap.get("EINVAL")!; - } - - if (this.#rid === undefined) { - return codeMap.get("EBADF")!; - } - - try { - if (this.#family === "IPv6") { - op_node_udp_leave_multi_v6( - this.#rid, - multicastAddress, - interfaceAddress ?? null, - ); - } else { - op_node_udp_leave_multi_v4( - this.#rid, - multicastAddress, - interfaceAddress ?? null, - ); - } - return 0; - } catch { - return codeMap.get("EINVAL")!; - } - } - - dropSourceSpecificMembership( + ) => number; + declare dropMembership: ( + multicastAddress: string, + interfaceAddress?: string, + ) => number; + declare addSourceSpecificMembership: ( sourceAddress: string, groupAddress: string, interfaceAddress?: string, - ): number { - if ( - !isValidIPv4Address(sourceAddress) || - !isValidIPv4Address(groupAddress) - ) { - return codeMap.get("EINVAL")!; - } - - if (this.#rid === undefined) { - return codeMap.get("EBADF")!; - } - - try { - op_node_udp_leave_source_specific( - this.#rid, - sourceAddress, - groupAddress, - interfaceAddress ?? "0.0.0.0", - ); - } catch { - return codeMap.get("EINVAL")!; - } - return 0; - } + ) => number; + declare dropSourceSpecificMembership: ( + sourceAddress: string, + groupAddress: string, + interfaceAddress?: string, + ) => number; /** * Populates the provided object with remote address entries. * @param peername An object to add the remote address entries to. * @return An error status code. */ - getpeername(peername: Record): number { - if (this.#remoteAddress === undefined) { - return codeMap.get("EBADF")!; - } - - peername.address = this.#remoteAddress; - peername.port = this.#remotePort!; - peername.family = this.#remoteFamily!; - - return 0; - } + declare getpeername: (peername: Record) => number; /** * Populates the provided object with local address entries. * @param sockname An object to add the local address entries to. * @return An error status code. */ - getsockname(sockname: Record): number { - if (this.#address === undefined) { - return codeMap.get("EBADF")!; - } - - sockname.address = this.#address; - sockname.port = this.#port!; - sockname.family = this.#family!; - - return 0; - } + declare getsockname: (sockname: Record) => number; /** * Opens an existing file descriptor as this UDP socket. * @param fd The file descriptor to open. * @return An error status code. */ - open(fd: number): number { - try { - const result = op_node_udp_open(fd); - const rid = result[0]; - const hostname = result[1]; - const boundPort = result[2]; - this.#rid = rid; - this.#address = hostname; - this.#port = boundPort; - // Determine family from the address string returned by the op. - this.#family = StringPrototypeIncludes(hostname, ":") - ? ("IPv6" as const) - : ("IPv4" as const); - return 0; - } catch (e) { - return codeMap.get(e.code ?? "UNKNOWN") ?? codeMap.get("UNKNOWN")!; - } - } + declare open: (fd: number) => number; /** * Return the raw fd so it can be sent over IPC via SCM_RIGHTS. * Returns -1 on platforms that don't support fd-passing. */ - fdForIpc(): number { - if (this.#rid === undefined) { - return -1; - } - return op_node_udp_fd_for_ipc(this.#rid); - } + declare fdForIpc: () => number; /** * Start receiving on the connection. @@ -459,129 +201,22 @@ class UDP extends NativeUDP { return this.#doSend(req, bufs, count, args, AF_INET6); } - setBroadcast(bool: 0 | 1): number { - if (this.#rid === undefined) { - return codeMap.get("EBADF")!; - } - - try { - op_node_udp_set_broadcast(this.#rid, bool === 1); - return 0; - } catch { - return codeMap.get("EINVAL")!; - } - } - - setMulticastInterface(interfaceAddress: string): number { - if (this.#rid === undefined) { - return codeMap.get("EBADF")!; - } - - try { - op_node_udp_set_multicast_interface( - this.#rid, - this.#family === "IPv6", - interfaceAddress, - ); - return 0; - } catch { - return codeMap.get("EINVAL")!; - } - } - - setMulticastLoopback(bool: 0 | 1): number { - if (this.#rid === undefined) { - return codeMap.get("EBADF")!; - } - - try { - op_node_udp_set_multicast_loopback( - this.#rid, - this.#family === "IPv4", - bool === 1, - ); - return 0; - } catch { - return codeMap.get("EINVAL")!; - } - } - - setMulticastTTL(ttl: number): number { - if (ttl < 1 || ttl > 255) { - return codeMap.get("EINVAL")!; - } - - if (this.#rid === undefined) { - return codeMap.get("EBADF")!; - } - - try { - if (this.#family === "IPv4") { - op_node_udp_set_multicast_ttl(this.#rid, ttl); - } - return 0; - } catch { - return codeMap.get("EINVAL")!; - } - } - - setTTL(ttl: number): number { - if (ttl < 1 || ttl > 255) { - return codeMap.get("EINVAL")!; - } - - if (this.#rid === undefined) { - return codeMap.get("EBADF")!; - } - - try { - op_node_udp_set_ttl(this.#rid, ttl); - return 0; - } catch { - return codeMap.get("EINVAL")!; - } - } + declare setBroadcast: (bool: 0 | 1) => number; + declare setMulticastInterface: (interfaceAddress: string) => number; + declare setMulticastLoopback: (bool: 0 | 1) => number; + declare setMulticastTTL: (ttl: number) => number; + declare setTTL: (ttl: number) => number; + declare _rid: () => number; + declare _setRemote: (ip: string, port: number, family: number) => number; + declare _remoteAddress: () => string | undefined; + declare _remotePort: () => number; + declare _recvBufferSize: () => number; + declare _closeResource: () => void; override unref() { this.#unrefed = true; } - #doBind(ip: string, port: number, flags: number, family: number): number { - try { - const result = op_node_udp_bind( - ip, - port, - (flags & os.UV_UDP_REUSEADDR) !== 0, - (flags & os.UV_UDP_IPV6ONLY) !== 0, - ); - const rid = result[0]; - const hostname = result[1]; - const boundPort = result[2]; - this.#rid = rid; - this.#address = hostname; - this.#port = boundPort; - this.#family = family === AF_INET6 - ? ("IPv6" as const) - : ("IPv4" as const); - return 0; - } catch (e) { - if (ObjectPrototypeIsPrototypeOf(Deno.errors.NotCapable.prototype, e)) { - throw e; - } - return codeMap.get(e.code ?? "UNKNOWN") ?? codeMap.get("UNKNOWN")!; - } - } - - #doConnect(ip: string, port: number, family: number): number { - this.#remoteAddress = ip; - this.#remotePort = port; - this.#remoteFamily = family === AF_INET6 - ? ("IPv6" as const) - : ("IPv4" as const); - - return 0; - } - #doSend( req: SendWrapInstance, bufs: MessageType[], @@ -592,8 +227,7 @@ class UDP extends NativeUDP { let hasCallback: boolean; if (args.length === 3) { - this.#remotePort = args[0] as number; - this.#remoteAddress = args[1] as string; + this._setRemote(args[1] as string, args[0] as number, _family); hasCallback = args[2] as boolean; } else { hasCallback = args[0] as boolean; @@ -619,10 +253,10 @@ class UDP extends NativeUDP { try { sent = await op_node_udp_send( - this.#rid!, + this._rid(), payload, - this.#remoteAddress!, - this.#remotePort!, + this._remoteAddress()!, + this._remotePort(), ); } catch (e) { if ( @@ -658,14 +292,14 @@ class UDP extends NativeUDP { return; } - const p = new Uint8Array(this.#recvBufferSize); + const p = new Uint8Array(this._recvBufferSize()); let nread: number; let remoteHostname: string | null = null; let remotePort: number | null = null; try { - const promise = op_node_udp_recv(this.#rid!, p); + const promise = op_node_udp_recv(this._rid(), p); if (this.#unrefed) { core.unrefOpPromise(promise); } @@ -712,18 +346,7 @@ class UDP extends NativeUDP { override _onClose(): number { this.#receiving = false; - this.#address = undefined; - this.#port = undefined; - this.#family = undefined; - - if (this.#rid !== undefined) { - try { - core.close(this.#rid); - } catch { - // already closed - } - this.#rid = undefined; - } + this._closeResource(); return 0; } From d6c97ba1ade4f09d064d69b78668524c9bc19808 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 17:48:21 -0700 Subject: [PATCH 22/33] port udp send path to rust --- ext/node/ops/udp.rs | 118 ++++++++++++++++-- .../polyfills/internal_binding/udp_wrap.ts | 62 ++------- 2 files changed, 117 insertions(+), 63 deletions(-) diff --git a/ext/node/ops/udp.rs b/ext/node/ops/udp.rs index 6c581780ffa36c..6615a16877942d 100644 --- a/ext/node/ops/udp.rs +++ b/ext/node/ops/udp.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use std::cell::Cell; use std::cell::RefCell; +use std::future::Future; use std::net::Ipv4Addr; use std::net::Ipv6Addr; use std::net::SocketAddr; @@ -20,6 +21,7 @@ use deno_core::RcRef; use deno_core::Resource; use deno_core::ResourceId; use deno_core::op2; +use deno_core::serde::Serialize; use deno_core::uv_compat; use deno_core::v8; use deno_permissions::PermissionsContainer; @@ -141,10 +143,13 @@ impl UDP { const UV_UNKNOWN: i32 = -4094; fn io_error_to_uv(err: &std::io::Error) -> i32 { - err - .raw_os_error() - .map(|code| -code) - .unwrap_or(uv_compat::UV_EINVAL) + match err.raw_os_error() { + #[cfg(windows)] + Some(10040) => -4065, + Some(code @ (40 | 90)) => -code, + Some(code) => -code, + None => uv_compat::UV_EINVAL, + } } fn udp_error_to_uv(err: &NodeUdpError) -> i32 { @@ -153,7 +158,8 @@ fn udp_error_to_uv(err: &NodeUdpError) -> i32 { NodeUdpError::Resource(_) => uv_compat::UV_EBADF, NodeUdpError::AddrParse(_) | NodeUdpError::NoResolvedAddress - | NodeUdpError::InvalidHostname(_) => uv_compat::UV_EINVAL, + | NodeUdpError::InvalidHostname(_) + | NodeUdpError::InvalidSendBuffer => uv_compat::UV_EINVAL, NodeUdpError::Canceled(_) => uv_compat::UV_ECANCELED, NodeUdpError::Permission(_) => UV_UNKNOWN, } @@ -786,6 +792,9 @@ pub enum NodeUdpError { #[class(type)] #[error("Invalid hostname: '{0}'")] InvalidHostname(String), + #[class(type)] + #[error("Invalid UDP send buffer")] + InvalidSendBuffer, #[class(inherit)] #[error(transparent)] Permission(#[from] deno_permissions::PermissionCheckError), @@ -1313,14 +1322,62 @@ pub fn op_node_udp_leave_source_specific( ) } -#[op2] -#[smi] -pub async fn op_node_udp_send( +fn array_buffer_view_to_vec(view: v8::Local) -> Vec { + let mut buf = vec![0u8; view.byte_length()]; + let copied = view.copy_contents(&mut buf); + debug_assert_eq!(copied, buf.len()); + buf +} + +fn string_to_utf8( + scope: &mut v8::PinScope, + value: v8::Local, +) -> Vec { + let len = value.utf8_length(scope); + let mut buf = Vec::with_capacity(len); + let written = value.write_utf8_uninit_v2( + scope, + buf.spare_capacity_mut(), + v8::WriteFlags::kReplaceInvalidUtf8, + None, + ); + // SAFETY: write_utf8_uninit_v2 initialized exactly `written` bytes. + unsafe { + buf.set_len(written); + } + buf +} + +fn udp_send_buffers_to_vec( + scope: &mut v8::PinScope, + bufs: v8::Local, + count: u32, +) -> Result, NodeUdpError> { + let count = count.min(bufs.length()); + let mut payload = Vec::new(); + for i in 0..count { + let Some(value) = bufs.get_index(scope, i) else { + continue; + }; + if let Ok(string) = v8::Local::::try_from(value) { + payload.extend_from_slice(&string_to_utf8(scope, string)); + continue; + } + if let Ok(view) = v8::Local::::try_from(value) { + payload.extend_from_slice(&array_buffer_view_to_vec(view)); + continue; + } + return Err(NodeUdpError::InvalidSendBuffer); + } + Ok(payload) +} + +async fn node_udp_send( state: Rc>, - #[smi] rid: ResourceId, - #[buffer] buf: JsBuffer, - #[string] hostname: String, - #[smi] port: u16, + rid: ResourceId, + buf: Vec, + hostname: String, + port: u16, ) -> Result { { state @@ -1355,6 +1412,43 @@ pub async fn op_node_udp_send( Ok(nwritten) } +#[derive(Serialize)] +pub struct SendResult { + pub err: Option, + pub sent: usize, +} + +#[op2] +#[serde] +pub fn op_node_udp_send<'a>( + scope: &mut v8::PinScope<'a, '_>, + state: Rc>, + #[smi] rid: ResourceId, + bufs: v8::Local<'a, v8::Array>, + #[smi] count: u32, + #[string] hostname: String, + #[smi] port: u16, +) -> impl Future + use<> { + let payload = udp_send_buffers_to_vec(scope, bufs, count); + async move { + match payload { + Ok(payload) => { + match node_udp_send(state, rid, payload, hostname, port).await { + Ok(sent) => SendResult { err: None, sent }, + Err(err) => SendResult { + err: Some(udp_error_to_uv(&err)), + sent: 0, + }, + } + } + Err(err) => SendResult { + err: Some(udp_error_to_uv(&err)), + sent: 0, + }, + } + } +} + #[derive(serde::Serialize)] pub struct RecvResult { pub nread: usize, diff --git a/ext/node/polyfills/internal_binding/udp_wrap.ts b/ext/node/polyfills/internal_binding/udp_wrap.ts index 3652ba24a458f0..3a5bf23a9bfe8d 100644 --- a/ext/node/polyfills/internal_binding/udp_wrap.ts +++ b/ext/node/polyfills/internal_binding/udp_wrap.ts @@ -29,16 +29,10 @@ const { UDP: NativeUDP, } = core.ops; const { - ArrayPrototypeMap, - ErrorPrototype, ObjectPrototypeIsPrototypeOf, - SafeRegExp, - StringPrototypeMatch, Uint8Array, } = primordials; -const osErrorRegExp = new SafeRegExp(/os error (40|90|10040)/); - core.loadExtScript("ext:deno_node/internal_binding/handle_wrap.ts"); const { ownerSymbol } = core.loadExtScript( "ext:deno_node/internal_binding/symbols.ts", @@ -220,7 +214,7 @@ class UDP extends NativeUDP { #doSend( req: SendWrapInstance, bufs: MessageType[], - _count: number, + count: number, args: [number, string, boolean] | [boolean], _family: number, ): number { @@ -233,56 +227,22 @@ class UDP extends NativeUDP { hasCallback = args[0] as boolean; } - const payload = new Uint8Array( - // deno-lint-ignore prefer-primordials - Buffer.concat( - ArrayPrototypeMap(bufs, (buf) => { - if (typeof buf === "string") { - return Buffer.from(buf); - } - - // deno-lint-ignore prefer-primordials - return Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength); - }), - ), + const promise = op_node_udp_send( + this._rid(), + bufs, + count, + this._remoteAddress()!, + this._remotePort(), ); - - (async () => { - let sent: number; - let err: number | null = null; - - try { - sent = await op_node_udp_send( - this._rid(), - payload, - this._remoteAddress()!, - this._remotePort(), - ); - } catch (e) { - if ( - ObjectPrototypeIsPrototypeOf(Deno.errors.BadResource.prototype, e) - ) { - err = codeMap.get("EBADF")!; - } else if ( - ObjectPrototypeIsPrototypeOf(ErrorPrototype, e) && - StringPrototypeMatch(e.message, osErrorRegExp) - ) { - err = codeMap.get("EMSGSIZE")!; - } else { - err = codeMap.get("UNKNOWN")!; - } - - sent = 0; - } - - if (hasCallback) { + if (hasCallback) { + promise.then(({ err, sent }) => { try { req.oncomplete(err, sent); } catch { // swallow callback errors } - } - })(); + }); + } return 0; } From d895454e612fb56781e18ed8138b7266ae6abe2a Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 17:53:54 -0700 Subject: [PATCH 23/33] port udp receive result handling to rust --- ext/node/ops/udp.rs | 64 +++++++++++++------ .../polyfills/internal_binding/udp_wrap.ts | 41 +++--------- 2 files changed, 55 insertions(+), 50 deletions(-) diff --git a/ext/node/ops/udp.rs b/ext/node/ops/udp.rs index 6615a16877942d..8861f9f2ab190c 100644 --- a/ext/node/ops/udp.rs +++ b/ext/node/ops/udp.rs @@ -1451,35 +1451,63 @@ pub fn op_node_udp_send<'a>( #[derive(serde::Serialize)] pub struct RecvResult { - pub nread: usize, - pub hostname: String, - pub port: u16, + pub nread: i32, + pub hostname: Option, + pub port: Option, + pub family: Option<&'static str>, } #[op2] #[serde] -pub async fn op_node_udp_recv( +pub fn op_node_udp_recv( state: Rc>, #[smi] rid: ResourceId, #[buffer] mut buf: JsBuffer, -) -> Result { +) -> impl Future + use<> { let resource = state .borrow() .resource_table - .get::(rid)?; - - let cancel = RcRef::map(&resource, |r| &r.cancel); - let (nread, remote_addr) = resource - .socket - .recv_from(&mut buf) - .or_cancel(cancel) - .await??; + .get::(rid); + async move { + let resource = match resource { + Ok(resource) => resource, + Err(_) => { + return RecvResult { + nread: 0, + hostname: None, + port: None, + family: None, + }; + } + }; + let cancel = RcRef::map(&resource, |r| &r.cancel); + let result = resource.socket.recv_from(&mut buf).or_cancel(cancel).await; - Ok(RecvResult { - nread, - hostname: remote_addr.ip().to_string(), - port: remote_addr.port(), - }) + match result { + Ok(Ok((nread, remote_addr))) => RecvResult { + nread: nread as i32, + hostname: Some(remote_addr.ip().to_string()), + port: Some(remote_addr.port()), + family: Some(if remote_addr.is_ipv6() { + "IPv6" + } else { + "IPv4" + }), + }, + Ok(Err(_)) => RecvResult { + nread: UV_UNKNOWN, + hostname: None, + port: None, + family: None, + }, + Err(_) => RecvResult { + nread: 0, + hostname: None, + port: None, + family: None, + }, + } + } } /// Return an owned dup of the bound UDP socket's file descriptor, for use as diff --git a/ext/node/polyfills/internal_binding/udp_wrap.ts b/ext/node/polyfills/internal_binding/udp_wrap.ts index 3a5bf23a9bfe8d..13a0c7441847ac 100644 --- a/ext/node/polyfills/internal_binding/udp_wrap.ts +++ b/ext/node/polyfills/internal_binding/udp_wrap.ts @@ -29,7 +29,6 @@ const { UDP: NativeUDP, } = core.ops; const { - ObjectPrototypeIsPrototypeOf, Uint8Array, } = primordials; @@ -37,9 +36,7 @@ core.loadExtScript("ext:deno_node/internal_binding/handle_wrap.ts"); const { ownerSymbol } = core.loadExtScript( "ext:deno_node/internal_binding/symbols.ts", ); -const { codeMap } = core.loadExtScript("ext:deno_node/internal_binding/uv.ts"); const { Buffer } = core.loadExtScript("ext:deno_node/internal/buffer.mjs"); -const { isIP } = core.loadExtScript("ext:deno_node/internal/net.ts"); type MessageType = string | Uint8Array | Buffer | DataView; type SendWrapInstance = InstanceType; @@ -254,41 +251,21 @@ class UDP extends NativeUDP { const p = new Uint8Array(this._recvBufferSize()); - let nread: number; - let remoteHostname: string | null = null; - let remotePort: number | null = null; - - try { - const promise = op_node_udp_recv(this._rid(), p); - if (this.#unrefed) { - core.unrefOpPromise(promise); - } - const result = await promise; - nread = result.nread; - remoteHostname = result.hostname; - remotePort = result.port; - } catch (e) { - if ( - ObjectPrototypeIsPrototypeOf(Deno.errors.Interrupted.prototype, e) || - ObjectPrototypeIsPrototypeOf(Deno.errors.BadResource.prototype, e) - ) { - nread = 0; - } else { - nread = codeMap.get("UNKNOWN")!; - } + const promise = op_node_udp_recv(this._rid(), p); + if (this.#unrefed) { + core.unrefOpPromise(promise); } + const { nread, hostname, port, family } = await promise; - const rinfo = remoteHostname !== null + const rinfo = hostname !== null ? { - address: remoteHostname, - port: remotePort!, - family: isIP(remoteHostname) === 6 - ? ("IPv6" as const) - : ("IPv4" as const), + address: hostname, + port: port!, + family: family!, } : undefined; - const buf = remoteHostname !== null + const buf = hostname !== null // deno-lint-ignore prefer-primordials ? Buffer.from(p.buffer, p.byteOffset, nread) : Buffer.alloc(0); From 365d2f27c4d7533e00dcf8f8009b26dfb85947e1 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 17:56:42 -0700 Subject: [PATCH 24/33] port dns reverse lookup name generation to rust --- ext/node/lib.rs | 1 + ext/node/ops/dns.rs | 44 ++++++++++ .../polyfills/internal_binding/cares_wrap.ts | 85 ++++--------------- 3 files changed, 61 insertions(+), 69 deletions(-) diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 7000b18e3d3e51..9b684a04f5a981 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -218,6 +218,7 @@ deno_core::extension!(deno_node, ops::buffer::op_node_encoding_slice, ops::dns::op_node_getaddrinfo, ops::dns::op_node_getnameinfo, + ops::dns::op_node_dns_reverse_name, ops::fs::op_node_fs_exists_sync, ops::fs::op_node_fs_exists, ops::fs::op_node_lchmod_sync, diff --git a/ext/node/ops/dns.rs b/ext/node/ops/dns.rs index f4f831d6ad2be5..45b599327af10f 100644 --- a/ext/node/ops/dns.rs +++ b/ext/node/ops/dns.rs @@ -12,12 +12,16 @@ use deno_core::CppgcInherits; use deno_core::GarbageCollected; use deno_core::OpState; use deno_core::op2; +use deno_core::serde::Serialize; use deno_core::unsync::spawn_blocking; +use deno_core::uv_compat; use deno_error::JsError; use deno_net::ops::NetPermToken; use deno_permissions::PermissionCheckError; use deno_permissions::PermissionsContainer; use socket2::SockAddr; +use std::net::Ipv4Addr; +use std::net::Ipv6Addr; use crate::ops::handle_wrap::AsyncWrap; use crate::ops::handle_wrap::ProviderType; @@ -154,6 +158,46 @@ pub enum DnsError { UnsupportedPlatform, } +#[derive(Serialize)] +pub struct ReverseNameResult { + pub code: i32, + pub name: Option, +} + +#[op2] +#[serde] +pub fn op_node_dns_reverse_name(#[string] name: &str) -> ReverseNameResult { + if let Ok(addr) = name.parse::() { + let octets = addr.octets(); + return ReverseNameResult { + code: 0, + name: Some(format!( + "{}.{}.{}.{}.in-addr.arpa", + octets[3], octets[2], octets[1], octets[0] + )), + }; + } + + if let Ok(addr) = name.parse::() { + let hex = format!("{:032x}", u128::from(addr)); + let mut reverse = String::with_capacity("ip6.arpa".len() + 64); + for ch in hex.chars().rev() { + reverse.push(ch); + reverse.push('.'); + } + reverse.push_str("ip6.arpa"); + return ReverseNameResult { + code: 0, + name: Some(reverse), + }; + } + + ReverseNameResult { + code: uv_compat::UV_EINVAL, + name: None, + } +} + impl DnsError { fn code(&self) -> i32 { match self { diff --git a/ext/node/polyfills/internal_binding/cares_wrap.ts b/ext/node/polyfills/internal_binding/cares_wrap.ts index fe876c6164d119..179cdc47b0cc93 100644 --- a/ext/node/polyfills/internal_binding/cares_wrap.ts +++ b/ext/node/polyfills/internal_binding/cares_wrap.ts @@ -28,17 +28,13 @@ const { core, primordials } = __bootstrap; const { ArrayPrototypeFilter, - ArrayPrototypeJoin, ArrayPrototypeMap, ArrayPrototypePush, - ArrayPrototypeReverse, ArrayPrototypeSort, Error, MathMin, MathPow, - Number, NumberParseInt, - NumberPrototypeToString, ObjectPrototypeIsPrototypeOf, PromisePrototypeThen, SafeArrayIterator, @@ -49,15 +45,13 @@ const { SetPrototypeClear, SetPrototypeDelete, SetPrototypeHas, - StringPrototypeIncludes, - StringPrototypePadStart, StringPrototypeReplace, - StringPrototypeSplit, } = primordials; const { op_dns_resolve, op_net_get_ips_from_perm_token, op_net_get_system_dns_servers, + op_node_dns_reverse_name, op_node_getaddrinfo, op_node_getnameinfo, GetAddrInfoReqWrap, @@ -757,74 +751,27 @@ class ChannelWrap extends NativeChannelWrap implements ChannelWrapQuery { } getHostByAddr(req: QueryReqWrapInstance, name: string): number { - let reverseName: string; - - if (isIPv4(name)) { - const octets = StringPrototypeSplit(name, "."); - reverseName = ArrayPrototypeJoin(ArrayPrototypeReverse(octets), ".") + - ".in-addr.arpa"; - } else if (isIPv6(name)) { - // Expand the IPv6 address to full form - const parts = StringPrototypeSplit(name, ":"); - const expanded: string[] = []; - let emptyFound = false; - for (const part of new SafeArrayIterator(parts)) { - if (part === "" && !emptyFound) { - emptyFound = true; - const missing = 8 - - ArrayPrototypeFilter(parts, (p) => p !== "").length; - for (let j = 0; j < missing; j++) { - ArrayPrototypePush(expanded, "0000"); - } - } else if (part !== "" && StringPrototypeIncludes(part, ".")) { - // IPv4-mapped IPv6 (e.g. ::ffff:1.2.3.4) - convert dotted - // quad to two 16-bit hex groups - const octets = ArrayPrototypeMap( - StringPrototypeSplit(part, "."), - Number, - ); - ArrayPrototypePush( - expanded, - StringPrototypePadStart( - NumberPrototypeToString((octets[0] << 8) | octets[1], 16), - 4, - "0", - ), - ); - ArrayPrototypePush( - expanded, - StringPrototypePadStart( - NumberPrototypeToString((octets[2] << 8) | octets[3], 16), - 4, - "0", - ), - ); - } else if (part !== "") { - ArrayPrototypePush(expanded, StringPrototypePadStart(part, 4, "0")); - } - } - const fullHex = ArrayPrototypeJoin(expanded, ""); - reverseName = ArrayPrototypeJoin( - ArrayPrototypeReverse(StringPrototypeSplit(fullHex, "")), - ".", - ) + ".ip6.arpa"; - } else { - req.oncomplete(codeMap.get("EINVAL")!, []); + const { code, name: reverseName } = op_node_dns_reverse_name(name); + if (code !== 0) { + req.oncomplete(code, []); return 0; } SetPrototypeAdd(this.#pendingQueries, req); - PromisePrototypeThen(this.#query(reverseName, "PTR"), ({ code, ret }) => { - if (!SetPrototypeHas(this.#pendingQueries, req)) return; - SetPrototypeDelete(this.#pendingQueries, req); + PromisePrototypeThen( + this.#query(reverseName!, "PTR"), + ({ code, ret }) => { + if (!SetPrototypeHas(this.#pendingQueries, req)) return; + SetPrototypeDelete(this.#pendingQueries, req); - const records = ArrayPrototypeMap( - ret as string[], - (record) => fqdnToHostname(record), - ); - req.oncomplete(code, records); - }); + const records = ArrayPrototypeMap( + ret as string[], + (record) => fqdnToHostname(record), + ); + req.oncomplete(code, records); + }, + ); return 0; } From e00266c422499250f0ba56b9fb67c3c1727f359f Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Wed, 10 Jun 2026 17:58:34 -0700 Subject: [PATCH 25/33] port dns lookup address ordering to rust --- ext/node/lib.rs | 1 + ext/node/ops/dns.rs | 30 ++++++++++++++++ .../polyfills/internal_binding/cares_wrap.ts | 34 ++----------------- 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 9b684a04f5a981..c0ef4c21fbb3b5 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -219,6 +219,7 @@ deno_core::extension!(deno_node, ops::dns::op_node_getaddrinfo, ops::dns::op_node_getnameinfo, ops::dns::op_node_dns_reverse_name, + ops::dns::op_node_dns_sort_lookup_addresses, ops::fs::op_node_fs_exists_sync, ops::fs::op_node_fs_exists, ops::fs::op_node_lchmod_sync, diff --git a/ext/node/ops/dns.rs b/ext/node/ops/dns.rs index 45b599327af10f..494e5ebbdf377f 100644 --- a/ext/node/ops/dns.rs +++ b/ext/node/ops/dns.rs @@ -198,6 +198,36 @@ pub fn op_node_dns_reverse_name(#[string] name: &str) -> ReverseNameResult { } } +fn is_ipv4_address(address: &str) -> bool { + matches!(address.parse::(), Ok(IpAddr::V4(_))) +} + +fn is_ipv6_address(address: &str) -> bool { + matches!(address.parse::(), Ok(IpAddr::V6(_))) +} + +#[op2] +#[serde] +pub fn op_node_dns_sort_lookup_addresses( + #[serde] mut addresses: Vec, + #[smi] family: i32, + #[smi] order: i32, +) -> Vec { + match order { + 1 => addresses.sort_by_key(|address| !is_ipv4_address(address)), + 2 => addresses.sort_by_key(|address| !is_ipv6_address(address)), + _ => {} + } + + match family { + 4 => addresses.retain(|address| is_ipv4_address(address)), + 6 => addresses.retain(|address| is_ipv6_address(address)), + _ => {} + } + + addresses +} + impl DnsError { fn code(&self) -> i32 { match self { diff --git a/ext/node/polyfills/internal_binding/cares_wrap.ts b/ext/node/polyfills/internal_binding/cares_wrap.ts index 179cdc47b0cc93..afb05b48f75df8 100644 --- a/ext/node/polyfills/internal_binding/cares_wrap.ts +++ b/ext/node/polyfills/internal_binding/cares_wrap.ts @@ -27,10 +27,8 @@ (function () { const { core, primordials } = __bootstrap; const { - ArrayPrototypeFilter, ArrayPrototypeMap, ArrayPrototypePush, - ArrayPrototypeSort, Error, MathMin, MathPow, @@ -52,6 +50,7 @@ const { op_net_get_ips_from_perm_token, op_net_get_system_dns_servers, op_node_dns_reverse_name, + op_node_dns_sort_lookup_addresses, op_node_getaddrinfo, op_node_getnameinfo, GetAddrInfoReqWrap, @@ -59,9 +58,6 @@ const { QueryReqWrap, ChannelWrap: NativeChannelWrap, } = core.ops; -const { isIPv4, isIPv6 } = core.loadExtScript( - "ext:deno_node/internal/net.ts", -); const { codeMap } = core.loadExtScript( "ext:deno_node/internal_binding/uv.ts", ); @@ -154,33 +150,7 @@ function getaddrinfo( } } - // REF: https://github.com/nodejs/node/blob/0e157b6cd8694424ea9d8a1c1854fd1d08cbb064/src/cares_wrap.cc#L1739 - if (order === DNS_ORDER_IPV4_FIRST) { - ArrayPrototypeSort(addresses, (a: string, b: string): number => { - if (isIPv4(a)) { - return -1; - } else if (isIPv4(b)) { - return 1; - } - - return 0; - }); - } else if (order === DNS_ORDER_IPV6_FIRST) { - ArrayPrototypeSort(addresses, (a: string, b: string): number => { - if (isIPv6(a)) { - return -1; - } else if (isIPv6(b)) { - return 1; - } - return 0; - }); - } - - if (family === 4) { - addresses = ArrayPrototypeFilter(addresses, (addr) => isIPv4(addr)); - } else if (family === 6) { - addresses = ArrayPrototypeFilter(addresses, (addr) => isIPv6(addr)); - } + addresses = op_node_dns_sort_lookup_addresses(addresses, family, order); req.oncomplete(error, addresses, netPermToken); })(); From d798f2e8d1d1ae3218073df5e840c4574a28cd60 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Fri, 12 Jun 2026 13:56:34 -0700 Subject: [PATCH 26/33] perf(node): cache HTTP parser callbacks --- ext/node/ops/llhttp/binding.rs | 52 +++++++++++++++++++++++----------- ext/node/ops/stream_wrap.rs | 13 --------- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/ext/node/ops/llhttp/binding.rs b/ext/node/ops/llhttp/binding.rs index c35cbd08742b89..4210b90f41c501 100644 --- a/ext/node/ops/llhttp/binding.rs +++ b/ext/node/ops/llhttp/binding.rs @@ -424,6 +424,30 @@ fn make_body_callback<'s>( function_with_data(scope, http_parser_on_body_callback, data) } +fn cached_body_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + parser: v8::Local<'s, v8::Object>, + callback: v8::Local<'s, v8::Value>, + buffer: v8::Local<'s, v8::Value>, +) -> v8::Local<'s, v8::Function> { + let cached_original = get_value(scope, parser, "__bodyCallbackOriginal"); + if cached_original + .map(|cached| cached.strict_equals(callback)) + .unwrap_or(false) + { + if let Some(cached_callback) = get_value(scope, parser, "__bodyCallback") + .and_then(|value| v8::Local::::try_from(value).ok()) + { + return cached_callback; + } + } + + let wrapped = make_body_callback(scope, parser, callback, buffer); + set_value(scope, parser, "__bodyCallbackOriginal", callback); + set_value(scope, parser, "__bodyCallback", wrapped.into()); + wrapped +} + fn make_parse_error<'s>( scope: &mut v8::PinScope<'s, '_>, native: v8::Local<'s, v8::Object>, @@ -547,17 +571,11 @@ fn http_parser_do_execute<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'s>, ) { - let Some(parser) = callback_data_value(scope, &args, "parser") - .and_then(|value| v8::Local::::try_from(value).ok()) - else { - return; - }; + let parser = args.this(); let Some(native) = get_native(scope, parser) else { return; }; - let Some(data) = callback_data_value(scope, &args, "data") else { - return; - }; + let data = args.get(0); if let Some(result) = call_method(scope, native, "execute", &[parser.into(), data]) { @@ -601,27 +619,29 @@ fn http_parser_execute<'s>( .unwrap_or_else(|| v8::undefined(scope).into()); if !original_on_body.is_undefined() && !original_on_body.is_null() { let wrapped = - make_body_callback(scope, parser, original_on_body, buffer_ctor); + cached_body_callback(scope, parser, original_on_body, buffer_ctor); parser.set_index(scope, K_ON_BODY, wrapped.into()); } - let exec_data = - data_object(scope, &[("parser", parser.into()), ("data", data)]); - let do_execute = function_with_data(scope, http_parser_do_execute, exec_data); + let do_execute = get_value(scope, parser, "__doExecute") + .and_then(|value| v8::Local::::try_from(value).ok()) + .unwrap_or_else(|| { + let do_execute = function_no_data(scope, http_parser_do_execute); + set_value(scope, parser, "__doExecute", do_execute.into()); + do_execute + }); let result = get_value(scope, parser, "_asyncResource") .and_then(|value| v8::Local::::try_from(value).ok()) .and_then(|resource| { - let undefined = v8::undefined(scope); call_method( scope, resource, "runInAsyncScope", - &[do_execute.into(), undefined.into()], + &[do_execute.into(), parser.into(), data], ) }) .or_else(|| { - let undefined = v8::undefined(scope); - do_execute.call(scope, undefined.into(), &[]) + do_execute.call(scope, parser.into(), &[data]) }); parser.set_index(scope, K_ON_BODY, original_on_body); diff --git a/ext/node/ops/stream_wrap.rs b/ext/node/ops/stream_wrap.rs index 5d80c6bf5a2fe2..c95eb401efd65c 100644 --- a/ext/node/ops/stream_wrap.rs +++ b/ext/node/ops/stream_wrap.rs @@ -31,7 +31,6 @@ use deno_core::v8; use crate::ops::handle_wrap::AsyncWrap; use crate::ops::handle_wrap::GlobalHandle; use crate::ops::handle_wrap::HandleWrap; -use crate::ops::handle_wrap::OwnedPtr; use crate::ops::handle_wrap::ProviderType; use crate::ops::stream_wrap_state::ReadCallbackKey; use crate::ops::stream_wrap_state::ReadCallbackRegistry; @@ -139,7 +138,6 @@ enum StreamBaseStateFields { #[repr(C)] pub struct WriteWrap { base: AsyncWrap, - req: OwnedPtr, } // SAFETY: WriteWrap is a cppgc-managed object; it holds no GC-traced references beyond its base. @@ -155,13 +153,8 @@ impl WriteWrap { pub fn new(op_state: &mut OpState) -> Self { Self { base: AsyncWrap::create(op_state, ProviderType::WriteWrap as i32), - req: OwnedPtr::from_box(Box::new(uv_compat::new_write())), } } - - pub fn req_ptr(&mut self) -> *mut uv_write_t { - self.req.as_mut_ptr() - } } #[op2(base, inherit = AsyncWrap)] @@ -182,7 +175,6 @@ impl WriteWrap { #[repr(C)] pub struct ShutdownWrap { base: AsyncWrap, - req: OwnedPtr, } // SAFETY: ShutdownWrap is a cppgc-managed object; it holds no GC-traced references beyond its base. @@ -198,13 +190,8 @@ impl ShutdownWrap { pub fn new(op_state: &mut OpState) -> Self { Self { base: AsyncWrap::create(op_state, ProviderType::ShutdownWrap as i32), - req: OwnedPtr::from_box(Box::new(uv_compat::new_shutdown())), } } - - pub fn req_ptr(&mut self) -> *mut uv_shutdown_t { - self.req.as_mut_ptr() - } } #[op2(base, inherit = AsyncWrap)] From bbb55e361127a2a5999ec37da8c5abe02f2fb7d9 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Fri, 12 Jun 2026 15:10:25 -0700 Subject: [PATCH 27/33] fix(node): stabilize internal binding callbacks --- ext/node/ops/blocklist.rs | 78 ++++--- ext/node/ops/buffer.rs | 65 +++--- ext/node/ops/dns.rs | 8 +- ext/node/ops/http2/mod.rs | 75 ++++--- ext/node/ops/internal_binding_async_wrap.rs | 19 +- ext/node/ops/internal_binding_crypto.rs | 73 +++--- ext/node/ops/internal_binding_utils.rs | 77 ++++--- ext/node/ops/llhttp/binding.rs | 208 +++++++++++------- ext/node/ops/pipe_wrap.rs | 2 + ext/node/ops/tcp_wrap.rs | 2 + ext/node/ops/udp.rs | 7 +- ext/node/ops/util.rs | 76 ++++--- .../polyfills/internal_binding/udp_wrap.ts | 3 +- 13 files changed, 415 insertions(+), 278 deletions(-) diff --git a/ext/node/ops/blocklist.rs b/ext/node/ops/blocklist.rs index 0a166c75ab24cd..a0b4affdca3d1b 100644 --- a/ext/node/ops/blocklist.rs +++ b/ext/node/ops/blocklist.rs @@ -152,24 +152,48 @@ fn socket_address_flowlabel<'s>( pub(crate) fn internal_binding_external_references() -> [ExternalReference; 5] { [ - ExternalReference { - function: socket_address_constructor.map_fn_to(), - }, - ExternalReference { - function: socket_address_address.map_fn_to(), - }, - ExternalReference { - function: socket_address_port.map_fn_to(), - }, - ExternalReference { - function: socket_address_family.map_fn_to(), - }, - ExternalReference { - function: socket_address_flowlabel.map_fn_to(), - }, + SOCKET_ADDRESS_CONSTRUCTOR.with(|callback| ExternalReference { + function: *callback, + }), + SOCKET_ADDRESS_ADDRESS.with(|callback| ExternalReference { + function: *callback, + }), + SOCKET_ADDRESS_PORT.with(|callback| ExternalReference { + function: *callback, + }), + SOCKET_ADDRESS_FAMILY.with(|callback| ExternalReference { + function: *callback, + }), + SOCKET_ADDRESS_FLOWLABEL.with(|callback| ExternalReference { + function: *callback, + }), ] } +thread_local! { + static SOCKET_ADDRESS_CONSTRUCTOR: v8::FunctionCallback = socket_address_constructor.map_fn_to(); + static SOCKET_ADDRESS_ADDRESS: v8::FunctionCallback = socket_address_address.map_fn_to(); + static SOCKET_ADDRESS_PORT: v8::FunctionCallback = socket_address_port.map_fn_to(); + static SOCKET_ADDRESS_FAMILY: v8::FunctionCallback = socket_address_family.map_fn_to(); + static SOCKET_ADDRESS_FLOWLABEL: v8::FunctionCallback = socket_address_flowlabel.map_fn_to(); +} + +fn function_template_from_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + callback: v8::FunctionCallback, +) -> v8::Local<'s, v8::FunctionTemplate> { + v8::FunctionTemplate::new_raw(scope, callback) +} + +fn function_from_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + callback: v8::FunctionCallback, +) -> v8::Local<'s, v8::Function> { + function_template_from_callback(scope, callback) + .get_function(scope) + .unwrap() +} + #[op2] pub fn op_node_internal_binding_block_list<'s>( scope: &mut v8::PinScope<'s, '_>, @@ -178,8 +202,8 @@ pub fn op_node_internal_binding_block_list<'s>( set_i32(scope, obj, "AF_INET", 2); set_i32(scope, obj, "AF_INET6", 10); - let constructor_template = - v8::FunctionTemplate::new(scope, socket_address_constructor); + let constructor_template = SOCKET_ADDRESS_CONSTRUCTOR + .with(|callback| function_template_from_callback(scope, *callback)); let constructor = constructor_template.get_function(scope).unwrap(); let constructor_name = v8::String::new(scope, "SocketAddress").unwrap(); constructor.set_name(constructor_name); @@ -187,21 +211,17 @@ pub fn op_node_internal_binding_block_list<'s>( let prototype = constructor.get(scope, prototype_key.into()).unwrap(); let prototype = v8::Local::::try_from(prototype).unwrap(); - let address = v8::FunctionTemplate::new(scope, socket_address_address) - .get_function(scope) - .unwrap(); + let address = SOCKET_ADDRESS_ADDRESS + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, prototype, "address", address); - let port = v8::FunctionTemplate::new(scope, socket_address_port) - .get_function(scope) - .unwrap(); + let port = SOCKET_ADDRESS_PORT + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, prototype, "port", port); - let family = v8::FunctionTemplate::new(scope, socket_address_family) - .get_function(scope) - .unwrap(); + let family = SOCKET_ADDRESS_FAMILY + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, prototype, "family", family); - let flowlabel = v8::FunctionTemplate::new(scope, socket_address_flowlabel) - .get_function(scope) - .unwrap(); + let flowlabel = SOCKET_ADDRESS_FLOWLABEL + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, prototype, "flowlabel", flowlabel); set_value(scope, obj, "SocketAddress", constructor.into()); diff --git a/ext/node/ops/buffer.rs b/ext/node/ops/buffer.rs index 6c8e078ce2fcc5..20e72c3f654bdd 100644 --- a/ext/node/ops/buffer.rs +++ b/ext/node/ops/buffer.rs @@ -609,7 +609,7 @@ fn index_of_buffer_from_bytes( } if !forward { if byte_offset < 0 { - byte_offset = target_len + byte_offset; + byte_offset += target_len; } if needle.is_empty() { return if byte_offset <= target_len { @@ -759,44 +759,53 @@ fn fill_callback( pub(crate) fn external_references() -> [ExternalReference; 4] { [ - ExternalReference { - function: index_of_buffer_callback.map_fn_to(), - }, - ExternalReference { - function: index_of_number_callback.map_fn_to(), - }, - ExternalReference { - function: index_of_needle_callback.map_fn_to(), - }, - ExternalReference { - function: fill_callback.map_fn_to(), - }, + INDEX_OF_BUFFER_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), + INDEX_OF_NUMBER_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), + INDEX_OF_NEEDLE_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), + FILL_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), ] } +thread_local! { + static INDEX_OF_BUFFER_CALLBACK: v8::FunctionCallback = index_of_buffer_callback.map_fn_to(); + static INDEX_OF_NUMBER_CALLBACK: v8::FunctionCallback = index_of_number_callback.map_fn_to(); + static INDEX_OF_NEEDLE_CALLBACK: v8::FunctionCallback = index_of_needle_callback.map_fn_to(); + static FILL_CALLBACK: v8::FunctionCallback = fill_callback.map_fn_to(); +} + +fn function_from_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + callback: v8::FunctionCallback, +) -> v8::Local<'s, v8::Function> { + v8::FunctionTemplate::new_raw(scope, callback) + .get_function(scope) + .unwrap() +} + #[op2] pub fn op_node_internal_binding_buffer<'s>( scope: &mut v8::PinScope<'s, '_>, ) -> v8::Local<'s, v8::Object> { let obj = v8::Object::new(scope); - let index_of_buffer = - v8::FunctionTemplate::new(scope, index_of_buffer_callback) - .get_function(scope) - .unwrap(); + let index_of_buffer = INDEX_OF_BUFFER_CALLBACK + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, obj, "indexOfBuffer", index_of_buffer); - let index_of_number = - v8::FunctionTemplate::new(scope, index_of_number_callback) - .get_function(scope) - .unwrap(); + let index_of_number = INDEX_OF_NUMBER_CALLBACK + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, obj, "indexOfNumber", index_of_number); - let fill = v8::FunctionTemplate::new(scope, fill_callback) - .get_function(scope) - .unwrap(); + let fill = + FILL_CALLBACK.with(|callback| function_from_callback(scope, *callback)); set_function(scope, obj, "fill", fill); - let index_of_needle = - v8::FunctionTemplate::new(scope, index_of_needle_callback) - .get_function(scope) - .unwrap(); + let index_of_needle = INDEX_OF_NEEDLE_CALLBACK + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, obj, "indexOfNeedle", index_of_needle); let default_obj = v8::Object::new(scope); diff --git a/ext/node/ops/dns.rs b/ext/node/ops/dns.rs index 494e5ebbdf377f..391451698df50b 100644 --- a/ext/node/ops/dns.rs +++ b/ext/node/ops/dns.rs @@ -2,6 +2,8 @@ use std::cell::RefCell; use std::net::IpAddr; +use std::net::Ipv4Addr; +use std::net::Ipv6Addr; use std::net::SocketAddr; use std::rc::Rc; #[cfg(target_family = "windows")] @@ -20,8 +22,6 @@ use deno_net::ops::NetPermToken; use deno_permissions::PermissionCheckError; use deno_permissions::PermissionsContainer; use socket2::SockAddr; -use std::net::Ipv4Addr; -use std::net::Ipv6Addr; use crate::ops::handle_wrap::AsyncWrap; use crate::ops::handle_wrap::ProviderType; @@ -33,6 +33,7 @@ pub struct GetAddrInfoReqWrap { base: AsyncWrap, } +// SAFETY: GetAddrInfoReqWrap is a CppGC object whose fields are traced by AsyncWrap. unsafe impl GarbageCollected for GetAddrInfoReqWrap { fn get_name(&self) -> &'static std::ffi::CStr { c"GetAddrInfoReqWrap" @@ -59,6 +60,7 @@ pub struct GetNameInfoReqWrap { base: AsyncWrap, } +// SAFETY: GetNameInfoReqWrap is a CppGC object whose fields are traced by AsyncWrap. unsafe impl GarbageCollected for GetNameInfoReqWrap { fn get_name(&self) -> &'static std::ffi::CStr { c"GetNameInfoReqWrap" @@ -85,6 +87,7 @@ pub struct QueryReqWrap { base: AsyncWrap, } +// SAFETY: QueryReqWrap is a CppGC object whose fields are traced by AsyncWrap. unsafe impl GarbageCollected for QueryReqWrap { fn get_name(&self) -> &'static std::ffi::CStr { c"QueryReqWrap" @@ -111,6 +114,7 @@ pub struct ChannelWrap { base: AsyncWrap, } +// SAFETY: ChannelWrap is a CppGC object whose fields are traced by AsyncWrap. unsafe impl GarbageCollected for ChannelWrap { fn get_name(&self) -> &'static std::ffi::CStr { c"ChannelWrap" diff --git a/ext/node/ops/http2/mod.rs b/ext/node/ops/http2/mod.rs index c75a4c958a4967..3ad245577a8064 100644 --- a/ext/node/ops/http2/mod.rs +++ b/ext/node/ops/http2/mod.rs @@ -8,7 +8,6 @@ use deno_core::op2; use deno_core::v8; use deno_core::v8::ExternalReference; use deno_core::v8::MapFnTo; - pub use session::Http2Session; pub use session::op_http2_callbacks; pub use session::op_http2_error_string; @@ -97,31 +96,48 @@ fn http2_session_request_callback( fn create_constructor<'s>( scope: &mut v8::PinScope<'s, '_>, name: &str, - constructor: impl MapFnTo, + constructor: v8::FunctionCallback, ) -> v8::Local<'s, v8::Function> { - let template = v8::FunctionTemplate::new(scope, constructor); + let template = v8::FunctionTemplate::new_raw(scope, constructor); let class_name = v8::String::new(scope, name).unwrap(); template.set_class_name(class_name); template.get_function(scope).unwrap() } +fn function_from_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + callback: v8::FunctionCallback, +) -> v8::Local<'s, v8::Function> { + v8::FunctionTemplate::new_raw(scope, callback) + .get_function(scope) + .unwrap() +} + +thread_local! { + static HTTP2_STREAM_CONSTRUCTOR_CALLBACK: v8::FunctionCallback = http2_stream_constructor_callback.map_fn_to(); + static HTTP2_SESSION_CONSTRUCTOR_CALLBACK: v8::FunctionCallback = http2_session_constructor_callback.map_fn_to(); + static HTTP2_STREAM_RESPOND_CALLBACK: v8::FunctionCallback = http2_stream_respond_callback.map_fn_to(); + static HTTP2_STREAM_PUSH_PROMISE_CALLBACK: v8::FunctionCallback = http2_stream_push_promise_callback.map_fn_to(); + static HTTP2_SESSION_REQUEST_CALLBACK: v8::FunctionCallback = http2_session_request_callback.map_fn_to(); +} + pub(crate) fn internal_binding_external_references() -> [ExternalReference; 5] { [ - ExternalReference { - function: http2_stream_constructor_callback.map_fn_to(), - }, - ExternalReference { - function: http2_session_constructor_callback.map_fn_to(), - }, - ExternalReference { - function: http2_stream_respond_callback.map_fn_to(), - }, - ExternalReference { - function: http2_stream_push_promise_callback.map_fn_to(), - }, - ExternalReference { - function: http2_session_request_callback.map_fn_to(), - }, + HTTP2_STREAM_CONSTRUCTOR_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), + HTTP2_SESSION_CONSTRUCTOR_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), + HTTP2_STREAM_RESPOND_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), + HTTP2_STREAM_PUSH_PROMISE_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), + HTTP2_SESSION_REQUEST_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), ] } @@ -131,36 +147,31 @@ pub fn op_node_internal_binding_http2<'s>( constants: v8::Local<'s, v8::Value>, nghttp2_error_string: v8::Local<'s, v8::Value>, ) -> v8::Local<'s, v8::Object> { - let http2_stream = - create_constructor(scope, "Http2Stream", http2_stream_constructor_callback); + let http2_stream = HTTP2_STREAM_CONSTRUCTOR_CALLBACK + .with(|callback| create_constructor(scope, "Http2Stream", *callback)); let prototype_key = v8::String::new(scope, "prototype").unwrap(); let prototype = http2_stream .get(scope, prototype_key.into()) .and_then(|value| v8::Local::::try_from(value).ok()) .unwrap(); - let respond = v8::FunctionTemplate::new(scope, http2_stream_respond_callback) - .get_function(scope) - .unwrap(); + let respond = HTTP2_STREAM_RESPOND_CALLBACK + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, prototype, "respond", respond); - let push_promise = - v8::FunctionTemplate::new(scope, http2_stream_push_promise_callback) - .get_function(scope) - .unwrap(); + let push_promise = HTTP2_STREAM_PUSH_PROMISE_CALLBACK + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, prototype, "pushPromise", push_promise); let http2_session = create_constructor( scope, "Http2Session", - http2_session_constructor_callback, + HTTP2_SESSION_CONSTRUCTOR_CALLBACK.with(|callback| *callback), ); let prototype = http2_session .get(scope, prototype_key.into()) .and_then(|value| v8::Local::::try_from(value).ok()) .unwrap(); - let request = - v8::FunctionTemplate::new(scope, http2_session_request_callback) - .get_function(scope) - .unwrap(); + let request = HTTP2_SESSION_REQUEST_CALLBACK + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, prototype, "request", request); let default = v8::Object::new(scope); diff --git a/ext/node/ops/internal_binding_async_wrap.rs b/ext/node/ops/internal_binding_async_wrap.rs index d4acebd5daae13..afcf5f9025675d 100644 --- a/ext/node/ops/internal_binding_async_wrap.rs +++ b/ext/node/ops/internal_binding_async_wrap.rs @@ -133,9 +133,15 @@ fn register_destroy_hook_callback( } pub(crate) fn external_references() -> [ExternalReference; 1] { - [ExternalReference { - function: register_destroy_hook_callback.map_fn_to(), - }] + [ + REGISTER_DESTROY_HOOK_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), + ] +} + +thread_local! { + static REGISTER_DESTROY_HOOK_CALLBACK: v8::FunctionCallback = register_destroy_hook_callback.map_fn_to(); } #[op2] @@ -156,10 +162,11 @@ pub fn op_node_internal_binding_async_wrap<'s>( set_value(scope, obj, "AsyncWrap", async_wrap); - let register_destroy_hook = - v8::FunctionTemplate::new(scope, register_destroy_hook_callback) + let register_destroy_hook = REGISTER_DESTROY_HOOK_CALLBACK.with(|callback| { + v8::FunctionTemplate::new_raw(scope, *callback) .get_function(scope) - .unwrap(); + .unwrap() + }); set_value( scope, obj, diff --git a/ext/node/ops/internal_binding_crypto.rs b/ext/node/ops/internal_binding_crypto.rs index eefbc69dcb50b0..5c48ea587a0884 100644 --- a/ext/node/ops/internal_binding_crypto.rs +++ b/ext/node/ops/internal_binding_crypto.rs @@ -9,6 +9,7 @@ use deno_core::v8::MapFnTo; enum CryptoBindingError { #[error("{0}")] #[class(type)] + #[property("code" = "ERR_INVALID_ARG_TYPE")] InvalidArgType(String), #[error("Input buffers must have the same byte length")] #[class(range)] @@ -168,16 +169,7 @@ fn timing_safe_equal_impl( } fn throw_crypto_error(scope: &mut v8::PinScope, error: CryptoBindingError) { - let message = v8::String::new(scope, &error.to_string()).unwrap(); - let exception = match error { - CryptoBindingError::InvalidArgType(_) => { - v8::Exception::type_error(scope, message) - } - CryptoBindingError::TimingSafeEqualLength => { - v8::Exception::range_error(scope, message) - } - CryptoBindingError::FipsUnsupported => v8::Exception::error(scope, message), - }; + let exception = deno_core::error::to_v8_error(scope, &error); scope.throw_exception(exception); } @@ -210,17 +202,32 @@ fn set_fips_callback( throw_crypto_error(scope, CryptoBindingError::FipsUnsupported); } +thread_local! { + static TIMING_SAFE_EQUAL_CALLBACK: v8::FunctionCallback = timing_safe_equal_callback.map_fn_to(); + static GET_FIPS_CALLBACK: v8::FunctionCallback = get_fips_callback.map_fn_to(); + static SET_FIPS_CALLBACK: v8::FunctionCallback = set_fips_callback.map_fn_to(); +} + +fn function_from_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + callback: v8::FunctionCallback, +) -> v8::Local<'s, v8::Function> { + v8::FunctionTemplate::new_raw(scope, callback) + .get_function(scope) + .unwrap() +} + pub(crate) fn external_references() -> [ExternalReference; 3] { [ - ExternalReference { - function: timing_safe_equal_callback.map_fn_to(), - }, - ExternalReference { - function: get_fips_callback.map_fn_to(), - }, - ExternalReference { - function: set_fips_callback.map_fn_to(), - }, + TIMING_SAFE_EQUAL_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), + GET_FIPS_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), + SET_FIPS_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), ] } @@ -248,18 +255,14 @@ pub fn op_node_internal_binding_crypto<'s>( scope: &mut v8::PinScope<'s, '_>, ) -> v8::Local<'s, v8::Object> { let obj = v8::Object::new(scope); - let timing_safe_equal = - v8::FunctionTemplate::new(scope, timing_safe_equal_callback) - .get_function(scope) - .unwrap(); + let timing_safe_equal = TIMING_SAFE_EQUAL_CALLBACK + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, obj, "timingSafeEqual", timing_safe_equal); - let get_fips = v8::FunctionTemplate::new(scope, get_fips_callback) - .get_function(scope) - .unwrap(); + let get_fips = + GET_FIPS_CALLBACK.with(|callback| function_from_callback(scope, *callback)); set_function(scope, obj, "getFipsCrypto", get_fips); - let set_fips = v8::FunctionTemplate::new(scope, set_fips_callback) - .get_function(scope) - .unwrap(); + let set_fips = + SET_FIPS_CALLBACK.with(|callback| function_from_callback(scope, *callback)); set_function(scope, obj, "setFipsCrypto", set_fips); obj } @@ -269,16 +272,12 @@ pub fn op_node_internal_binding_timing_safe_equal<'s>( scope: &mut v8::PinScope<'s, '_>, ) -> v8::Local<'s, v8::Object> { let obj = v8::Object::new(scope); - let timing_safe_equal = - v8::FunctionTemplate::new(scope, timing_safe_equal_callback) - .get_function(scope) - .unwrap(); + let timing_safe_equal = TIMING_SAFE_EQUAL_CALLBACK + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, obj, "timingSafeEqual", timing_safe_equal); let default_obj = v8::Object::new(scope); - let timing_safe_equal = - v8::FunctionTemplate::new(scope, timing_safe_equal_callback) - .get_function(scope) - .unwrap(); + let timing_safe_equal = TIMING_SAFE_EQUAL_CALLBACK + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, default_obj, "timingSafeEqual", timing_safe_equal); set_value(scope, obj, "default", default_obj.into()); obj diff --git a/ext/node/ops/internal_binding_utils.rs b/ext/node/ops/internal_binding_utils.rs index 36232427d45ab8..53eb7f85c95101 100644 --- a/ext/node/ops/internal_binding_utils.rs +++ b/ext/node/ops/internal_binding_utils.rs @@ -86,6 +86,7 @@ fn simdutf_base64_decode(input: &[u8]) -> Option> { let max_len = simdutf::maximal_binary_length_from_base64(input); let mut output = vec![0; max_len]; + // SAFETY: `output` is allocated to simdutf's reported maximum decoded length. let result = unsafe { simdutf::base64_to_binary( input, @@ -241,57 +242,65 @@ fn utf16le_to_bytes_callback( pub(crate) fn external_references() -> [ExternalReference; 5] { [ - ExternalReference { - function: ascii_to_bytes_callback.map_fn_to(), - }, - ExternalReference { - function: base64_to_bytes_callback.map_fn_to(), - }, - ExternalReference { - function: base64_url_to_bytes_callback.map_fn_to(), - }, - ExternalReference { - function: hex_to_bytes_callback.map_fn_to(), - }, - ExternalReference { - function: utf16le_to_bytes_callback.map_fn_to(), - }, + ASCII_TO_BYTES_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), + BASE64_TO_BYTES_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), + BASE64_URL_TO_BYTES_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), + HEX_TO_BYTES_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), + UTF16LE_TO_BYTES_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), ] } +thread_local! { + static ASCII_TO_BYTES_CALLBACK: v8::FunctionCallback = ascii_to_bytes_callback.map_fn_to(); + static BASE64_TO_BYTES_CALLBACK: v8::FunctionCallback = base64_to_bytes_callback.map_fn_to(); + static BASE64_URL_TO_BYTES_CALLBACK: v8::FunctionCallback = base64_url_to_bytes_callback.map_fn_to(); + static HEX_TO_BYTES_CALLBACK: v8::FunctionCallback = hex_to_bytes_callback.map_fn_to(); + static UTF16LE_TO_BYTES_CALLBACK: v8::FunctionCallback = utf16le_to_bytes_callback.map_fn_to(); +} + +fn function_from_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + callback: v8::FunctionCallback, +) -> v8::Local<'s, v8::Function> { + v8::FunctionTemplate::new_raw(scope, callback) + .get_function(scope) + .unwrap() +} + #[op2] pub fn op_node_internal_binding_utils<'s>( scope: &mut v8::PinScope<'s, '_>, ) -> v8::Local<'s, v8::Object> { let obj = v8::Object::new(scope); - let ascii_to_bytes = - v8::FunctionTemplate::new(scope, ascii_to_bytes_callback) - .get_function(scope) - .unwrap(); + let ascii_to_bytes = ASCII_TO_BYTES_CALLBACK + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, obj, "asciiToBytes", ascii_to_bytes); - let base64_to_bytes = - v8::FunctionTemplate::new(scope, base64_to_bytes_callback) - .get_function(scope) - .unwrap(); + let base64_to_bytes = BASE64_TO_BYTES_CALLBACK + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, obj, "base64ToBytes", base64_to_bytes); - let base64_url_to_bytes = - v8::FunctionTemplate::new(scope, base64_url_to_bytes_callback) - .get_function(scope) - .unwrap(); + let base64_url_to_bytes = BASE64_URL_TO_BYTES_CALLBACK + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, obj, "base64UrlToBytes", base64_url_to_bytes); - let hex_to_bytes = v8::FunctionTemplate::new(scope, hex_to_bytes_callback) - .get_function(scope) - .unwrap(); + let hex_to_bytes = HEX_TO_BYTES_CALLBACK + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, obj, "hexToBytes", hex_to_bytes); - let utf16le_to_bytes = - v8::FunctionTemplate::new(scope, utf16le_to_bytes_callback) - .get_function(scope) - .unwrap(); + let utf16le_to_bytes = UTF16LE_TO_BYTES_CALLBACK + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, obj, "utf16leToBytes", utf16le_to_bytes); let table = UNHEX_TABLE diff --git a/ext/node/ops/llhttp/binding.rs b/ext/node/ops/llhttp/binding.rs index 4210b90f41c501..84ff00b7e16ebf 100644 --- a/ext/node/ops/llhttp/binding.rs +++ b/ext/node/ops/llhttp/binding.rs @@ -328,12 +328,29 @@ fn callback_data_value<'s>( get_value(scope, data, name) } +thread_local! { + static HTTP_PARSER_CONSTRUCTOR: v8::FunctionCallback = http_parser_constructor.map_fn_to(); + static HTTP_PARSER_INITIALIZE: v8::FunctionCallback = http_parser_initialize.map_fn_to(); + static HTTP_PARSER_EXECUTE: v8::FunctionCallback = http_parser_execute.map_fn_to(); + static HTTP_PARSER_DO_EXECUTE: v8::FunctionCallback = http_parser_do_execute.map_fn_to(); + static HTTP_PARSER_ON_BODY_CALLBACK: v8::FunctionCallback = http_parser_on_body_callback.map_fn_to(); + static HTTP_PARSER_FINISH: v8::FunctionCallback = http_parser_finish.map_fn_to(); + static HTTP_PARSER_PAUSE: v8::FunctionCallback = http_parser_pause.map_fn_to(); + static HTTP_PARSER_RESUME: v8::FunctionCallback = http_parser_resume.map_fn_to(); + static HTTP_PARSER_CLOSE: v8::FunctionCallback = http_parser_close.map_fn_to(); + static HTTP_PARSER_FREE: v8::FunctionCallback = http_parser_free.map_fn_to(); + static HTTP_PARSER_REMOVE: v8::FunctionCallback = http_parser_remove.map_fn_to(); + static HTTP_PARSER_GET_CURRENT_BUFFER: v8::FunctionCallback = http_parser_get_current_buffer.map_fn_to(); + static HTTP_PARSER_CONSUME: v8::FunctionCallback = http_parser_consume.map_fn_to(); + static HTTP_PARSER_UNCONSUME: v8::FunctionCallback = http_parser_unconsume.map_fn_to(); +} + fn function_with_data<'s>( scope: &mut v8::PinScope<'s, '_>, - callback: impl MapFnTo, + callback: v8::FunctionCallback, data: v8::Local<'s, v8::Object>, ) -> v8::Local<'s, v8::Function> { - v8::Function::builder(callback) + v8::Function::builder_raw(callback) .data(data.into()) .build(scope) .unwrap() @@ -341,9 +358,9 @@ fn function_with_data<'s>( fn function_no_data<'s>( scope: &mut v8::PinScope<'s, '_>, - callback: impl MapFnTo, + callback: v8::FunctionCallback, ) -> v8::Local<'s, v8::Function> { - v8::Function::builder(callback).build(scope).unwrap() + v8::Function::builder_raw(callback).build(scope).unwrap() } fn parser_constructor_object<'s>( @@ -421,7 +438,8 @@ fn make_body_callback<'s>( ("Buffer", buffer), ], ); - function_with_data(scope, http_parser_on_body_callback, data) + HTTP_PARSER_ON_BODY_CALLBACK + .with(|callback| function_with_data(scope, *callback, data)) } fn cached_body_callback<'s>( @@ -434,12 +452,10 @@ fn cached_body_callback<'s>( if cached_original .map(|cached| cached.strict_equals(callback)) .unwrap_or(false) - { - if let Some(cached_callback) = get_value(scope, parser, "__bodyCallback") + && let Some(cached_callback) = get_value(scope, parser, "__bodyCallback") .and_then(|value| v8::Local::::try_from(value).ok()) - { - return cached_callback; - } + { + return cached_callback; } let wrapped = make_body_callback(scope, parser, callback, buffer); @@ -603,14 +619,13 @@ fn http_parser_execute<'s>( { let offset = args.get(1).uint32_value(scope).unwrap_or(0) as usize; let length = args.get(2).uint32_value(scope).unwrap_or(0) as usize; - if offset != 0 + if (offset != 0 || v8::Local::::try_from(data) .map(|view| view.byte_length() != length) - .unwrap_or(false) + .unwrap_or(false)) + && let Some(view) = create_uint8_array_view(scope, data, offset, length) { - if let Some(view) = create_uint8_array_view(scope, data, offset, length) { - data = view.into(); - } + data = view.into(); } } @@ -626,7 +641,8 @@ fn http_parser_execute<'s>( let do_execute = get_value(scope, parser, "__doExecute") .and_then(|value| v8::Local::::try_from(value).ok()) .unwrap_or_else(|| { - let do_execute = function_no_data(scope, http_parser_do_execute); + let do_execute = HTTP_PARSER_DO_EXECUTE + .with(|callback| function_no_data(scope, *callback)); set_value(scope, parser, "__doExecute", do_execute.into()); do_execute }); @@ -640,9 +656,7 @@ fn http_parser_execute<'s>( &[do_execute.into(), parser.into(), data], ) }) - .or_else(|| { - do_execute.call(scope, parser.into(), &[data]) - }); + .or_else(|| do_execute.call(scope, parser.into(), &[data])); parser.set_index(scope, K_ON_BODY, original_on_body); @@ -824,7 +838,7 @@ fn set_prototype_function<'s>( scope: &mut v8::PinScope<'s, '_>, prototype: v8::Local<'s, v8::Object>, name: &str, - callback: impl MapFnTo, + callback: v8::FunctionCallback, ) { let function = function_no_data(scope, callback); set_value(scope, prototype, name, function.into()); @@ -833,48 +847,48 @@ fn set_prototype_function<'s>( pub(crate) fn internal_binding_external_references() -> [ExternalReference; 14] { [ - ExternalReference { - function: http_parser_constructor.map_fn_to(), - }, - ExternalReference { - function: http_parser_initialize.map_fn_to(), - }, - ExternalReference { - function: http_parser_execute.map_fn_to(), - }, - ExternalReference { - function: http_parser_do_execute.map_fn_to(), - }, - ExternalReference { - function: http_parser_on_body_callback.map_fn_to(), - }, - ExternalReference { - function: http_parser_finish.map_fn_to(), - }, - ExternalReference { - function: http_parser_pause.map_fn_to(), - }, - ExternalReference { - function: http_parser_resume.map_fn_to(), - }, - ExternalReference { - function: http_parser_close.map_fn_to(), - }, - ExternalReference { - function: http_parser_free.map_fn_to(), - }, - ExternalReference { - function: http_parser_remove.map_fn_to(), - }, - ExternalReference { - function: http_parser_get_current_buffer.map_fn_to(), - }, - ExternalReference { - function: http_parser_consume.map_fn_to(), - }, - ExternalReference { - function: http_parser_unconsume.map_fn_to(), - }, + HTTP_PARSER_CONSTRUCTOR.with(|callback| ExternalReference { + function: *callback, + }), + HTTP_PARSER_INITIALIZE.with(|callback| ExternalReference { + function: *callback, + }), + HTTP_PARSER_EXECUTE.with(|callback| ExternalReference { + function: *callback, + }), + HTTP_PARSER_DO_EXECUTE.with(|callback| ExternalReference { + function: *callback, + }), + HTTP_PARSER_ON_BODY_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), + HTTP_PARSER_FINISH.with(|callback| ExternalReference { + function: *callback, + }), + HTTP_PARSER_PAUSE.with(|callback| ExternalReference { + function: *callback, + }), + HTTP_PARSER_RESUME.with(|callback| ExternalReference { + function: *callback, + }), + HTTP_PARSER_CLOSE.with(|callback| ExternalReference { + function: *callback, + }), + HTTP_PARSER_FREE.with(|callback| ExternalReference { + function: *callback, + }), + HTTP_PARSER_REMOVE.with(|callback| ExternalReference { + function: *callback, + }), + HTTP_PARSER_GET_CURRENT_BUFFER.with(|callback| ExternalReference { + function: *callback, + }), + HTTP_PARSER_CONSUME.with(|callback| ExternalReference { + function: *callback, + }), + HTTP_PARSER_UNCONSUME.with(|callback| ExternalReference { + function: *callback, + }), ] } @@ -885,7 +899,8 @@ pub fn op_node_internal_binding_http_parser<'s>( buffer: v8::Local<'s, v8::Value>, async_resource: v8::Local<'s, v8::Value>, ) -> v8::Local<'s, v8::Object> { - let constructor = function_no_data(scope, http_parser_constructor); + let constructor = + HTTP_PARSER_CONSTRUCTOR.with(|callback| function_no_data(scope, *callback)); let name = string(scope, "HTTPParser"); constructor.set_name(name); @@ -899,23 +914,68 @@ pub fn op_node_internal_binding_http_parser<'s>( scope, prototype, "initialize", - http_parser_initialize, + HTTP_PARSER_INITIALIZE.with(|callback| *callback), + ); + set_prototype_function( + scope, + prototype, + "execute", + HTTP_PARSER_EXECUTE.with(|callback| *callback), + ); + set_prototype_function( + scope, + prototype, + "finish", + HTTP_PARSER_FINISH.with(|callback| *callback), + ); + set_prototype_function( + scope, + prototype, + "pause", + HTTP_PARSER_PAUSE.with(|callback| *callback), + ); + set_prototype_function( + scope, + prototype, + "resume", + HTTP_PARSER_RESUME.with(|callback| *callback), + ); + set_prototype_function( + scope, + prototype, + "close", + HTTP_PARSER_CLOSE.with(|callback| *callback), + ); + set_prototype_function( + scope, + prototype, + "free", + HTTP_PARSER_FREE.with(|callback| *callback), + ); + set_prototype_function( + scope, + prototype, + "remove", + HTTP_PARSER_REMOVE.with(|callback| *callback), ); - set_prototype_function(scope, prototype, "execute", http_parser_execute); - set_prototype_function(scope, prototype, "finish", http_parser_finish); - set_prototype_function(scope, prototype, "pause", http_parser_pause); - set_prototype_function(scope, prototype, "resume", http_parser_resume); - set_prototype_function(scope, prototype, "close", http_parser_close); - set_prototype_function(scope, prototype, "free", http_parser_free); - set_prototype_function(scope, prototype, "remove", http_parser_remove); set_prototype_function( scope, prototype, "getCurrentBuffer", - http_parser_get_current_buffer, + HTTP_PARSER_GET_CURRENT_BUFFER.with(|callback| *callback), + ); + set_prototype_function( + scope, + prototype, + "consume", + HTTP_PARSER_CONSUME.with(|callback| *callback), + ); + set_prototype_function( + scope, + prototype, + "unconsume", + HTTP_PARSER_UNCONSUME.with(|callback| *callback), ); - set_prototype_function(scope, prototype, "consume", http_parser_consume); - set_prototype_function(scope, prototype, "unconsume", http_parser_unconsume); let constructor_obj: v8::Local = constructor.into(); set_value( diff --git a/ext/node/ops/pipe_wrap.rs b/ext/node/ops/pipe_wrap.rs index a3869920019542..4fe9fe8639efe9 100644 --- a/ext/node/ops/pipe_wrap.rs +++ b/ext/node/ops/pipe_wrap.rs @@ -49,6 +49,7 @@ pub struct PipeConnectWrap { base: AsyncWrap, } +// SAFETY: PipeConnectWrap is a CppGC object whose fields are traced by AsyncWrap. unsafe impl GarbageCollected for PipeConnectWrap { fn get_name(&self) -> &'static std::ffi::CStr { c"PipeConnectWrap" @@ -205,6 +206,7 @@ fn new_pipe_client<'s>( let global = v8::Global::new(scope, obj); let pipe = deno_core::cppgc::try_unwrap_cppgc_object::(scope, obj.into())?; + // SAFETY: `obj` was just created from this PipeWrap and remains live in the scope. let pipe = unsafe { pipe.as_ref() }; pipe.base.set_js_handle(global, scope); Some(obj) diff --git a/ext/node/ops/tcp_wrap.rs b/ext/node/ops/tcp_wrap.rs index d8d946dc2dc957..6c16169b8cb5d2 100644 --- a/ext/node/ops/tcp_wrap.rs +++ b/ext/node/ops/tcp_wrap.rs @@ -49,6 +49,7 @@ pub struct TCPConnectWrap { base: AsyncWrap, } +// SAFETY: TCPConnectWrap is a CppGC object whose fields are traced by AsyncWrap. unsafe impl GarbageCollected for TCPConnectWrap { fn get_name(&self) -> &'static std::ffi::CStr { c"TCPConnectWrap" @@ -202,6 +203,7 @@ fn new_tcp_client<'s>( let global = v8::Global::new(scope, obj); let tcp = deno_core::cppgc::try_unwrap_cppgc_object::(scope, obj.into())?; + // SAFETY: `obj` was just created from this TCPWrap and remains live in the scope. let tcp = unsafe { tcp.as_ref() }; tcp.base.set_js_handle(global, scope); Some(obj) diff --git a/ext/node/ops/udp.rs b/ext/node/ops/udp.rs index 8861f9f2ab190c..c7cd1ad356bc0e 100644 --- a/ext/node/ops/udp.rs +++ b/ext/node/ops/udp.rs @@ -42,6 +42,7 @@ pub struct SendWrap { base: AsyncWrap, } +// SAFETY: SendWrap is a CppGC object whose fields are traced by AsyncWrap. unsafe impl GarbageCollected for SendWrap { fn get_name(&self) -> &'static std::ffi::CStr { c"SendWrap" @@ -112,6 +113,7 @@ impl UdpFamily { } } +// SAFETY: UDP owns no V8 handles directly; all JS-visible state is held by HandleWrap. unsafe impl GarbageCollected for UDP { fn get_name(&self) -> &'static std::ffi::CStr { c"UDP" @@ -766,7 +768,10 @@ impl UDP { self.remote_family.set(None); self.remote_port.set(None); if let Some(rid) = self.rid.take() { - #[allow(deprecated)] + #[allow( + deprecated, + reason = "ResourceTable::close is used for sync close" + )] let _ = state.resource_table.close(rid); } } diff --git a/ext/node/ops/util.rs b/ext/node/ops/util.rs index e7911ec2532701..5e76719670a7a4 100644 --- a/ext/node/ops/util.rs +++ b/ext/node/ops/util.rs @@ -412,63 +412,71 @@ fn parse_env_callback( pub(crate) fn external_references() -> [ExternalReference; 5] { [ - ExternalReference { - function: guess_handle_type_callback.map_fn_to(), - }, - ExternalReference { - function: is_array_index_callback.map_fn_to(), - }, - ExternalReference { - function: get_own_non_index_properties_callback.map_fn_to(), - }, - ExternalReference { - function: array_buffer_view_has_buffer_callback.map_fn_to(), - }, - ExternalReference { - function: parse_env_callback.map_fn_to(), - }, + GUESS_HANDLE_TYPE_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), + IS_ARRAY_INDEX_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), + GET_OWN_NON_INDEX_PROPERTIES_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), + ARRAY_BUFFER_VIEW_HAS_BUFFER_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), + PARSE_ENV_CALLBACK.with(|callback| ExternalReference { + function: *callback, + }), ] } +thread_local! { + static GUESS_HANDLE_TYPE_CALLBACK: v8::FunctionCallback = guess_handle_type_callback.map_fn_to(); + static IS_ARRAY_INDEX_CALLBACK: v8::FunctionCallback = is_array_index_callback.map_fn_to(); + static GET_OWN_NON_INDEX_PROPERTIES_CALLBACK: v8::FunctionCallback = get_own_non_index_properties_callback.map_fn_to(); + static ARRAY_BUFFER_VIEW_HAS_BUFFER_CALLBACK: v8::FunctionCallback = array_buffer_view_has_buffer_callback.map_fn_to(); + static PARSE_ENV_CALLBACK: v8::FunctionCallback = parse_env_callback.map_fn_to(); +} + +fn function_from_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + callback: v8::FunctionCallback, +) -> v8::Local<'s, v8::Function> { + v8::FunctionTemplate::new_raw(scope, callback) + .get_function(scope) + .unwrap() +} + #[op2] pub fn op_node_internal_binding_util<'s>( scope: &mut v8::PinScope<'s, '_>, ) -> v8::Local<'s, v8::Object> { let obj = v8::Object::new(scope); - let guess_handle_type = - v8::FunctionTemplate::new(scope, guess_handle_type_callback) - .get_function(scope) - .unwrap(); + let guess_handle_type = GUESS_HANDLE_TYPE_CALLBACK + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, obj, "guessHandleType", guess_handle_type); - let is_array_index = - v8::FunctionTemplate::new(scope, is_array_index_callback) - .get_function(scope) - .unwrap(); + let is_array_index = IS_ARRAY_INDEX_CALLBACK + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, obj, "isArrayIndex", is_array_index); - let get_own_non_index_properties = - v8::FunctionTemplate::new(scope, get_own_non_index_properties_callback) - .get_function(scope) - .unwrap(); + let get_own_non_index_properties = GET_OWN_NON_INDEX_PROPERTIES_CALLBACK + .with(|callback| function_from_callback(scope, *callback)); set_function( scope, obj, "getOwnNonIndexProperties", get_own_non_index_properties, ); - let array_buffer_view_has_buffer = - v8::FunctionTemplate::new(scope, array_buffer_view_has_buffer_callback) - .get_function(scope) - .unwrap(); + let array_buffer_view_has_buffer = ARRAY_BUFFER_VIEW_HAS_BUFFER_CALLBACK + .with(|callback| function_from_callback(scope, *callback)); set_function( scope, obj, "arrayBufferViewHasBuffer", array_buffer_view_has_buffer, ); - let parse_env = v8::FunctionTemplate::new(scope, parse_env_callback) - .get_function(scope) - .unwrap(); + let parse_env = PARSE_ENV_CALLBACK + .with(|callback| function_from_callback(scope, *callback)); set_function(scope, obj, "parseEnv", parse_env); for (name, value) in [ diff --git a/ext/node/polyfills/internal_binding/udp_wrap.ts b/ext/node/polyfills/internal_binding/udp_wrap.ts index 13a0c7441847ac..cabf788703e8db 100644 --- a/ext/node/polyfills/internal_binding/udp_wrap.ts +++ b/ext/node/polyfills/internal_binding/udp_wrap.ts @@ -29,6 +29,7 @@ const { UDP: NativeUDP, } = core.ops; const { + PromisePrototypeThen, Uint8Array, } = primordials; @@ -232,7 +233,7 @@ class UDP extends NativeUDP { this._remotePort(), ); if (hasCallback) { - promise.then(({ err, sent }) => { + PromisePrototypeThen(promise, ({ err, sent }) => { try { req.oncomplete(err, sent); } catch { From 670e6aad765ff396d1f08653595d99a2bfe88436 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Fri, 12 Jun 2026 15:50:42 -0700 Subject: [PATCH 28/33] chore(node): update polyfill lint baseline --- tools/lint_plugins/no_deno_api_in_polyfills.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/lint_plugins/no_deno_api_in_polyfills.ts b/tools/lint_plugins/no_deno_api_in_polyfills.ts index f85ae2b8edec66..66369b3d48eeb3 100644 --- a/tools/lint_plugins/no_deno_api_in_polyfills.ts +++ b/tools/lint_plugins/no_deno_api_in_polyfills.ts @@ -21,7 +21,6 @@ export const EXPECTED_VIOLATIONS: Record = { "ext/node/polyfills/internal/process/report.ts": 6, "ext/node/polyfills/path/_win32.ts": 5, "ext/node/polyfills/_process/process.ts": 5, - "ext/node/polyfills/internal_binding/udp_wrap.ts": 4, "ext/node/polyfills/_process/streams.mjs": 4, "ext/node/polyfills/internal/errors.ts": 3, "ext/node/polyfills/_fs/_fs_lstat.ts": 4, @@ -36,7 +35,6 @@ export const EXPECTED_VIOLATIONS: Record = { "ext/node/polyfills/internal/util/colors.ts": 1, "ext/node/polyfills/internal/options.ts": 1, "ext/node/polyfills/internal/assert/assertion_error.js": 1, - "ext/node/polyfills/internal_binding/node_options.ts": 1, "ext/node/polyfills/_fs/cp/cp.ts": 1, "ext/node/polyfills/_fs/_fs_lutimes.ts": 1, }; From ee0ae2990bd72a1abb1e3e5e33faba3723648a53 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Fri, 12 Jun 2026 16:19:26 -0700 Subject: [PATCH 29/33] fix(node): address internal binding CI regressions --- ext/node/ops/internal_binding_node_options.rs | 7 ++- ext/node/ops/llhttp/binding.rs | 8 ++- ext/node/ops/udp.rs | 63 ++++++++++++++----- ext/node/polyfills/dns.ts | 7 ++- ext/node/polyfills/internal/dns/promises.ts | 7 ++- ext/node/polyfills/internal/dns/utils.ts | 5 +- .../polyfills/internal_binding/cares_wrap.ts | 3 +- 7 files changed, 77 insertions(+), 23 deletions(-) diff --git a/ext/node/ops/internal_binding_node_options.rs b/ext/node/ops/internal_binding_node_options.rs index a937ba4f491bf4..1f800607092fae 100644 --- a/ext/node/ops/internal_binding_node_options.rs +++ b/ext/node/ops/internal_binding_node_options.rs @@ -260,8 +260,11 @@ pub fn op_node_options_get_options<'s, TSys: ExtNodeSys + 'static>( let options = create_default_options(scope); let node_options = { - let sys = state.borrow::(); - sys.env_var("NODE_OPTIONS").ok() + if let Some(sys) = state.try_borrow::() { + sys.env_var("NODE_OPTIONS").ok() + } else { + std::env::var("NODE_OPTIONS").ok() + } }; if let Some(node_options) = node_options { for arg in split_node_options(&node_options) { diff --git a/ext/node/ops/llhttp/binding.rs b/ext/node/ops/llhttp/binding.rs index 84ff00b7e16ebf..033d4eff126f52 100644 --- a/ext/node/ops/llhttp/binding.rs +++ b/ext/node/ops/llhttp/binding.rs @@ -589,6 +589,9 @@ fn http_parser_do_execute<'s>( ) { let parser = args.this(); let Some(native) = get_native(scope, parser) else { + let message = string(scope, "Illegal invocation"); + let exception = v8::Exception::type_error(scope, message); + scope.throw_exception(exception); return; }; let data = args.get(0); @@ -606,6 +609,9 @@ fn http_parser_execute<'s>( ) { let parser = args.this(); let Some(native) = get_native(scope, parser) else { + let message = string(scope, "Illegal invocation"); + let exception = v8::Exception::type_error(scope, message); + scope.throw_exception(exception); return; }; let Some(buffer_ctor) = parser_constructor_value(scope, parser, "_Buffer") @@ -1800,7 +1806,7 @@ impl HTTPParser { inner.max_header_size = if max_header_size > 0 { max_header_size as u32 } else { - 0 + 16_384 }; inner.init(parser_type, lenient_flags); } diff --git a/ext/node/ops/udp.rs b/ext/node/ops/udp.rs index c7cd1ad356bc0e..c4f24a0d33b655 100644 --- a/ext/node/ops/udp.rs +++ b/ext/node/ops/udp.rs @@ -461,6 +461,11 @@ impl UDP { #[string] multicast_address: &str, #[string] interface_address: Option, ) -> i32 { + let ipv4_addr = Ipv4Addr::from_str(multicast_address).ok(); + let ipv6_addr = Ipv6Addr::from_str(multicast_address).ok(); + if ipv4_addr.is_none() && ipv6_addr.is_none() { + return uv_compat::UV_EINVAL; + } let Some(rid) = self.rid.get() else { return uv_compat::UV_EBADF; }; @@ -470,7 +475,7 @@ impl UDP { .get::(rid) .map_err(NodeUdpError::from) .and_then(|resource| { - let addr = Ipv6Addr::from_str(multicast_address)?; + let addr = ipv6_addr.ok_or_else(invalid_input)?; let iface = resolve_ipv6_interface(interface_address.as_deref())?; resource.socket.join_multicast_v6(&addr, iface)?; Ok(()) @@ -481,7 +486,7 @@ impl UDP { .get::(rid) .map_err(NodeUdpError::from) .and_then(|resource| { - let addr = Ipv4Addr::from_str(multicast_address)?; + let addr = ipv4_addr.ok_or_else(invalid_input)?; let iface = interface_address .as_deref() .map(Ipv4Addr::from_str) @@ -504,6 +509,11 @@ impl UDP { #[string] multicast_address: &str, #[string] interface_address: Option, ) -> i32 { + let ipv4_addr = Ipv4Addr::from_str(multicast_address).ok(); + let ipv6_addr = Ipv6Addr::from_str(multicast_address).ok(); + if ipv4_addr.is_none() && ipv6_addr.is_none() { + return uv_compat::UV_EINVAL; + } let Some(rid) = self.rid.get() else { return uv_compat::UV_EBADF; }; @@ -513,7 +523,7 @@ impl UDP { .get::(rid) .map_err(NodeUdpError::from) .and_then(|resource| { - let addr = Ipv6Addr::from_str(multicast_address)?; + let addr = ipv6_addr.ok_or_else(invalid_input)?; let iface = resolve_ipv6_interface(interface_address.as_deref())?; resource.socket.leave_multicast_v6(&addr, iface)?; Ok(()) @@ -524,7 +534,7 @@ impl UDP { .get::(rid) .map_err(NodeUdpError::from) .and_then(|resource| { - let addr = Ipv4Addr::from_str(multicast_address)?; + let addr = ipv4_addr.ok_or_else(invalid_input)?; let iface = interface_address .as_deref() .map(Ipv4Addr::from_str) @@ -548,6 +558,17 @@ impl UDP { #[string] group_address: &str, #[string] interface_address: Option, ) -> i32 { + let Ok(source_addr) = Ipv4Addr::from_str(source_address) else { + return uv_compat::UV_EINVAL; + }; + let Ok(group_addr) = Ipv4Addr::from_str(group_address) else { + return uv_compat::UV_EINVAL; + }; + let Ok(interface_addr) = + Ipv4Addr::from_str(interface_address.as_deref().unwrap_or("0.0.0.0")) + else { + return uv_compat::UV_EINVAL; + }; let Some(rid) = self.rid.get() else { return uv_compat::UV_EBADF; }; @@ -558,11 +579,9 @@ impl UDP { .and_then(|resource| { source_specific_multicast( &resource.socket, - Ipv4Addr::from_str(source_address)?, - Ipv4Addr::from_str(group_address)?, - Ipv4Addr::from_str( - interface_address.as_deref().unwrap_or("0.0.0.0"), - )?, + source_addr, + group_addr, + interface_addr, { #[cfg(unix)] { @@ -589,6 +608,17 @@ impl UDP { #[string] group_address: &str, #[string] interface_address: Option, ) -> i32 { + let Ok(source_addr) = Ipv4Addr::from_str(source_address) else { + return uv_compat::UV_EINVAL; + }; + let Ok(group_addr) = Ipv4Addr::from_str(group_address) else { + return uv_compat::UV_EINVAL; + }; + let Ok(interface_addr) = + Ipv4Addr::from_str(interface_address.as_deref().unwrap_or("0.0.0.0")) + else { + return uv_compat::UV_EINVAL; + }; let Some(rid) = self.rid.get() else { return uv_compat::UV_EBADF; }; @@ -599,11 +629,9 @@ impl UDP { .and_then(|resource| { source_specific_multicast( &resource.socket, - Ipv4Addr::from_str(source_address)?, - Ipv4Addr::from_str(group_address)?, - Ipv4Addr::from_str( - interface_address.as_deref().unwrap_or("0.0.0.0"), - )?, + source_addr, + group_addr, + interface_addr, { #[cfg(unix)] { @@ -805,6 +833,13 @@ pub enum NodeUdpError { Permission(#[from] deno_permissions::PermissionCheckError), } +fn invalid_input() -> NodeUdpError { + NodeUdpError::Io(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "invalid address", + )) +} + pub struct NodeUdpSocketResource { pub socket: UdpSocket, pub cancel: CancelHandle, diff --git a/ext/node/polyfills/dns.ts b/ext/node/polyfills/dns.ts index 993804986b3a0e..7fd2ea8b22b9df 100644 --- a/ext/node/polyfills/dns.ts +++ b/ext/node/polyfills/dns.ts @@ -22,6 +22,9 @@ (function () { const { core, primordials } = __bootstrap; +const lazyBindingMod = core.createLazyLoader( + "ext:deno_node/internal_binding/mod.ts", +); const { nextTick } = core.loadExtScript("ext:deno_node/_next_tick.ts"); const { customPromisifyArgs } = core.loadExtScript( "ext:deno_node/internal/util.mjs", @@ -66,12 +69,12 @@ const { AI_ALL: ALL, AI_V4MAPPED: V4MAPPED, } = core.loadExtScript("ext:deno_node/internal_binding/ares.ts"); +const cares = lazyBindingMod().getBinding("cares_wrap"); const { - default: cares, GetAddrInfoReqWrap, GetNameInfoReqWrap, QueryReqWrap, -} = core.loadExtScript("ext:deno_node/internal_binding/cares_wrap.ts"); +} = cares; const { domainToASCII } = core.loadExtScript( "ext:deno_node/internal/idna.ts", ); diff --git a/ext/node/polyfills/internal/dns/promises.ts b/ext/node/polyfills/internal/dns/promises.ts index a3b75bc40d1765..ce376a71113716 100644 --- a/ext/node/polyfills/internal/dns/promises.ts +++ b/ext/node/polyfills/internal/dns/promises.ts @@ -22,6 +22,9 @@ (function () { const { core, primordials } = __bootstrap; +const lazyBindingMod = core.createLazyLoader( + "ext:deno_node/internal_binding/mod.ts", +); const { ArrayPrototypeMap, FunctionPrototypeBind, @@ -56,12 +59,12 @@ const { ERR_MISSING_ARGS, handleDnsError, } = core.loadExtScript("ext:deno_node/internal/errors.ts"); +const cares = lazyBindingMod().getBinding("cares_wrap"); const { - default: cares, GetAddrInfoReqWrap, GetNameInfoReqWrap, QueryReqWrap, -} = core.loadExtScript("ext:deno_node/internal_binding/cares_wrap.ts"); +} = cares; const { domainToASCII } = core.loadExtScript( "ext:deno_node/internal/idna.ts", ); diff --git a/ext/node/polyfills/internal/dns/utils.ts b/ext/node/polyfills/internal/dns/utils.ts index d9bc35711ee638..6c7cc1a4dd20ff 100644 --- a/ext/node/polyfills/internal/dns/utils.ts +++ b/ext/node/polyfills/internal/dns/utils.ts @@ -22,6 +22,9 @@ (function () { const { core, primordials } = __bootstrap; +const lazyBindingMod = core.createLazyLoader( + "ext:deno_node/internal_binding/mod.ts", +); const { ArrayPrototypeForEach, ArrayPrototypeJoin, @@ -47,7 +50,7 @@ const { DNS_ORDER_IPV6_FIRST, DNS_ORDER_VERBATIM, strerror, -} = core.loadExtScript("ext:deno_node/internal_binding/cares_wrap.ts"); +} = lazyBindingMod().getBinding("cares_wrap"); const { ERR_DNS_SET_SERVERS_FAILED, ERR_INVALID_ARG_VALUE, diff --git a/ext/node/polyfills/internal_binding/cares_wrap.ts b/ext/node/polyfills/internal_binding/cares_wrap.ts index afb05b48f75df8..9cbf86d40c8d9b 100644 --- a/ext/node/polyfills/internal_binding/cares_wrap.ts +++ b/ext/node/polyfills/internal_binding/cares_wrap.ts @@ -802,7 +802,7 @@ function strerror(code: number) { : ares_strerror(code); } -return { +const binding = { DNS_ORDER_VERBATIM, DNS_ORDER_IPV4_FIRST, DNS_ORDER_IPV6_FIRST, @@ -825,4 +825,5 @@ return { strerror, }, }; +return binding; })(); From d327a91ccd30fe6fa29ff7c78f8b8a39a17795d4 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Fri, 12 Jun 2026 16:38:05 -0700 Subject: [PATCH 30/33] fix(node): validate buffer fill range --- ext/node/ops/buffer.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/ext/node/ops/buffer.rs b/ext/node/ops/buffer.rs index 20e72c3f654bdd..5a7443d9a07d6e 100644 --- a/ext/node/ops/buffer.rs +++ b/ext/node/ops/buffer.rs @@ -715,16 +715,22 @@ fn fill_callback( let buffer = v8::Local::::try_from(buffer_value).unwrap(); let len = buffer.byte_length(); - let mut start = args.get(2).integer_value(scope).unwrap_or(0); - let mut end = args.get(3).integer_value(scope).unwrap_or(len as i64); - if start < 0 { - start = (len as i64 + start).max(0); - } - if end < 0 { - end = (len as i64 + end).max(0); - } - let start = (start as usize).min(len); - let end = (end as usize).min(len); + let start = match parse_array_index(scope, args.get(2), 0) { + Ok(start) => start.min(len), + Err(err) => { + let exception = deno_core::error::to_v8_error(scope, &err); + scope.throw_exception(exception); + return; + } + }; + let end = match parse_array_index(scope, args.get(3), len) { + Ok(end) => end.min(len), + Err(err) => { + let exception = deno_core::error::to_v8_error(scope, &err); + scope.throw_exception(exception); + return; + } + }; if end <= start { rv.set(buffer_value); return; From 1fee1db069b5a7aa616ac246819d2ee52405d696 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Fri, 12 Jun 2026 22:51:37 -0700 Subject: [PATCH 31/33] fix(node): map windows udp EINVAL --- ext/node/ops/udp.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/node/ops/udp.rs b/ext/node/ops/udp.rs index c4f24a0d33b655..210bbce11311e5 100644 --- a/ext/node/ops/udp.rs +++ b/ext/node/ops/udp.rs @@ -146,6 +146,8 @@ const UV_UNKNOWN: i32 = -4094; fn io_error_to_uv(err: &std::io::Error) -> i32 { match err.raw_os_error() { + #[cfg(windows)] + Some(code) if code == libc::EINVAL || code == 10022 => uv_compat::UV_EINVAL, #[cfg(windows)] Some(10040) => -4065, Some(code @ (40 | 90)) => -code, From 82eff55707dd5301a86fd91780282008e17ad364 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Fri, 12 Jun 2026 23:03:11 -0700 Subject: [PATCH 32/33] fix(node): map windows udp address errors --- ext/node/ops/udp.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/node/ops/udp.rs b/ext/node/ops/udp.rs index 210bbce11311e5..1e3bc84f37f58d 100644 --- a/ext/node/ops/udp.rs +++ b/ext/node/ops/udp.rs @@ -149,6 +149,8 @@ fn io_error_to_uv(err: &std::io::Error) -> i32 { #[cfg(windows)] Some(code) if code == libc::EINVAL || code == 10022 => uv_compat::UV_EINVAL, #[cfg(windows)] + Some(10049) => uv_compat::UV_EADDRNOTAVAIL, + #[cfg(windows)] Some(10040) => -4065, Some(code @ (40 | 90)) => -code, Some(code) => -code, From cff0ee3a1bd988458a0474852007c22d7f52ce53 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Fri, 12 Jun 2026 23:11:41 -0700 Subject: [PATCH 33/33] fix(node): use windows udp address uv code --- ext/node/ops/udp.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/node/ops/udp.rs b/ext/node/ops/udp.rs index 1e3bc84f37f58d..045ada2fce2ad0 100644 --- a/ext/node/ops/udp.rs +++ b/ext/node/ops/udp.rs @@ -143,13 +143,15 @@ impl UDP { } const UV_UNKNOWN: i32 = -4094; +#[cfg(windows)] +const UV_EADDRNOTAVAIL: i32 = -4090; fn io_error_to_uv(err: &std::io::Error) -> i32 { match err.raw_os_error() { #[cfg(windows)] Some(code) if code == libc::EINVAL || code == 10022 => uv_compat::UV_EINVAL, #[cfg(windows)] - Some(10049) => uv_compat::UV_EADDRNOTAVAIL, + Some(10049) => UV_EADDRNOTAVAIL, #[cfg(windows)] Some(10040) => -4065, Some(code @ (40 | 90)) => -code,