Skip to content

Commit

Permalink
http2: receive customsettings
Browse files Browse the repository at this point in the history
This commit gives node.js the ability to also receive custom settings,
in addition to sending, them which was implemented before.
The custom settings received are limited to setting ids,
that were specified before, when creating the session eithers through
the server or the client.

PR-URL: #51323
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
  • Loading branch information
martenrichter authored and richardlau committed Mar 25, 2024
1 parent aeae43d commit 3eaf640
Show file tree
Hide file tree
Showing 9 changed files with 383 additions and 31 deletions.
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

0 comments on commit 3eaf640

Please sign in to comment.