denoland/deno · error · RangeError

ERR_HTTP2_INVALID_PACKED_SETTINGS_LENGTH

ERR_HTTP2_INVALID_PACKED_SETTINGS_LENGTH

Error message

Packed settings length must be a multiple of six

What it means

The packed SETTINGS format is a sequence of 6-byte records: a 2-byte big-endian setting ID followed by a 4-byte big-endian value. getUnpackedSettings(buf) throws ERR_HTTP2_INVALID_PACKED_SETTINGS_LENGTH when buf.length % 6 !== 0 because partial records cannot be decoded. The check runs after type validation, once non-Buffer inputs have been converted via Buffer.from.

Source

Thrown at ext/node/polyfills/http2.ts:5534

]);

function getUnpackedSettings(buf) {
  if (
    // deno-lint-ignore deno-internal/prefer-primordials
    !Buffer.isBuffer(buf) &&
    !(ArrayBufferIsView(buf) && !(buf instanceof DataView))
  ) {
    throw new ERR_INVALID_ARG_TYPE("buf", [
      "Buffer",
      "TypedArray",
    ], buf);
  }
  if (!Buffer.isBuffer(buf)) {
    // deno-lint-ignore deno-internal/prefer-primordials
    buf = Buffer.from(buf);
  }
  if (buf.length % 6 !== 0) {
    throw new ERR_HTTP2_INVALID_PACKED_SETTINGS_LENGTH();
  }

  const settings = { __proto__: null };
  for (let i = 0; i < buf.length; i += 6) {
    const id = buf.readUInt16BE(i);
    const value = buf.readUInt32BE(i + 2);
    const name = SETTING_ID_TO_NAME.get(id);
    if (name !== undefined) {
      if (name === "enablePush" || name === "enableConnectProtocol") {
        settings[name] = value !== 0;
      } else {
        settings[name] = value;
        if (name === "maxHeaderListSize") {
          settings.maxHeaderSize = value;
        }
      }
    } else {
      // Unknown setting IDs become custom settings

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Slice the exact payload: for an HTTP/2 frame, the packed settings are frame.subarray(9, 9 + frameLength) — the 9-byte header (3-byte length, type, flags, 4-byte stream id) is not part of the settings.
  2. Guard with if (buf.length % 6 !== 0) and drop or log the malformed frame instead of decoding it.
  3. Generate fixtures with http2.getPackedSettings(settings) — its output is always a multiple of 6 and round-trips through getUnpackedSettings.

Example fix

// before
const settings = http2.getUnpackedSettings(frame); // frame includes 9-byte header -> length % 6 !== 0

// after
const payloadLength = frame.readUIntBE(0, 3); // 24-bit frame length
const payload = frame.subarray(9, 9 + payloadLength);
const settings = http2.getUnpackedSettings(payload);
Defensive patterns

Strategy: validation

Validate before calling

function isValidPackedSettings(buf) {
  return (Buffer.isBuffer(buf) || ArrayBuffer.isView(buf)) && buf.length % 6 === 0;
}
if (!isValidPackedSettings(payload)) throw new Error('malformed SETTINGS payload');
const settings = http2.getUnpackedSettings(payload);

Try / catch

try {
  const s = http2.getUnpackedSettings(buf);
} catch (e) {
  if (e.code === 'ERR_HTTP2_INVALID_PACKED_SETTINGS_LENGTH') {
    // drop/log the malformed frame, do not decode
  } else throw e;
}

Prevention

When it happens

Trigger: http2.getUnpackedSettings(buf) with a truncated or over-long SETTINGS payload — any byte length not divisible by 6 (e.g. 7, 11, 17); slicing a SETTINGS frame with wrong offsets, such as including the 9-byte frame header or stopping before the payload ends; concatenating unrelated bytes before decoding.

Common situations: Manual HTTP/2 frame parsing that miscomputes payload boundaries; hand-written test fixtures with arbitrary byte counts; reading a SETTINGS frame but slicing from the start of the frame header instead of the payload.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/61ed5e63b621ce23. Report an issue: GitHub.