Skip to content

Commit

Permalink
zlib: add zstd support
Browse files Browse the repository at this point in the history
  • Loading branch information
jkrems committed Mar 17, 2024
1 parent 085f01b commit 383ff46
Show file tree
Hide file tree
Showing 13 changed files with 476 additions and 13 deletions.
2 changes: 1 addition & 1 deletion benchmark/zlib/creation.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const zlib = require('zlib');
const bench = common.createBenchmark(main, {
type: [
'Deflate', 'DeflateRaw', 'Inflate', 'InflateRaw', 'Gzip', 'Gunzip', 'Unzip',
'BrotliCompress', 'BrotliDecompress',
'BrotliCompress', 'BrotliDecompress', 'ZstdCompress', 'ZstdDecompress',
],
options: ['true', 'false'],
n: [5e5],
Expand Down
15 changes: 10 additions & 5 deletions benchmark/zlib/pipe.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,27 @@ const bench = common.createBenchmark(main, {
inputLen: [1024],
duration: [5],
type: ['string', 'buffer'],
algorithm: ['gzip', 'brotli'],
algorithm: ['gzip', 'brotli', 'zstd'],
}, {
test: {
inputLen: 1024,
duration: 0.2,
},
});

const algorithms = {
'gzip': [zlib.createGzip, zlib.createGunzip],
'brotli': [zlib.createBrotliCompress, zlib.createBrotliDecompress],
'zstd': [zlib.createZstdCompress, zlib.createZstdDecompress],
};

function main({ inputLen, duration, type, algorithm }) {
const buffer = Buffer.alloc(inputLen, fs.readFileSync(__filename));
const chunk = type === 'buffer' ? buffer : buffer.toString('utf8');

const input = algorithm === 'gzip' ?
zlib.createGzip() : zlib.createBrotliCompress();
const output = algorithm === 'gzip' ?
zlib.createGunzip() : zlib.createBrotliDecompress();
const [createCompress, createUncompress] = algorithms[algorithm];
const input = createCompress();
const output = createUncompress();

let readFromOutput = 0;
input.pipe(output);
Expand Down
1 change: 1 addition & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1917,3 +1917,4 @@ E('ERR_WORKER_UNSERIALIZABLE_ERROR',
E('ERR_WORKER_UNSUPPORTED_OPERATION',
'%s is not supported in workers', TypeError);
E('ERR_ZLIB_INITIALIZATION_FAILED', 'Initialization failed', Error);
E('ERR_ZSTD_INVALID_PARAM', '%s is not a valid zstd parameter', RangeError);
95 changes: 91 additions & 4 deletions lib/zlib.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const {
ERR_INVALID_ARG_TYPE,
ERR_OUT_OF_RANGE,
ERR_ZLIB_INITIALIZATION_FAILED,
ERR_ZSTD_INVALID_PARAM,
},
genericNodeError,
hideStackFrames,
Expand Down Expand Up @@ -88,9 +89,12 @@ const {
// Node's compression stream modes (node_zlib_mode)
DEFLATE, DEFLATERAW, INFLATE, INFLATERAW, GZIP, GUNZIP, UNZIP,
BROTLI_DECODE, BROTLI_ENCODE,
ZSTD_COMPRESS, ZSTD_DECOMPRESS,
// Brotli operations (~flush levels)
BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_FLUSH,
BROTLI_OPERATION_FINISH, BROTLI_OPERATION_EMIT_METADATA,
// Zstd end directives (~flush levels)
ZSTD_e_continue, ZSTD_e_flush, ZSTD_e_end,
} = constants;

// Translation table for return codes.
Expand Down Expand Up @@ -237,9 +241,11 @@ const checkRangesOrGetDefault = hideStackFrames(
const FLUSH_BOUND = [
[ Z_NO_FLUSH, Z_BLOCK ],
[ BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_EMIT_METADATA ],
[ ZSTD_e_continue, ZSTD_e_end ],
];
const FLUSH_BOUND_IDX_NORMAL = 0;
const FLUSH_BOUND_IDX_BROTLI = 1;
const FLUSH_BOUND_IDX_ZSTD = 2;

// The base class for all Zlib-style streams.
function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
Expand All @@ -248,13 +254,15 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
// The ZlibBase class is not exported to user land, the mode should only be
// passed in by us.
assert(typeof mode === 'number');
assert(mode >= DEFLATE && mode <= BROTLI_ENCODE);
assert(mode >= DEFLATE && mode <= ZSTD_DECOMPRESS);

let flushBoundIdx;
if (mode !== BROTLI_ENCODE && mode !== BROTLI_DECODE) {
flushBoundIdx = FLUSH_BOUND_IDX_NORMAL;
} else {
if (mode === BROTLI_ENCODE || mode === BROTLI_DECODE) {
flushBoundIdx = FLUSH_BOUND_IDX_BROTLI;
} else if (mode === ZSTD_COMPRESS || mode === ZSTD_DECOMPRESS) {
flushBoundIdx = FLUSH_BOUND_IDX_ZSTD;
} else {
flushBoundIdx = FLUSH_BOUND_IDX_NORMAL;
}

if (opts) {
Expand Down Expand Up @@ -888,6 +896,77 @@ ObjectSetPrototypeOf(BrotliDecompress.prototype, Brotli.prototype);
ObjectSetPrototypeOf(BrotliDecompress, Brotli);


const kMaxZstdParam = MathMaxApply(ArrayPrototypeMap(
ObjectKeys(constants),
(key) => (StringPrototypeStartsWith(key, 'ZSTD_c_') ?
constants[key] :
0),
));

const zstdInitParamsArray = new Uint32Array(kMaxZstdParam + 1);

const zstdDefaultOpts = {
flush: ZSTD_e_continue, // BROTLI_OPERATION_PROCESS,
finishFlush: ZSTD_e_end, // BROTLI_OPERATION_FINISH,
fullFlush: ZSTD_e_flush, // BROTLI_OPERATION_FLUSH,
};
function Zstd(opts, mode) {
assert(mode === ZSTD_COMPRESS || mode === ZSTD_DECOMPRESS);

TypedArrayPrototypeFill(zstdInitParamsArray, -1);
if (opts?.params) {
ArrayPrototypeForEach(ObjectKeys(opts.params), (origKey) => {
const key = +origKey;
if (NumberIsNaN(key) || key < 0 || key > kMaxZstdParam ||
(zstdInitParamsArray[key] | 0) !== -1) {
throw new ERR_ZSTD_INVALID_PARAM(origKey);
}

const value = opts.params[origKey];
if (typeof value !== 'number' && typeof value !== 'boolean') {
throw new ERR_INVALID_ARG_TYPE('options.params[key]',
'number', opts.params[origKey]);
}
zstdInitParamsArray[key] = value;
});
}

const handle = mode === ZSTD_COMPRESS ?
new binding.ZstdCompress() : new binding.ZstdDecompress();

this._writeState = new Uint32Array(2);
if (!handle.init(zstdInitParamsArray,
this._writeState,
processCallback)) {
throw new ERR_ZLIB_INITIALIZATION_FAILED();
}

ReflectApply(ZlibBase, this, [opts, mode, handle, zstdDefaultOpts]);
}
ObjectSetPrototypeOf(Zstd.prototype, ZlibBase.prototype);
ObjectSetPrototypeOf(Zstd, ZlibBase);


function ZstdCompress(opts) {
if (!(this instanceof ZstdCompress))
return new ZstdCompress(opts);

ReflectApply(Zstd, this, [opts, ZSTD_COMPRESS]);
}
ObjectSetPrototypeOf(ZstdCompress.prototype, Zstd.prototype);
ObjectSetPrototypeOf(ZstdCompress, Zstd);


function ZstdDecompress(opts) {
if (!(this instanceof ZstdDecompress))
return new ZstdDecompress(opts);

ReflectApply(Zstd, this, [opts, ZSTD_DECOMPRESS]);
}
ObjectSetPrototypeOf(ZstdDecompress.prototype, Zstd.prototype);
ObjectSetPrototypeOf(ZstdDecompress, Zstd);


function createProperty(ctor) {
return {
__proto__: null,
Expand Down Expand Up @@ -917,6 +996,8 @@ module.exports = {
Unzip,
BrotliCompress,
BrotliDecompress,
ZstdCompress,
ZstdDecompress,

// Convenience methods.
// compress/decompress a string or buffer in one step.
Expand All @@ -938,6 +1019,10 @@ module.exports = {
brotliCompressSync: createConvenienceMethod(BrotliCompress, true),
brotliDecompress: createConvenienceMethod(BrotliDecompress, false),
brotliDecompressSync: createConvenienceMethod(BrotliDecompress, true),
zstdCompress: createConvenienceMethod(ZstdCompress, false),
zstdCompressSync: createConvenienceMethod(ZstdCompress, true),
zstdDecompress: createConvenienceMethod(ZstdDecompress, false),
zstdDecompressSync: createConvenienceMethod(ZstdDecompress, true),
};

ObjectDefineProperties(module.exports, {
Expand All @@ -950,6 +1035,8 @@ ObjectDefineProperties(module.exports, {
createUnzip: createProperty(Unzip),
createBrotliCompress: createProperty(BrotliCompress),
createBrotliDecompress: createProperty(BrotliDecompress),
createZstdCompress: createProperty(ZstdCompress),
createZstdDecompress: createProperty(ZstdDecompress),
constants: {
__proto__: null,
configurable: false,
Expand Down
Loading

0 comments on commit 383ff46

Please sign in to comment.