Skip to content

Commit 3ec37cc

Browse files
bartlomiejuNathan Whitakerclaude
authored
refactor(ext/node): wire up native TLSWrap from JS (#33184)
Replace the `kStreamBaseField`-based TLS implementation with a native `TLSWrap` cppgc object backed by rustls. This eliminates the async stream-swap pattern where TLS upgrades worked by swapping `handle[kStreamBaseField]` from a `TcpConn` to a `TlsConn`. Key changes: - Rewrite `_tls_wrap.js`: `TLSSocket` uses `tls_wrap.wrap()` to create a Rust-backed TLS interceptor between JS and the underlying TCP stream - TLS server rewritten to use `net.Server` (matching Node.js) instead of `Deno.listenTls()` directly - Add `JSStreamSocket` for TLS over arbitrary Duplex streams - Certificate errors now use Node-style codes (`UNABLE_TO_VERIFY_LEAF_SIGNATURE`) instead of Deno-style (`InvalidData: invalid peer certificate: UnknownIssuer`) - Explicit `ca` in SecureContext takes precedence over global `setDefaultCACertificates()`, matching Node.js behavior - Strip trailing dot from servername before passing to rustls - Remove all `Deno.*` API usage from `_tls_wrap.js` Based on #32819 by @nathanwhit, adapted for incremental landing. --------- Co-authored-by: Nathan Whitaker <user@example.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b8ec861 commit 3ec37cc

12 files changed

Lines changed: 1516 additions & 643 deletions

File tree

ext/node/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ deno_core::extension!(deno_node,
444444
"internal_binding/string_decoder.ts",
445445
"internal_binding/symbols.ts",
446446
"internal_binding/tcp_wrap.ts",
447+
"internal_binding/tls_wrap.ts",
447448
"internal_binding/tty_wrap.ts",
448449
"internal_binding/types.ts",
449450
"internal_binding/udp_wrap.ts",
@@ -497,6 +498,7 @@ deno_core::extension!(deno_node,
497498
"internal/http2/util.ts",
498499
"internal/http2/compat.js",
499500
"internal/idna.ts",
501+
"internal/js_stream_socket.js",
500502
"internal/mime.ts",
501503
"internal/net.ts",
502504
"internal/normalize_encoding.ts",
@@ -527,6 +529,7 @@ deno_core::extension!(deno_node,
527529
"internal/streams/state.js",
528530
"internal/streams/utils.js",
529531
"internal/test/binding.ts",
532+
"internal/tls_common.js",
530533
"internal/timers.mjs",
531534
"internal/tty.js",
532535
"internal/url.ts",

ext/node/ops/stream_wrap.rs

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -293,11 +293,10 @@ impl LibUvStreamWrap {
293293
// `StreamHandleData` allocation while the native stream is alive.
294294
let handle_data = unsafe { handle_data_ptr.as_ref() };
295295
handle_data.desired_read_interceptor.set(interceptor);
296-
if let Some(key) = handle_data.active_read.get() {
297-
let _ = handle_data
298-
.read_callbacks
299-
.borrow_mut()
300-
.update_interceptor(key, interceptor);
296+
if let Some(key) = handle_data.active_read.get()
297+
&& let Ok(mut callbacks) = handle_data.read_callbacks.try_borrow_mut()
298+
{
299+
let _ = callbacks.update_interceptor(key, interceptor);
301300
}
302301
}
303302

@@ -315,18 +314,22 @@ impl LibUvStreamWrap {
315314
return 0;
316315
}
317316

318-
let key =
319-
handle_data
320-
.read_callbacks
321-
.borrow_mut()
322-
.insert(ReadCallbackState {
323-
isolate: v8::UnsafeRawIsolatePtr::null(),
324-
onread: None,
325-
stream_base_state: None,
326-
handle: None,
327-
bytes_read: handle_data.bytes_read.clone(),
328-
read_interceptor: handle_data.desired_read_interceptor.get(),
329-
});
317+
// Use try_borrow_mut to handle re-entrant calls gracefully.
318+
// This can happen when uv_read_start fires on_uv_read synchronously
319+
// (data already buffered), whose interceptor calls cycle() which
320+
// may call back into read_start.
321+
let Ok(mut callbacks) = handle_data.read_callbacks.try_borrow_mut() else {
322+
return 0;
323+
};
324+
let key = callbacks.insert(ReadCallbackState {
325+
isolate: v8::UnsafeRawIsolatePtr::null(),
326+
onread: None,
327+
stream_base_state: None,
328+
handle: None,
329+
bytes_read: handle_data.bytes_read.clone(),
330+
read_interceptor: handle_data.desired_read_interceptor.get(),
331+
});
332+
drop(callbacks);
330333
handle_data.active_read.set(Some(key));
331334

332335
// SAFETY: `stream` is a valid libuv stream owned by this wrapper.
@@ -343,7 +346,11 @@ impl LibUvStreamWrap {
343346
// `StreamHandleData` allocation while the native stream is alive.
344347
let handle_data = unsafe { handle_data_ptr.as_ref() };
345348
if let Some(key) = handle_data.active_read.take() {
346-
let _ = handle_data.read_callbacks.borrow_mut().remove(key);
349+
// Use try_borrow_mut to handle re-entrant calls from interceptor
350+
// callbacks that may fire during on_uv_read processing.
351+
if let Ok(mut callbacks) = handle_data.read_callbacks.try_borrow_mut() {
352+
let _ = callbacks.remove(key);
353+
}
347354
}
348355
// SAFETY: `stream` is a valid libuv stream owned by this wrapper.
349356
unsafe { uv_compat::uv_read_stop(stream) }

0 commit comments

Comments
 (0)