diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index bb1cc9e6c56fa2..bfdb45ab7ba216 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -19,14 +19,16 @@ const { Promise, PromisePrototypeCatch, ReflectApply, + ReflectGet, ReflectGetPrototypeOf, + ReflectSet, RegExpPrototypeTest, SafeArrayIterator, SafeMap, SafeSet, StringPrototypeSlice, Symbol, - TypedArrayPrototypeSet, + TypedArrayPrototypeGetLength, Uint32Array, Uint8Array, } = primordials; @@ -959,6 +961,36 @@ const validateSettings = hideStackFrames((settings) => { } }); +// Wrap a typed array in a proxy, and allow selectively copying the entries +// that have explicitly been set to another typed array. +function trackAssignmentsTypedArray(typedArray) { + const typedArrayLength = TypedArrayPrototypeGetLength(typedArray); + const modifiedEntries = new Uint8Array(typedArrayLength); + + function copyAssigned(target) { + for (let i = 0; i < typedArrayLength; i++) { + if (modifiedEntries[i]) { + target[i] = typedArray[i]; + } + } + } + + return new Proxy(typedArray, { + get(obj, prop, receiver) { + if (prop === 'copyAssigned') { + return copyAssigned; + } + return ReflectGet(obj, prop, receiver); + }, + set(obj, prop, value) { + if (`${+prop}` === prop) { + modifiedEntries[prop] = 1; + } + return ReflectSet(obj, prop, value); + } + }); +} + // Creates the internal binding.Http2Session handle for an Http2Session // instance. This occurs only after the socket connection has been // established. Note: the binding.Http2Session will take over ownership @@ -989,10 +1021,13 @@ function setupHandle(socket, type, options) { handle.consume(socket._handle); this[kHandle] = handle; - if (this[kNativeFields]) - TypedArrayPrototypeSet(handle.fields, this[kNativeFields]); - else - this[kNativeFields] = handle.fields; + if (this[kNativeFields]) { + // If some options have already been set before the handle existed, copy + // those (and only those) that have manually been set over. + this[kNativeFields].copyAssigned(handle.fields); + } + + this[kNativeFields] = handle.fields; if (socket.encrypted) { this[kAlpnProtocol] = socket.alpnProtocol; @@ -1044,7 +1079,8 @@ function cleanupSession(session) { session[kProxySocket] = undefined; session[kSocket] = undefined; session[kHandle] = undefined; - session[kNativeFields] = new Uint8Array(kSessionUint8FieldCount); + session[kNativeFields] = trackAssignmentsTypedArray( + new Uint8Array(kSessionUint8FieldCount)); if (handle) handle.ondone = null; if (socket) { @@ -1212,8 +1248,10 @@ class Http2Session extends EventEmitter { setupFn(); } - if (!this[kNativeFields]) - this[kNativeFields] = new Uint8Array(kSessionUint8FieldCount); + if (!this[kNativeFields]) { + this[kNativeFields] = trackAssignmentsTypedArray( + new Uint8Array(kSessionUint8FieldCount)); + } this.on('newListener', sessionListenerAdded); this.on('removeListener', sessionListenerRemoved);