Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
541 changes: 504 additions & 37 deletions src/js/internal/test/binding.ts

Large diffs are not rendered by default.

70 changes: 68 additions & 2 deletions src/js/node/http2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1707,6 +1707,34 @@ const constants = {
HTTP_STATUS_NOT_EXTENDED: 510,
HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED: 511,
};
{
// Constants node's binding registers with NODE_DEFINE_HIDDEN_CONSTANT
// (src/node_http2.cc): present on http2.constants but non-enumerable.
const hidden = {
NGHTTP2_HCAT_REQUEST: 0,
NGHTTP2_HCAT_RESPONSE: 1,
NGHTTP2_HCAT_PUSH_RESPONSE: 2,
NGHTTP2_HCAT_HEADERS: 3,
NGHTTP2_NV_FLAG_NONE: 0,
NGHTTP2_NV_FLAG_NO_INDEX: 1,
NGHTTP2_ERR_DEFERRED: -508,
NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE: -509,
NGHTTP2_ERR_INVALID_ARGUMENT: -501,
NGHTTP2_ERR_STREAM_CLOSED: -510,
NGHTTP2_ERR_NOMEM: -901,
STREAM_OPTION_EMPTY_PAYLOAD: 1,
STREAM_OPTION_GET_TRAILERS: 2,
};
for (const name of Object.keys(hidden)) {
Object.defineProperty(constants, name, {
__proto__: null,
value: hidden[name],
writable: false,
enumerable: false,
configurable: false,
});
}
}
const {
NGHTTP2_ERR_FRAME_SIZE_ERROR,
NGHTTP2_SESSION_SERVER,
Expand Down Expand Up @@ -2034,23 +2062,59 @@ function assertWithinRange(name: string, value: number, min = 0, max = Infinity)
}
}

// nghttp2_strerror() messages for the codes the runtime/test-suite surface; everything else uses
// nghttp2's default branch ("Unknown error code").
// Full transcription of nghttp2_strerror(); unknown codes use nghttp2's default branch
// ("Unknown error code").
// https://github.com/nghttp2/nghttp2/blob/master/lib/nghttp2_helper.c (nghttp2_strerror)
const kNghttp2ErrorMessages = {
0: "Success",
[-501]: "Invalid argument",
[-502]: "Out of buffer space",
[-503]: "Unsupported SPDY version",
[-504]: "Operation would block",
[-505]: "Protocol error",
[-506]: "Invalid frame octets",
[-507]: "EOF",
[-508]: "Data transfer deferred",
[-509]: "No more Stream ID available",
[-510]: "Stream was already closed or invalid",
[-511]: "Stream is closing",
[-512]: "The transmission is not allowed for this stream",
[-513]: "Stream ID is invalid",
[-514]: "Invalid stream state",
[-515]: "Another DATA frame has already been deferred",
[-516]: "request HEADERS is not allowed",
[-517]: "GOAWAY has already been sent",
[-518]: "Invalid header block",
[-519]: "Invalid state",
[-521]: "The user callback function failed due to the temporal error",
[-522]: "The length of the frame is invalid",
[-523]: "Header compression/decompression error",
[-524]: "Flow control error",
[-525]: "Insufficient buffer size given to function",
[-526]: "Callback was paused by the application",
[-527]: "Too many inflight SETTINGS",
[-528]: "Server push is disabled by peer",
[-529]: "DATA or HEADERS frame has already been submitted for the stream",
[-530]: "The current session is closing",
[-531]: "Invalid HTTP header field was received",
[-532]: "Violation in HTTP messaging rule",
[-533]: "Stream was refused",
[-534]: "Internal error",
[-535]: "Cancel",
[-536]: "When a local endpoint expects to receive SETTINGS frame, it receives an other type of frame",
[-537]: "SETTINGS frame contained more than the maximum allowed entries",
[-901]: "Out of memory",
[-902]: "The user callback function failed",
[-903]: "Received bad client magic byte string",
[-904]: "Flooding was detected in this HTTP/2 session, and it must be closed",
[-905]: "Too many CONTINUATION frames following a HEADER frame",
};

// Same surface as node's internalBinding("http2").nghttp2ErrorString.
function nghttp2ErrorString(code: number): string {
return kNghttp2ErrorMessages[code] || "Unknown error code";
}

class NghttpError extends Error {
code: string;
errno: number;
Expand Down Expand Up @@ -6916,6 +6980,8 @@ export default {
assertValidPseudoHeader,
sessionName,
NghttpError,
nghttp2ErrorString,
createPendingStreamCancelError,
},
},
};
Expand Down
4 changes: 3 additions & 1 deletion src/js/node/net.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,9 @@ const SocketHandlers: SocketHandler = {
} as const;

