Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions doc/api/zlib.md
Original file line number Diff line number Diff line change
Expand Up @@ -2158,7 +2158,8 @@ added: v0.5.8
-->

* `kind` **Default:** `zlib.constants.Z_FULL_FLUSH` for zlib-based streams,
`zlib.constants.BROTLI_OPERATION_FLUSH` for Brotli-based streams.
`zlib.constants.BROTLI_OPERATION_FLUSH` for Brotli-based streams, and
`zlib.constants.ZSTD_e_flush` for Zstd-based streams.
* `callback` {Function}

Flush pending data. Don't call this frivolously, premature flushes negatively
Expand Down Expand Up @@ -2190,8 +2191,14 @@ Only applicable to deflate algorithm.
added: v0.7.0
-->

Reset the compressor/decompressor to factory defaults. Only applicable to
the inflate and deflate algorithms.
For inflate and deflate streams, reset the compressor/decompressor to factory
defaults.

For Zstd streams, cancel the current frame and start a new session while
preserving the configured parameters and dictionary. If `pledgedSrcSize` was
configured for a Zstd compressor, it applies again to the next frame.

Calling `reset()` while a write is in progress throws an `Error`.

## Class: `ZstdOptions`

Expand Down Expand Up @@ -2223,6 +2230,9 @@ Each Zstd-based class takes an `options` object. All options are optional.
* `finishFlush` {integer} **Default:** `zlib.constants.ZSTD_e_end`
* `chunkSize` {integer} **Default:** `16 * 1024`
* `params` {Object} Key-value object containing indexed [Zstd parameters][].
* `pledgedSrcSize` {number} Expected total size of the uncompressed input. It
must be a non-negative safe integer and must match the input size when
compression finishes. Only applicable to Zstd compressors.
* `maxOutputLength` {integer} Limits output size when using
[convenience methods][]. **Default:** [`buffer.kMaxLength`][]
* `info` {boolean} If `true`, returns an object with `buffer` and `engine`. **Default:** `false`
Expand Down Expand Up @@ -3062,6 +3072,8 @@ Compress a chunk of data with [`ZstdCompress`][].

### `zlib.zstdDecompress(buffer[, options], callback)`

> Stability: 1 - Experimental

<!-- YAML
added:
- v23.8.0
Expand Down
6 changes: 5 additions & 1 deletion lib/zlib.js
Original file line number Diff line number Diff line change
Expand Up @@ -907,7 +907,11 @@ class Zstd extends ZlibBase {
if (isAnyArrayBuffer(dictionary)) {
dictionary = new Uint8Array(dictionary);
} else {
dictionary = undefined;
throw new ERR_INVALID_ARG_TYPE(
'options.dictionary',
['Buffer', 'TypedArray', 'DataView', 'ArrayBuffer'],
dictionary,
);
}
}

Expand Down
39 changes: 35 additions & 4 deletions src/node_zlib.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1703,7 +1703,29 @@ CompressionError ZstdCompressContext::Init(uint64_t pledged_src_size,
}

CompressionError ZstdCompressContext::ResetStream() {
return Init(pledged_src_size_);
size_t result = ZSTD_CCtx_reset(cctx_.get(), ZSTD_reset_session_only);
if (ZSTD_isError(result)) {
const ZSTD_ErrorCode error = ZSTD_getErrorCode(result);
return CompressionError(
ZSTD_getErrorString(error), ZstdStrerror(error), error);
}

result = ZSTD_CCtx_setPledgedSrcSize(cctx_.get(), pledged_src_size_);
if (ZSTD_isError(result)) {
const ZSTD_ErrorCode error = ZSTD_getErrorCode(result);
return CompressionError(
ZSTD_getErrorString(error), ZstdStrerror(error), error);
}

if (pledged_src_size_ == ZSTD_CONTENTSIZE_UNKNOWN) {
consumed_src_size_.reset();
} else {
consumed_src_size_ = 0;
}
error_ = ZSTD_error_no_error;
error_string_.clear();
error_code_string_.clear();
return {};
}

