zlib-transform
Version:
A zlib transform for the BOB streaming protocol.
411 lines (344 loc) • 12.7 kB
JavaScript
/* eslint-disable semi, comma-dangle, curly, indent, spaced-comment, no-unused-vars, node/no-deprecated-api, no-self-compare, space-before-function-paren */
'use strict';
// Flags: --expose-internals
const util = require('util')
// const errors = require('internal/errors');
const Transform = require('_stream_transform');
const { _extend } = require('util');
const { isAnyArrayBuffer } = process.binding('util');
// const { isArrayBufferView } = require('internal/util/types');
const binding = process.binding('zlib');
const assert = require('assert').ok;
const {
Buffer,
kMaxLength
} = require('buffer');
const status_type = require('bob-status') // eslint-disable-line camelcase
const constants = process.binding('constants').zlib;
const {
Z_NO_FLUSH, Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, Z_FULL_FLUSH, Z_FINISH,
Z_MIN_CHUNK, Z_MIN_WINDOWBITS, Z_MAX_WINDOWBITS, Z_MIN_LEVEL, Z_MAX_LEVEL,
Z_MIN_MEMLEVEL, Z_MAX_MEMLEVEL, Z_DEFAULT_CHUNK, Z_DEFAULT_COMPRESSION,
Z_DEFAULT_STRATEGY, Z_DEFAULT_WINDOWBITS, Z_DEFAULT_MEMLEVEL, Z_FIXED,
DEFLATE, DEFLATERAW, INFLATE, INFLATERAW, GZIP, GUNZIP, UNZIP
} = constants;
const { inherits } = require('util');
// translation table for return codes.
const codes = {
Z_OK: constants.Z_OK,
Z_STREAM_END: constants.Z_STREAM_END,
Z_NEED_DICT: constants.Z_NEED_DICT,
Z_ERRNO: constants.Z_ERRNO,
Z_STREAM_ERROR: constants.Z_STREAM_ERROR,
Z_DATA_ERROR: constants.Z_DATA_ERROR,
Z_MEM_ERROR: constants.Z_MEM_ERROR,
Z_BUF_ERROR: constants.Z_BUF_ERROR,
Z_VERSION_ERROR: constants.Z_VERSION_ERROR
};
const ckeys = Object.keys(codes);
for (var ck = 0; ck < ckeys.length; ck++) {
var ckey = ckeys[ck];
codes[codes[ckey]] = ckey;
}
// If a flush is scheduled while another flush is still pending, a way to figure
// out which one is the "stronger" flush is needed.
// Roughly, the following holds:
// Z_NO_FLUSH (< Z_TREES) < Z_BLOCK < Z_PARTIAL_FLUSH <
// Z_SYNC_FLUSH < Z_FULL_FLUSH < Z_FINISH
const flushiness = [];
let i = 0;
for (const flushFlag of [Z_NO_FLUSH, Z_BLOCK, Z_PARTIAL_FLUSH,
Z_SYNC_FLUSH, Z_FULL_FLUSH, Z_FINISH]) {
flushiness[flushFlag] = i++;
}
function maxFlush(a, b) {
return flushiness[a] > flushiness[b] ? a : b;
}
class ZlibTransform {
constructor (opts, mode) {
this.sink = null
this.source = null
var chunkSize = Z_DEFAULT_CHUNK;
var flush = Z_NO_FLUSH;
var finishFlush = Z_FINISH;
var windowBits = Z_DEFAULT_WINDOWBITS;
var level = Z_DEFAULT_COMPRESSION;
var memLevel = Z_DEFAULT_MEMLEVEL;
var strategy = Z_DEFAULT_STRATEGY;
var dictionary;
if (typeof mode !== 'number')
throw new /*errors.*/TypeError('ERR_INVALID_ARG_TYPE', 'mode', 'number');
if (mode < DEFLATE || mode > UNZIP)
throw new /*errors.*/RangeError('ERR_OUT_OF_RANGE', 'mode');
if (opts) {
chunkSize = opts.chunkSize;
if (chunkSize !== undefined && chunkSize === chunkSize) {
if (chunkSize < Z_MIN_CHUNK || !Number.isFinite(chunkSize))
throw new /*errors.*/RangeError('ERR_INVALID_OPT_VALUE',
'chunkSize',
chunkSize);
} else {
chunkSize = Z_DEFAULT_CHUNK;
}
flush = opts.flush;
if (flush !== undefined && flush === flush) {
if (flush < Z_NO_FLUSH || flush > Z_BLOCK || !Number.isFinite(flush))
throw new /*errors.*/RangeError('ERR_INVALID_OPT_VALUE', 'flush', flush);
} else {
flush = Z_NO_FLUSH;
}
finishFlush = opts.finishFlush;
if (finishFlush !== undefined && finishFlush === finishFlush) {
if (finishFlush < Z_NO_FLUSH || finishFlush > Z_BLOCK ||
!Number.isFinite(finishFlush)) {
throw new /*errors.*/RangeError('ERR_INVALID_OPT_VALUE',
'finishFlush',
finishFlush);
}
} else {
finishFlush = Z_FINISH;
}
windowBits = opts.windowBits;
if (windowBits !== undefined && windowBits === windowBits) {
if (windowBits < Z_MIN_WINDOWBITS || windowBits > Z_MAX_WINDOWBITS ||
!Number.isFinite(windowBits)) {
throw new /*errors.*/RangeError('ERR_INVALID_OPT_VALUE',
'windowBits',
windowBits);
}
} else {
windowBits = Z_DEFAULT_WINDOWBITS;
}
level = opts.level;
if (level !== undefined && level === level) {
if (level < Z_MIN_LEVEL || level > Z_MAX_LEVEL ||
!Number.isFinite(level)) {
throw new /*errors.*/RangeError('ERR_INVALID_OPT_VALUE',
'level', level);
}
} else {
level = Z_DEFAULT_COMPRESSION;
}
memLevel = opts.memLevel;
if (memLevel !== undefined && memLevel === memLevel) {
if (memLevel < Z_MIN_MEMLEVEL || memLevel > Z_MAX_MEMLEVEL ||
!Number.isFinite(memLevel)) {
throw new /*errors.*/RangeError('ERR_INVALID_OPT_VALUE',
'memLevel', memLevel);
}
} else {
memLevel = Z_DEFAULT_MEMLEVEL;
}
strategy = opts.strategy;
if (strategy !== undefined && strategy === strategy) {
if (strategy < Z_DEFAULT_STRATEGY || strategy > Z_FIXED ||
!Number.isFinite(strategy)) {
throw new /*errors.*/TypeError('ERR_INVALID_OPT_VALUE',
'strategy', strategy);
}
} else {
strategy = Z_DEFAULT_STRATEGY;
}
dictionary = opts.dictionary;
// if (dictionary !== undefined && !isArrayBufferView(dictionary)) {
// if (isAnyArrayBuffer(dictionary)) {
// dictionary = Buffer.from(dictionary);
// } else {
// throw new errors.TypeError('ERR_INVALID_OPT_VALUE',
// 'dictionary',
// dictionary);
// }
// }
}
this._handle = new binding.Zlib(mode);
this._handle.jsref = this; // Used by processCallback() and zlibOnError()
this._handle.onerror = zlibOnError;
this._hadError = false;
this._writeState = new Uint32Array(2);
if (!this._handle.init(windowBits,
level,
memLevel,
strategy,
this._writeState,
processCallback,
dictionary)) {
throw new /*errors.*/Error('ERR_ZLIB_INITIALIZATION_FAILED');
}
this._outBuffer = Buffer.allocUnsafe(chunkSize);
this._outOffset = 0;
this._level = level;
this._strategy = strategy;
this._chunkSize = chunkSize;
this._flushFlag = flush;
this._scheduledFlushFlag = Z_NO_FLUSH;
this._origFlushFlag = flush;
this._finishFlushFlag = finishFlush;
this._info = opts && opts.info;
// The streams+ state
this._ended = false
this._pullFromHandle = false
this._pullSize = opts.hwm || 1024 * 16
}
bindSource (source) {
source.bindSink(this)
this.source = source
return this
}
bindSink (sink) {
this.sink = sink
}
close () {
_close(this)
}
get _closed () {
return !this._handle
}
next (status, error, buffer, bytes) {
if (error !== null) {
this.close()
return this.sink.next(status, error, Buffer.alloc(0), 0)
}
if (status === status_type.end) {
this._ended = true
}
if (buffer === null) buffer = Buffer.alloc(0)
if (bytes < 0) bytes = 0
// If it's the last chunk, or a final flush, we use the Z_FINISH flush flag
// (or whatever flag was provided using opts.finishFlush).
// If it's explicitly flushing at some other time, then we use
// Z_FULL_FLUSH. Otherwise, use the original opts.flush flag.
var flushFlag;
var ws = {} //this._writableState;
// if ((ws.ending || ws.ended) && ws.length === chunk.byteLength) {
if (status === status_type.end) {
// XXX: Should be on status_type.end message?
flushFlag = this._finishFlushFlag;
} else {
flushFlag = this._flushFlag;
// once we've flushed the last of the queue, stop flushing and
// go back to the normal behavior.
// if (chunk.byteLength >= ws.length)
// this._flushFlag = this._origFlushFlag;
}
var handle = this._handle;
if (!handle) {
this.close()
return this.sink.next(status, new /*errors.*/Error('ERR_ZLIB_BINDING_CLOSED'), Buffer.alloc(0), 0)
}
const cb = (error, pullMore) => {
if (error) {
this.close()
return this.sink.next(status, error, Buffer.alloc(0), 0)
}
if (status === status_type.end) return
if (pullMore) this.source.pull(null, Buffer.alloc(this._pullSize))
}
const chunk = buffer.slice(0, bytes)
handle.buffer = chunk;
handle.cb = cb;
handle.availOutBefore = this._chunkSize - this._outOffset;
handle.availInBefore = chunk.length;
handle.inOff = 0;
handle.flushFlag = flushFlag;
handle.write(flushFlag,
chunk, // in
0, // in_off
handle.availInBefore, // in_len
this._outBuffer, // out
this._outOffset, // out_off
handle.availOutBefore); // out_len
}
pull (error, buffer) {
if (this._pullFromHandle) {
var handle = this._handle;
if (!handle) {
this.close()
return this.sink.next(status_type.error, new /*errors.*/Error('ERR_ZLIB_BINDING_CLOSED'), Buffer.alloc(0), 0)
}
return handle.write(handle.flushFlag,
handle.buffer, // in
handle.inOff, // in_off
handle.availInBefore, // in_len
this._outBuffer, // out
this._outOffset, // out_off
this._chunkSize); // out_len
}
if (this._ended) {
this.sink.next(status_type.end, null, Buffer.alloc(0), 0)
return
}
return this.source.pull(error, buffer || Buffer.alloc(this._pullSize))
}
}
function zlibOnError(message, errno) {
var self = this.jsref;
// there is no way to cleanly recover.
// continuing only obscures problems.
_close(self);
self._hadError = true;
const error = new Error(message);
error.errno = errno;
error.code = codes[errno];
// Propogate the error up
self.source.pull(error, Buffer.alloc(0))
}
function _close(engine, callback) {
// Caller may invoke .close after a zlib error (which will null _handle).
if (!engine._handle)
return;
engine._handle.close();
engine._handle = null;
}
function processCallback() {
// This callback's context (`this`) is the `_handle` (ZCtx) object. It is
// important to null out the values once they are no longer needed since
// `_handle` can stay in memory long after the buffer is needed.
var handle = this;
var self = this.jsref;
var state = self._writeState;
var pullMore = true
if (self._hadError) {
this.buffer = null;
return;
}
if (self.destroyed) {
this.buffer = null;
return;
}
var availOutAfter = state[0];
var availInAfter = state[1];
var inDelta = (handle.availInBefore - availInAfter);
var have = handle.availOutBefore - availOutAfter;
if (have > 0) {
var out = self._outBuffer.slice(self._outOffset, self._outOffset + have);
self._outOffset += have;
pullMore = false
self.sink.next(status_type.continue, null, out, out.length)
} else if (have < 0) {
assert(false, 'have should not go down');
}
// exhausted the output buffer, or used all the input create a new one.
if (availOutAfter === 0 || self._outOffset >= self._chunkSize) {
handle.availOutBefore = self._chunkSize;
self._outOffset = 0;
self._outBuffer = Buffer.allocUnsafe(self._chunkSize);
}
self._pullFromHandle = false
if (availOutAfter === 0) {
// Not actually done. Need to reprocess.
// Also, update the availInBefore to the availInAfter value,
// so that if we have to hit it a third (fourth, etc.) time,
// it'll have the correct byte counts.
handle.inOff += inDelta;
handle.availInBefore = availInAfter;
self._pullFromHandle = true
if (have === 0) {
self.sink.next(status_type.continue, null, Buffer.alloc(0), 0)
}
return;
}
// finished with the chunk.
this.buffer = null;
this.cb(null, pullMore);
}
module.exports = ZlibTransform