Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

http2: Receive customsettings #51323

Merged
merged 1 commit into from
Jan 7, 2024
Merged
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
21 changes: 18 additions & 3 deletions doc/api/http2.md
Original file line number Diff line number Diff line change
Expand Up @@ -2498,6 +2498,11 @@ changes:
**Default:** `100`.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `remoteCustomSettings` {Array} The array of integer values determines the
settings types, which are included in the `CustomSettings`-property of
the received remoteSettings. Please see the `CustomSettings`-property of
the `Http2Settings` object for more information,
on the allowed setting types.
* `Http1IncomingMessage` {http.IncomingMessage} Specifies the
`IncomingMessage` class to used for HTTP/1 fallback. Useful for extending
the original `http.IncomingMessage`. **Default:** `http.IncomingMessage`.
Expand Down Expand Up @@ -2652,6 +2657,10 @@ changes:
**Default:** `100`.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `remoteCustomSettings` {Array} The array of integer values determines the
settings types, which are included in the `customSettings`-property of the
received remoteSettings. Please see the `customSettings`-property of the
`Http2Settings` object for more information, on the allowed setting types.
* ...: Any [`tls.createServer()`][] options can be provided. For
servers, the identity options (`pfx` or `key`/`cert`) are usually required.
* `origins` {string\[]} An array of origin strings to send within an `ORIGIN`
Expand Down Expand Up @@ -2780,6 +2789,10 @@ changes:
`'https:'`
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `remoteCustomSettings` {Array} The array of integer values determines the
settings types, which are included in the `CustomSettings`-property of the
received remoteSettings. Please see the `CustomSettings`-property of the
`Http2Settings` object for more information, on the allowed setting types.
* `createConnection` {Function} An optional callback that receives the `URL`
instance passed to `connect` and the `options` object, and returns any
[`Duplex`][] stream that is to be used as the connection for this session.
Expand Down Expand Up @@ -3022,9 +3035,11 @@ properties.
it should be greater than 6, although it is not an error.
The values need to be unsigned integers in the range from 0 to 2^32-1.
Currently, a maximum of up 10 custom settings is supported.
It is only supported for sending SETTINGS.
Custom settings are not supported for the functions retrieving remote and
local settings as nghttp2 does not pass unknown HTTP/2 settings to Node.js.
It is only supported for sending SETTINGS, or for receiving settings values
specified in the `remoteCustomSettings` options of the server or client
object. Do not mix the `customSettings`-mechanism for a settings id with
interfaces for the natively handled settings, in case a setting becomes
natively supported in a future node version.

All additional properties on the settings object are ignored.

