diff --git a/benchmark/zlib/creation.js b/benchmark/zlib/creation.js
index 90b22780d2d312..41b1e4917a67bb 100644
--- a/benchmark/zlib/creation.js
+++ b/benchmark/zlib/creation.js
@@ -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],
diff --git a/benchmark/zlib/pipe.js b/benchmark/zlib/pipe.js
index a9c86e3de660a9..5a21c3ff417084 100644
--- a/benchmark/zlib/pipe.js
+++ b/benchmark/zlib/pipe.js
@@ -7,7 +7,7 @@ const bench = common.createBenchmark(main, {
inputLen: [1024],
duration: [5],
type: ['string', 'buffer'],
- algorithm: ['gzip', 'brotli'],
+ algorithm: ['gzip', 'brotli', 'zstd'],
}, {
test: {
inputLen: 1024,
@@ -15,14 +15,19 @@ const bench = common.createBenchmark(main, {
},
});
+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);
diff --git a/doc/api/errors.md b/doc/api/errors.md
index 558bc46088adef..b9cfc10ef12414 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -3800,6 +3800,12 @@ removed: v10.0.0
Used when an attempt is made to use a `zlib` object after it has already been
closed.
+
+
+### `ERR_ZSTD_INVALID_PARAM`
+
+An invalid parameter key was passed during construction of a Zstd stream.
+
### `ERR_CPU_USAGE`
diff --git a/doc/api/zlib.md b/doc/api/zlib.md
index b54fcbaeefe5c2..4ee28f503c78a2 100644
--- a/doc/api/zlib.md
+++ b/doc/api/zlib.md
@@ -124,8 +124,8 @@ operations be cached to avoid duplication of effort.
## Compressing HTTP requests and responses
-The `node:zlib` module can be used to implement support for the `gzip`, `deflate`
-and `br` content-encoding mechanisms defined by
+The `node:zlib` module can be used to implement support for the `gzip`, `deflate`,
+`br` and `zstd` content-encoding mechanisms defined by
[HTTP](https://tools.ietf.org/html/rfc7230#section-4.2).
The HTTP [`Accept-Encoding`][] header is used within an HTTP request to identify
@@ -148,7 +148,7 @@ const { pipeline } = require('node:stream');
const request = http.get({ host: 'example.com',
path: '/',
port: 80,
- headers: { 'Accept-Encoding': 'br,gzip,deflate' } });
+ headers: { 'Accept-Encoding': 'br,gzip,deflate,zstd' } });
request.on('response', (response) => {
const output = fs.createWriteStream('example.com_index.html');
@@ -170,6 +170,9 @@ request.on('response', (response) => {
case 'deflate':
pipeline(response, zlib.createInflate(), output, onError);
break;
+ case 'zstd':
+ pipeline(response, zlib.createZstdDecompress(), output, onError);
+ break;
default:
pipeline(response, output, onError);
break;
@@ -218,6 +221,9 @@ http.createServer((request, response) => {
} else if (/\bbr\b/.test(acceptEncoding)) {
response.writeHead(200, { 'Content-Encoding': 'br' });
pipeline(raw, zlib.createBrotliCompress(), response, onError);
+ } else if (/\bzstd\b/.test(acceptEncoding)) {
+ response.writeHead(200, { 'Content-Encoding': 'zstd' });
+ pipeline(raw, zlib.createZstdCompress(), response, onError);
} else {
response.writeHead(200, {});
pipeline(raw, response, onError);
@@ -238,6 +244,7 @@ const buffer = Buffer.from('eJzT0yMA', 'base64');
zlib.unzip(
buffer,
// For Brotli, the equivalent is zlib.constants.BROTLI_OPERATION_FLUSH.
+ // For Zstd, the equivalent is zlib.constants.ZSTD_e_flush.
{ finishFlush: zlib.constants.Z_SYNC_FLUSH },
(err, buffer) => {
if (err) {
@@ -309,6 +316,16 @@ these options have different ranges than the zlib ones:
See [below][Brotli parameters] for more details on Brotli-specific options.
+### For Zstd-based streams
+
+There are equivalents to the zlib options for Zstd-based streams, although
+these options have different ranges than the zlib ones:
+
+* zlib's `level` option matches Zstd's `ZSTD_c_compressionLevel` option.
+* zlib's `windowBits` option matches Zstd's `ZSTD_c_windowLog` option.
+
+See [below][Zstd parameters] for more details on Zstd-specific options.
+
## Flushing
Calling [`.flush()`][] on a compression stream will make `zlib` return as much
@@ -487,6 +504,50 @@ These advanced options are available for controlling decompression:
* Boolean flag enabling “Large Window Brotli” mode (not compatible with the
Brotli format as standardized in [RFC 7932][]).
+### Zstd constants
+
+
+
+There are several options and other constants available for Zstd-based
+streams:
+
+#### Flush operations
+
+The following values are valid flush operations for Zstd-based streams:
+
+* `zlib.constants.ZSTD_e_continue` (default for all operations)
+* `zlib.constants.ZSTD_e_flush` (default when calling `.flush()`)
+* `zlib.constants.ZSTD_e_end` (default for the last chunk)
+
+#### Compressor options
+
+There are several options that can be set on Zstd encoders, affecting
+compression efficiency and speed. Both the keys and the values can be accessed
+as properties of the `zlib.constants` object.
+
+The most important options are:
+
+* `ZSTD_c_compressionLevel`
+ * Set compression parameters according to pre-defined cLevel table. Default
+ level is ZSTD\_CLEVEL\_DEFAULT==3.
+
+#### Pledged Source Size
+
+It's possible to specify the expected total size of the uncompressed input via
+`opts.pledgedSrcSize`. If the size doesn't match at the end of the input,
+compression will fail with the code `ZSTD_error_srcSize_wrong`.
+
+#### Decompressor options
+
+These advanced options are available for controlling decompression:
+
+* `ZSTD_d_windowLogMax`
+ * Select a size limit (in power of 2) beyond which the streaming API will
+ refuse to allocate memory buffer in order to protect the host from
+ unreasonable memory requirements.
+
## Class: `Options`
+
+
+
+Each Zstd-based class takes an `options` object. All options are optional.
+
+* `flush` {integer} **Default:** `zlib.constants.ZSTD_e_continue`
+* `finishFlush` {integer} **Default:** `zlib.constants.ZSTD_e_end`
+* `chunkSize` {integer} **Default:** `16 * 1024`
+* `params` {Object} Key-value object containing indexed [Zstd parameters][].
+* `maxOutputLength` {integer} Limits output size when using
+ [convenience methods][]. **Default:** [`buffer.kMaxLength`][]
+
+For example:
+
+```js
+const stream = zlib.createZstdCompress({
+ chunkSize: 32 * 1024,
+ params: {
+ [zlib.constants.ZSTD_c_compressionLevel]: 10,
+ [zlib.constants.ZSTD_c_checksumFlag]: 1,
+ },
+});
+```
+
+## Class: `zlib.ZstdCompress`
+
+
+
+Compress data using the Zstd algorithm.
+
+## Class: `zlib.ZstdDecompress`
+
+
+
+Decompress data using the Zstd algorithm.
+
### `zlib.bytesRead`
+
+* `options` {zstd options}
+
+Creates and returns a new [`ZstdCompress`][] object.
+
+## `zlib.createZstdDecompress([options])`
+
+
+
+* `options` {zstd options}
+
+Creates and returns a new [`ZstdDecompress`][] object.
+
## Convenience methods
@@ -1283,11 +1409,54 @@ changes:
Decompress a chunk of data with [`Unzip`][].
+### `zlib.zstdCompress(buffer[, options], callback)`
+
+
+
+* `buffer` {Buffer|TypedArray|DataView|ArrayBuffer|string}
+* `options` {zstd options}
+* `callback` {Function}
+
+### `zlib.zstdCompressSync(buffer[, options])`
+
+
+
+* `buffer` {Buffer|TypedArray|DataView|ArrayBuffer|string}
+* `options` {zstd options}
+
+Compress a chunk of data with [`ZstdCompress`][].
+
+### `zlib.zstdDecompress(buffer[, options], callback)`
+
+
+
+* `buffer` {Buffer|TypedArray|DataView|ArrayBuffer|string}
+* `options` {zstd options}
+* `callback` {Function}
+
+### `zlib.zstdDecompressSync(buffer[, options])`
+
+
+
+* `buffer` {Buffer|TypedArray|DataView|ArrayBuffer|string}
+* `options` {zstd options}
+
+Decompress a chunk of data with [`ZstdDecompress`][].
+
[Brotli parameters]: #brotli-constants
[Cyclic redundancy check]: https://en.wikipedia.org/wiki/Cyclic_redundancy_check
[Memory usage tuning]: #memory-usage-tuning
[RFC 7932]: https://www.rfc-editor.org/rfc/rfc7932.txt
[Streams API]: stream.md
+[Zstd parameters]: #zstd-constants
[`.flush()`]: #zlibflushkind-callback
[`Accept-Encoding`]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
[`ArrayBuffer`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer
@@ -1304,6 +1473,8 @@ Decompress a chunk of data with [`Unzip`][].
[`Inflate`]: #class-zlibinflate
[`TypedArray`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
[`Unzip`]: #class-zlibunzip
+[`ZstdCompress`]: #class-zlibzstdcompress
+[`ZstdDecompress`]: #class-zlibzstddecompress
[`buffer.kMaxLength`]: buffer.md#bufferkmaxlength
[`deflateInit2` and `inflateInit2`]: https://zlib.net/manual.html#Advanced
[`stream.Transform`]: stream.md#class-streamtransform
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 95acfef204734b..309602f3bff48c 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -1887,3 +1887,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);
diff --git a/lib/zlib.js b/lib/zlib.js
index a2c092f1037261..61c5d6d6a8f702 100644
--- a/lib/zlib.js
+++ b/lib/zlib.js
@@ -48,6 +48,7 @@ const {
ERR_INVALID_ARG_TYPE,
ERR_OUT_OF_RANGE,
ERR_ZLIB_INITIALIZATION_FAILED,
+ ERR_ZSTD_INVALID_PARAM,
},
genericNodeError,
} = require('internal/errors');
@@ -89,9 +90,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.
@@ -197,9 +201,11 @@ function zlibOnError(message, errno, code) {
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 }) {
@@ -208,13 +214,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) {
@@ -847,6 +855,91 @@ ObjectSetPrototypeOf(BrotliDecompress.prototype, Brotli.prototype);
ObjectSetPrototypeOf(BrotliDecompress, Brotli);
+const zstdDefaultOpts = {
+ flush: ZSTD_e_continue,
+ finishFlush: ZSTD_e_end,
+ fullFlush: ZSTD_e_flush,
+};
+function Zstd(opts, mode, initParamsArray, maxParam) {
+ assert(mode === ZSTD_COMPRESS || mode === ZSTD_DECOMPRESS);
+
+ TypedArrayPrototypeFill(initParamsArray, -1);
+ if (opts?.params) {
+ ArrayPrototypeForEach(ObjectKeys(opts.params), (origKey) => {
+ const key = +origKey;
+ if (NumberIsNaN(key) || key < 0 || key > maxParam ||
+ (initParamsArray[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]);
+ }
+ initParamsArray[key] = value;
+ });
+ }
+
+ const handle = mode === ZSTD_COMPRESS ?
+ new binding.ZstdCompress() : new binding.ZstdDecompress();
+
+ const pledgedSrcSize = opts?.pledgedSrcSize ?? undefined;
+
+ this._writeState = new Uint32Array(2);
+ if (!handle.init(initParamsArray,
+ pledgedSrcSize,
+ this._writeState,
+ processCallback)) {
+ throw new ERR_ZLIB_INITIALIZATION_FAILED();
+ }
+
+ ReflectApply(ZlibBase, this, [opts, mode, handle, zstdDefaultOpts]);
+}
+ObjectSetPrototypeOf(Zstd.prototype, ZlibBase.prototype);
+ObjectSetPrototypeOf(Zstd, ZlibBase);
+
+
+const kMaxZstdCParam = MathMaxApply(ArrayPrototypeMap(
+ ObjectKeys(constants),
+ (key) => (StringPrototypeStartsWith(key, 'ZSTD_c_') ?
+ constants[key] :
+ 0),
+));
+
+const zstdInitCParamsArray = new Uint32Array(kMaxZstdCParam + 1);
+
+function ZstdCompress(opts) {
+ if (!(this instanceof ZstdCompress))
+ return new ZstdCompress(opts);
+
+ ReflectApply(Zstd, this,
+ [opts, ZSTD_COMPRESS, zstdInitCParamsArray, kMaxZstdCParam]);
+}
+ObjectSetPrototypeOf(ZstdCompress.prototype, Zstd.prototype);
+ObjectSetPrototypeOf(ZstdCompress, Zstd);
+
+
+const kMaxZstdDParam = MathMaxApply(ArrayPrototypeMap(
+ ObjectKeys(constants),
+ (key) => (StringPrototypeStartsWith(key, 'ZSTD_d_') ?
+ constants[key] :
+ 0),
+));
+
+const zstdInitDParamsArray = new Uint32Array(kMaxZstdDParam + 1);
+
+function ZstdDecompress(opts) {
+ if (!(this instanceof ZstdDecompress))
+ return new ZstdDecompress(opts);
+
+ ReflectApply(Zstd, this,
+ [opts, ZSTD_DECOMPRESS, zstdInitDParamsArray, kMaxZstdDParam]);
+}
+ObjectSetPrototypeOf(ZstdDecompress.prototype, Zstd.prototype);
+ObjectSetPrototypeOf(ZstdDecompress, Zstd);
+
+
function createProperty(ctor) {
return {
__proto__: null,
@@ -885,6 +978,8 @@ module.exports = {
Unzip,
BrotliCompress,
BrotliDecompress,
+ ZstdCompress,
+ ZstdDecompress,
// Convenience methods.
// compress/decompress a string or buffer in one step.
@@ -906,6 +1001,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, {
@@ -918,6 +1017,8 @@ ObjectDefineProperties(module.exports, {
createUnzip: createProperty(Unzip),
createBrotliCompress: createProperty(BrotliCompress),
createBrotliDecompress: createProperty(BrotliDecompress),
+ createZstdCompress: createProperty(ZstdCompress),
+ createZstdDecompress: createProperty(ZstdDecompress),
constants: {
__proto__: null,
configurable: false,
diff --git a/src/node_zlib.cc b/src/node_zlib.cc
index 3ff3ac13112330..4ebd6f746ff6d3 100644
--- a/src/node_zlib.cc
+++ b/src/node_zlib.cc
@@ -31,9 +31,11 @@
#include "v8.h"
-#include "brotli/encode.h"
#include "brotli/decode.h"
+#include "brotli/encode.h"
#include "zlib.h"
+#include "zstd.h"
+#include "zstd_errors.h"
#include
@@ -94,6 +96,44 @@ inline const char* ZlibStrerror(int err) {
return "Z_UNKNOWN_ERROR";
}
+#define ZSTD_ERROR_CODES(V) \
+ V(ZSTD_error_no_error) \
+ V(ZSTD_error_GENERIC) \
+ V(ZSTD_error_prefix_unknown) \
+ V(ZSTD_error_version_unsupported) \
+ V(ZSTD_error_frameParameter_unsupported) \
+ V(ZSTD_error_frameParameter_windowTooLarge) \
+ V(ZSTD_error_corruption_detected) \
+ V(ZSTD_error_checksum_wrong) \
+ V(ZSTD_error_literals_headerWrong) \
+ V(ZSTD_error_dictionary_corrupted) \
+ V(ZSTD_error_dictionary_wrong) \
+ V(ZSTD_error_dictionaryCreation_failed) \
+ V(ZSTD_error_parameter_unsupported) \
+ V(ZSTD_error_parameter_combination_unsupported) \
+ V(ZSTD_error_parameter_outOfBound) \
+ V(ZSTD_error_tableLog_tooLarge) \
+ V(ZSTD_error_maxSymbolValue_tooLarge) \
+ V(ZSTD_error_maxSymbolValue_tooSmall) \
+ V(ZSTD_error_stabilityCondition_notRespected) \
+ V(ZSTD_error_stage_wrong) \
+ V(ZSTD_error_init_missing) \
+ V(ZSTD_error_memory_allocation) \
+ V(ZSTD_error_workSpace_tooSmall) \
+ V(ZSTD_error_dstSize_tooSmall) \
+ V(ZSTD_error_srcSize_wrong) \
+ V(ZSTD_error_dstBuffer_null) \
+ V(ZSTD_error_noForwardProgress_destFull) \
+ V(ZSTD_error_noForwardProgress_inputEmpty)
+
+inline const char* ZstdStrerror(int err) {
+#define V(code) \
+ if (err == code) return #code;
+ ZSTD_ERROR_CODES(V)
+#undef V
+ return "ZSTD_error_GENERIC";
+}
+
enum node_zlib_mode {
NONE,
DEFLATE,
@@ -104,7 +144,9 @@ enum node_zlib_mode {
INFLATERAW,
UNZIP,
BROTLI_DECODE,
- BROTLI_ENCODE
+ BROTLI_ENCODE,
+ ZSTD_COMPRESS,
+ ZSTD_DECOMPRESS
};
constexpr uint8_t GZIP_HEADER_ID1 = 0x1f;
@@ -248,6 +290,79 @@ class BrotliDecoderContext final : public BrotliContext {
DeleteFnPtr state_;
};
+class ZstdContext : public MemoryRetainer {
+ public:
+ ZstdContext() = default;
+
+ // Streaming-related, should be available for all compression libraries:
+ void Close();
+ void SetBuffers(const char* in, uint32_t in_len, char* out, uint32_t out_len);
+ void SetFlush(int flush);
+ void GetAfterWriteOffsets(uint32_t* avail_in, uint32_t* avail_out) const;
+ CompressionError GetErrorInfo() const;
+
+ ZstdContext(const ZstdContext&) = delete;
+ ZstdContext& operator=(const ZstdContext&) = delete;
+
+ protected:
+ ZSTD_EndDirective flush_ = ZSTD_e_continue;
+
+ ZSTD_inBuffer input_ = {nullptr, 0, 0};
+ ZSTD_outBuffer output_ = {nullptr, 0, 0};
+
+ ZSTD_ErrorCode error_ = ZSTD_error_no_error;
+ std::string error_string_;
+ std::string error_code_string_;
+};
+
+class ZstdCompressContext final : public ZstdContext {
+ public:
+ ZstdCompressContext() = default;
+
+ // Streaming-related, should be available for all compression libraries:
+ void DoThreadPoolWork();
+ CompressionError ResetStream();
+
+ // Zstd specific:
+ CompressionError Init(uint64_t pledged_src_size);
+ CompressionError SetParameter(int key, int value);
+
+ // Wrap ZSTD_freeCCtx to remove the return type.
+ static void FreeCCtx(ZSTD_CCtx* cctx) { ZSTD_freeCCtx(cctx); }
+
+ SET_MEMORY_INFO_NAME(ZstdCompressContext)
+ SET_SELF_SIZE(ZstdCompressContext)
+ SET_NO_MEMORY_INFO()
+
+ private:
+ DeleteFnPtr cctx_;
+
+ uint64_t pledged_src_size_ = ZSTD_CONTENTSIZE_UNKNOWN;
+};
+
+class ZstdDecompressContext final : public ZstdContext {
+ public:
+ ZstdDecompressContext() = default;
+
+ // Streaming-related, should be available for all compression libraries:
+ void DoThreadPoolWork();
+ CompressionError ResetStream();
+
+ // Zstd specific:
+ CompressionError Init(uint64_t pledged_src_size);
+ CompressionError SetParameter(int key, int value);
+
+ // Wrap ZSTD_freeDCtx to remove the return type.
+ static void FreeDCtx(ZSTD_DCtx* dctx) { ZSTD_freeDCtx(dctx); }
+
+ SET_MEMORY_INFO_NAME(ZstdDecompressContext)
+ SET_SELF_SIZE(ZstdDecompressContext)
+ SET_NO_MEMORY_INFO()
+
+ private:
+ DeleteFnPtr dctx_;
+};
+
template
class CompressionStream : public AsyncWrap, public ThreadPoolWork {
public:
@@ -730,6 +845,92 @@ class BrotliCompressionStream final :
using BrotliEncoderStream = BrotliCompressionStream;
using BrotliDecoderStream = BrotliCompressionStream;
+template
+class ZstdStream final : public CompressionStream {
+ public:
+ ZstdStream(Environment* env, Local