function finishSocketEnd(self) {
if (self[kended]) return;
// A locally-destroyed socket must not synthesize a graceful 'end' from its own teardown close
// (node's readable is already destroyed; destroy() then native close was emitting one here).
if (self[kended] || self.destroyed) return;
self[kended] = true;
if (!self.allowHalfOpen) self.write = writeAfterFIN;
self.push(null);
Expand Down
1 change: 1 addition & 0 deletions src/jsc/bindings/BunHeapProfiler.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "root.h"
#include "BunHeapProfiler.h"
#include "BunCPUProfiler.h"
#include "headers-handwritten.h"
#include "ZigGlobalObject.h"
Expand Down
35 changes: 34 additions & 1 deletion test/js/node/test/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ function installBunExposeInternalsRequireInterceptor() {
if (override !== undefined) {
const cached = mergedInternals[id];
if (cached !== undefined) return cached;
return (mergedInternals[id] = { ...vendored, ...override() });
return (mergedInternals[id] = { ...vendored, ...override(vendored) });
}
return vendored;
}
Expand All @@ -354,6 +354,39 @@ function installBunExposeInternalsRequireInterceptor() {
kReinitializeHandle: bySymbolName('kReinitializeHandle'),
};
},
// internal/http2/util: the vendored copy mints its own NghttpError and
// assert helpers; recover the live ones from node:http2's internals (plus
// the kSocket symbol) so instances created by the runtime keep matching.
'internal/http2/util': (vendored) => {
let internals = {};
let realSensitiveHeaders;
try {
const http2 = originalRequire.call(module, 'node:http2');
internals = http2[Symbol.for('::bunhttp2internals::')] ?? {};
realSensitiveHeaders = http2.sensitiveHeaders;
} catch {
// http2 may be unavailable in some builds; the vendored exports stand alone.
}
const util = { kSocket: Symbol.for('::bunhttp2socket::'), ...(internals.util ?? {}) };
// The vendored module mints its own kSensitiveHeaders symbol, but tests
// mark headers with the public http2.sensitiveHeaders. Bridge the two:
// export the real symbol and mirror it onto the vendored one before
// buildNgHeaderString reads its module-private copy.
if (realSensitiveHeaders !== undefined && vendored !== undefined &&
typeof vendored.buildNgHeaderString === 'function' && vendored.kSensitiveHeaders !== undefined) {
const vendoredSymbol = vendored.kSensitiveHeaders;
const inner = vendored.buildNgHeaderString;
util.kSensitiveHeaders = realSensitiveHeaders;
util.buildNgHeaderString = function buildNgHeaderString(arrayOrMap, ...rest) {
if (arrayOrMap != null && arrayOrMap[realSensitiveHeaders] !== undefined &&
arrayOrMap[vendoredSymbol] === undefined) {
arrayOrMap[vendoredSymbol] = arrayOrMap[realSensitiveHeaders];
}
return inner(arrayOrMap, ...rest);
};
}
return util;
},
};
// http2-specific internal modules (internal/http2/util, …) are
// served separately via Bun.plugin module shims backed by the
Expand Down
11 changes: 11 additions & 0 deletions test/js/node/test/common/nodeinternals.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const VENDORED = new Set([
'internal/fs/sync_write_stream',
'internal/net',
'internal/event_target',
'internal/http2/util',
'internal/js_stream_socket',
'internal/webstreams/adapters',
]);

// ---------------- primordials emulator ----------------
Expand Down Expand Up @@ -351,6 +354,14 @@ const errDefs = {
ERR_NO_TEMPORAL: [Error, () => 'Temporal unavailable'],
ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED: [Error, () => 'Custom engines not supported by this OpenSSL'],
ERR_CRYPTO_ENGINE_UNKNOWN: [Error, (e) => `Engine "${e}" was not found`],
// http2 codes thrown by the vendored internal/http2/util.js (messages from
// node's lib/internal/errors.js).
ERR_HTTP2_HEADER_SINGLE_VALUE: [TypeError, (name) => `Header field "${name}" must only have a single value`],
ERR_HTTP2_INVALID_CONNECTION_HEADERS: [TypeError, (name) => `HTTP/1 Connection specific headers are forbidden: "${name}"`],
ERR_HTTP2_INVALID_PSEUDOHEADER: [TypeError, (name) => `"${name}" is an invalid pseudoheader or is used incorrectly`],
ERR_INVALID_HTTP_TOKEN: [TypeError, (name, field) => `${name} must be a valid HTTP token ["${field}"]`],
ERR_INVALID_STATE: [Error, (message) => `Invalid state: ${message}`],
ERR_STREAM_WRAP: [Error, () => 'Stream has StringDecoder set or is in objectMode'],
};

const systemErrDefs = {
Expand Down
Loading
Loading