Expand Down
32 changes: 32 additions & 0 deletions lib/internal/http2/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ const {
FunctionPrototypeBind,
FunctionPrototypeCall,
MathMin,
Number,
ObjectAssign,
ObjectKeys,
ObjectDefineProperty,
ObjectEntries,
ObjectPrototypeHasOwnProperty,
Promise,
PromisePrototypeThen,
Expand Down Expand Up @@ -105,6 +107,7 @@ const {
ERR_HTTP2_STREAM_CANCEL,
ERR_HTTP2_STREAM_ERROR,
ERR_HTTP2_STREAM_SELF_DEPENDENCY,
ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS,
ERR_HTTP2_TRAILERS_ALREADY_SENT,
ERR_HTTP2_TRAILERS_NOT_READY,
ERR_HTTP2_UNSUPPORTED_PROTOCOL,
Expand Down Expand Up @@ -140,6 +143,7 @@ const {

const {
assertIsObject,
assertIsArray,
assertValidPseudoHeader,
assertValidPseudoHeaderResponse,
assertValidPseudoHeaderTrailer,
Expand All @@ -155,7 +159,9 @@ const {
kRequest,
kProxySocket,
mapToHeaders,
MAX_ADDITIONAL_SETTINGS,
NghttpError,
remoteCustomSettingsToBuffer,
sessionName,
toHeaderObject,
updateOptionsBuffer,
Expand Down Expand Up @@ -947,6 +953,15 @@ function pingCallback(cb) {
const validateSettings = hideStackFrames((settings) => {
if (settings === undefined) return;
assertIsObject.withoutStackTrace(settings.customSettings, 'customSettings', 'Number');
if (settings.customSettings) {
const entries = ObjectEntries(settings.customSettings);
if (entries.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
for (const { 0: key, 1: value } of entries) {
assertWithinRange.withoutStackTrace('customSettings:id', Number(key), 0, 0xffff);
assertWithinRange.withoutStackTrace('customSettings:value', Number(value), 0, kMaxInt);
}
}

assertWithinRange.withoutStackTrace('headerTableSize',
settings.headerTableSize,
Expand Down Expand Up @@ -1031,6 +1046,9 @@ function setupHandle(socket, type, options) {
this[kState].flags |= SESSION_FLAGS_READY;

updateOptionsBuffer(options);
if (options.remoteCustomSettings) {
remoteCustomSettingsToBuffer(options.remoteCustomSettings);
}
const handle = new binding.Http2Session(type);
handle[kOwner] = this;

Expand Down Expand Up @@ -3103,6 +3121,13 @@ function initializeOptions(options) {
assertIsObject(options.settings, 'options.settings');
options.settings = { ...options.settings };

assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
if (options.remoteCustomSettings) {
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
}

if (options.maxSessionInvalidFrames !== undefined)
validateUint32(options.maxSessionInvalidFrames, 'maxSessionInvalidFrames');

Expand Down Expand Up @@ -3277,6 +3302,13 @@ function connect(authority, options, listener) {
assertIsObject(options, 'options');
options = { ...options };

assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
if (options.remoteCustomSettings) {
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
}

if (typeof authority === 'string')
authority = new URL(authority);

Expand Down
66 changes: 60 additions & 6 deletions lib/internal/http2/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,19 @@ function updateOptionsBuffer(options) {
optionsBuffer[IDX_OPTIONS_FLAGS] = flags;
}

function addCustomSettingsToObj() {
const toRet = {};
const num = settingsBuffer[IDX_SETTINGS_FLAGS + 1];
for (let i = 0; i < num; i++) {
toRet[settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * i + 1].toString()] =
Number(settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * i + 2]);
}
return toRet;
}

function getDefaultSettings() {
settingsBuffer[IDX_SETTINGS_FLAGS] = 0;
settingsBuffer[IDX_SETTINGS_FLAGS + 1] = 0; // Length of custom settings
binding.refreshDefaultSettings();
const holder = { __proto__: null };

Expand Down Expand Up @@ -327,6 +338,8 @@ function getDefaultSettings() {
settingsBuffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL] === 1;
}

if (settingsBuffer[IDX_SETTINGS_FLAGS + 1]) holder.customSettings = addCustomSettingsToObj();

return holder;
}

Expand All @@ -338,7 +351,7 @@ function getSettings(session, remote) {
else
session.localSettings();

return {
const toRet = {
headerTableSize: settingsBuffer[IDX_SETTINGS_HEADER_TABLE_SIZE],
enablePush: !!settingsBuffer[IDX_SETTINGS_ENABLE_PUSH],
initialWindowSize: settingsBuffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE],
Expand All @@ -349,6 +362,8 @@ function getSettings(session, remote) {
enableConnectProtocol:
!!settingsBuffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL],
};
if (settingsBuffer[IDX_SETTINGS_FLAGS + 1]) toRet.customSettings = addCustomSettingsToObj();
return toRet;
}

function updateSettingsBuffer(settings) {
Expand Down Expand Up @@ -415,12 +430,22 @@ function updateSettingsBuffer(settings) {
}
}
if (!set) { // not supported
if (numCustomSettings === MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
let i = 0;
while (i < numCustomSettings) {
if (settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * i + 1] === nsetting) {
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * i + 2] = val;
break;
}
i++;
}
if (i === numCustomSettings) {
if (numCustomSettings === MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();

settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings + 1] = nsetting;
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings + 2] = val;
numCustomSettings++;
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings + 1] = nsetting;
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings + 2] = val;
numCustomSettings++;
}
}
}
}
Expand Down Expand Up @@ -475,6 +500,24 @@ function updateSettingsBuffer(settings) {
settingsBuffer[IDX_SETTINGS_FLAGS] = flags;
}

function remoteCustomSettingsToBuffer(remoteCustomSettings) {
if (remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
let numCustomSettings = 0;
for (let i = 0; i < remoteCustomSettings.length; i++) {
const nsetting = remoteCustomSettings[i];
if (typeof nsetting === 'number' && nsetting <= 0xffff &&
nsetting >= 0) {
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings + 1] = nsetting;
numCustomSettings++;
} else
throw new ERR_HTTP2_INVALID_SETTING_VALUE.RangeError(
'Range Error', nsetting, 0, 0xffff);

}
settingsBuffer[IDX_SETTINGS_FLAGS + 1] = numCustomSettings;
}

function getSessionState(session) {
session.refreshState();
return {
Expand Down Expand Up @@ -649,6 +692,14 @@ const assertIsObject = hideStackFrames((value, name, types) => {
}
});

const assertIsArray = hideStackFrames((value, name, types) => {
if (value !== undefined &&
(value === null ||
!ArrayIsArray(value))) {
throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(name, types || 'Array', value);
}
});

const assertWithinRange = hideStackFrames(
(name, value, min = 0, max = Infinity) => {
if (value !== undefined &&
Expand Down Expand Up @@ -732,6 +783,7 @@ function getAuthority(headers) {

module.exports = {
assertIsObject,
assertIsArray,
assertValidPseudoHeader,
assertValidPseudoHeaderResponse,
assertValidPseudoHeaderTrailer,
Expand All @@ -747,7 +799,9 @@ module.exports = {
kProxySocket,
kRequest,
mapToHeaders,
MAX_ADDITIONAL_SETTINGS,
NghttpError,
remoteCustomSettingsToBuffer,
sessionName,
toHeaderObject,
updateOptionsBuffer,
Expand Down
Loading