void ZstdCompressContext::DoThreadPoolWork() {
Expand Down Expand Up @@ -1777,9 +1799,18 @@ CompressionError ZstdDecompressContext::Init(uint64_t pledged_src_size,
}

CompressionError ZstdDecompressContext::ResetStream() {
// We pass ZSTD_CONTENTSIZE_UNKNOWN because the argument is ignored for
// decompression.
return Init(ZSTD_CONTENTSIZE_UNKNOWN);
const size_t result = ZSTD_DCtx_reset(dctx_.get(), ZSTD_reset_session_only);
if (ZSTD_isError(result)) {
const ZSTD_ErrorCode error = ZSTD_getErrorCode(result);
return CompressionError(
ZSTD_getErrorString(error), ZstdStrerror(error), error);
}

frame_complete_ = false;
error_ = ZSTD_error_no_error;
error_string_.clear();
error_code_string_.clear();
return {};
}

void ZstdDecompressContext::DoThreadPoolWork() {
Expand Down
21 changes: 21 additions & 0 deletions test/parallel/test-zlib-zstd-dictionary.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,24 @@ for (const dict of [arrayBuffer, uint8, dataView]) {
const decompressed = zlib.zstdDecompressSync(compressed, { dictionary: dict });
assert.strictEqual(decompressed.toString(), input.toString());
}

for (const dictionary of [null, 'string', 123, true, {}, [1, 2, 3]]) {
const options = { dictionary };
const expected = {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
};

assert.throws(() => zlib.createZstdCompress(options), expected);
assert.throws(() => zlib.createZstdDecompress(options), expected);
assert.throws(() => zlib.zstdCompressSync(input, options), expected);
assert.throws(() => zlib.zstdDecompressSync(input, options), expected);
assert.throws(
() => zlib.zstdCompress(input, options, common.mustNotCall()),
expected,
);
assert.throws(
() => zlib.zstdDecompress(input, options, common.mustNotCall()),
expected,
);
}
68 changes: 68 additions & 0 deletions test/parallel/test-zlib-zstd-reset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use strict';

require('../common');
const assert = require('assert');
const { finished } = require('stream/promises');
const test = require('node:test');
const zlib = require('zlib');

const dictionary = Buffer.from(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' +
'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
);
const input = Buffer.from(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. '.repeat(100),
);

async function collect(stream, ...data) {
const chunks = [];
stream.on('data', (chunk) => chunks.push(chunk));
for (let i = 0; i < data.length - 1; i++) {
stream.write(data[i]);
}
stream.end(data[data.length - 1]);
await finished(stream);
return Buffer.concat(chunks);
}

test('ZstdCompress reset preserves its initial options', async () => {
const options = {
dictionary,
pledgedSrcSize: input.length,
params: {
[zlib.constants.ZSTD_c_compressionLevel]: 19,
[zlib.constants.ZSTD_c_checksumFlag]: 1,
},
};
const expected = await collect(zlib.createZstdCompress(options), input);
const reset = zlib.createZstdCompress(options);
reset.reset();

assert.deepStrictEqual(await collect(reset, input), expected);
});

test('ZstdDecompress reset preserves its dictionary', async () => {
const compressed = zlib.zstdCompressSync(input, { dictionary });
const decompress = zlib.createZstdDecompress({ dictionary });
decompress.reset();

assert.deepStrictEqual(await collect(decompress, compressed), input);
});

test('ZstdDecompress reset preserves its parameters', async () => {
const compressed = await collect(zlib.createZstdCompress({
params: {
[zlib.constants.ZSTD_c_windowLog]: 11,
},
}), Buffer.alloc(2048), Buffer.alloc(2048));
const decompress = zlib.createZstdDecompress({
params: {
[zlib.constants.ZSTD_d_windowLogMax]: 10,
},
});
decompress.reset();

await assert.rejects(collect(decompress, compressed), {
code: 'ZSTD_error_frameParameter_windowTooLarge',
});
});
Loading