'use strict'; const { createInflateRaw, Z_DEFAULT_WINDOWBITS } = require('node:zlib'); const { isValidClientWindowBits } = require('./util'); const tail = Buffer.from([0x00, 0x00, 0xff, 0xff]); const kBuffer = Symbol('kBuffer'); const kLength = Symbol('kLength'); class PerMessageDeflate { /** @type {import('node:zlib').InflateRaw} */ #inflate; #options = {}; constructor(extensions) { this.#options.serverNoContextTakeover = extensions.has( 'server_no_context_takeover' ); this.#options.serverMaxWindowBits = extensions.get( 'server_max_window_bits' ); } decompress(chunk, fin, callback) { // An endpoint uses the following algorithm to decompress a message. // 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the // payload of the message. // 2. Decompress the resulting data using DEFLATE. if (!this.#inflate) { let windowBits = Z_DEFAULT_WINDOWBITS; if (this.#options.serverMaxWindowBits) { // empty values default to Z_DEFAULT_WINDOWBITS if (!isValidClientWindowBits(this.#options.serverMaxWindowBits)) { callback(new Error('Invalid server_max_window_bits')); return; } windowBits = Number.parseInt(this.#options.serverMaxWindowBits); } this.#inflate = createInflateRaw({ windowBits }); this.#inflate[kBuffer] = []; this.#inflate[kLength] = 0; this.#inflate.on('data', (data) => { this.#inflate[kBuffer].push(data); this.#inflate[kLength] += data.length; }); this.#inflate.on('error', (err) => { this.#inflate = null; callback(err); }); } this.#inflate.write(chunk); if (fin) { this.#inflate.write(tail); } this.#inflate.flush(() => { const full = Buffer.concat( this.#inflate[kBuffer], this.#inflate[kLength] ); this.#inflate[kBuffer].length = 0; this.#inflate[kLength] = 0; callback(null, full); }); } } module.exports = { PerMessageDeflate };