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
4 changes: 4 additions & 0 deletions build/deps/deps.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
"name": "brotli",
"type": "bazel_dep"
},
{
"name": "zstd",
"type": "bazel_dep"
},
{
"name": "tcmalloc",
"type": "bazel_dep"
Expand Down
3 changes: 3 additions & 0 deletions build/deps/gen/deps.MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,6 @@ git_override(
],
remote = "https://chromium.googlesource.com/chromium/src/third_party/zlib.git",
)

# zstd
bazel_dep(name = "zstd", version = "1.5.7.bcr.1")
6 changes: 6 additions & 0 deletions src/node/internal/internal_errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,12 @@ export class ERR_BROTLI_INVALID_PARAM extends NodeRangeError {
}
}

export class ERR_ZSTD_INVALID_PARAM extends NodeRangeError {
constructor(value: unknown) {
super('ERR_ZSTD_INVALID_PARAM', `${value} is not a valid Zstd parameter`);
}
}

export class ERR_ZLIB_INITIALIZATION_FAILED extends NodeError {
constructor() {
super('ERR_ZLIB_INITIALIZATION_FAILED', 'Initialization failed');
Expand Down
116 changes: 115 additions & 1 deletion src/node/internal/internal_zlib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,21 @@ import {
default as zlibUtil,
type ZlibOptions,
type BrotliOptions,
type ZstdOptions,
} from 'node-internal:zlib';
import { Buffer } from 'node-internal:internal_buffer';
import { validateUint32 } from 'node-internal:validators';
import { ERR_INVALID_ARG_TYPE } from 'node-internal:internal_errors';
import { Zlib, Brotli, type ZlibBase } from 'node-internal:internal_zlib_base';
import {
Zlib,
Brotli,
Zstd,
zstdInitCParamsArray,
zstdInitDParamsArray,
kMaxZstdCParam,
kMaxZstdDParam,
type ZlibBase,
} from 'node-internal:internal_zlib_base';

type ZlibResult = Buffer | { buffer: Buffer; engine: ZlibBase };
type CompressCallback = (err: Error | null, result?: ZlibResult) => void;
Expand All @@ -26,6 +36,8 @@ const {
CONST_UNZIP,
CONST_BROTLI_DECODE,
CONST_BROTLI_ENCODE,
CONST_ZSTD_ENCODE,
CONST_ZSTD_DECODE,
} = zlibUtil;

const ZlibMode = {
Expand Down Expand Up @@ -328,6 +340,88 @@ export function brotliCompress(

processChunkCaptureError(new BrotliCompress(options), data, callback);
}

export function zstdDecompressSync(
data: ArrayBufferView | string,
options: ZstdOptions = {}
): ZlibResult {
if (!options.info) {
// Fast path, where we send the data directly to C++
return Buffer.from(zlibUtil.zstdDecompressSync(data, options));
}

// Else, use the Engine class in sync mode
return processChunk(new ZstdDecompress(options), data);
}

export function zstdCompressSync(
data: ArrayBufferView | string,
options: ZstdOptions = {}
): ZlibResult {
if (!options.info) {
// Fast path, where we send the data directly to C++
return Buffer.from(zlibUtil.zstdCompressSync(data, options));
}

// Else, use the Engine class in sync mode
return processChunk(new ZstdCompress(options), data);
}

export function zstdDecompress(
data: ArrayBufferView | string,
optionsOrCallback: ZstdOptions | CompressCallback,
callbackOrUndefined?: CompressCallback
): void {
const [options, callback] = normalizeArgs(
optionsOrCallback,
callbackOrUndefined
);

if (!options.info) {
// Fast path
zlibUtil.zstdDecompress(data, options, (res) => {
queueMicrotask(() => {
if (res instanceof Error) {
callback(res);
} else {
callback(null, Buffer.from(res));
}
});
});

return;
}

processChunkCaptureError(new ZstdDecompress(options), data, callback);
}

export function zstdCompress(
data: ArrayBufferView | string,
optionsOrCallback: ZstdOptions | CompressCallback,
callbackOrUndefined?: CompressCallback
): void {
const [options, callback] = normalizeArgs(
optionsOrCallback,
callbackOrUndefined
);

if (!options.info) {
// Fast path
zlibUtil.zstdCompress(data, options, (res) => {
queueMicrotask(() => {
if (res instanceof Error) {
callback(res);
} else {
callback(null, Buffer.from(res));
}
});
});

return;
}

processChunkCaptureError(new ZstdCompress(options), data, callback);
}
export class Gzip extends Zlib {
constructor(options: ZlibOptions) {
super(options, CONST_GZIP);
Expand Down Expand Up @@ -385,6 +479,18 @@ export class BrotliDecompress extends Brotli {
}
}

export class ZstdCompress extends Zstd {
constructor(options: ZstdOptions) {
super(options, CONST_ZSTD_ENCODE, zstdInitCParamsArray, kMaxZstdCParam);
}
}

export class ZstdDecompress extends Zstd {
constructor(options: ZstdOptions) {
super(options, CONST_ZSTD_DECODE, zstdInitDParamsArray, kMaxZstdDParam);
}
}

const CLASS_BY_MODE: Record<
(typeof ZlibMode)[keyof typeof ZlibMode],
| typeof Deflate
Expand Down Expand Up @@ -440,3 +546,11 @@ export function createBrotliDecompress(
): BrotliDecompress {
return new BrotliDecompress(options);
}

export function createZstdCompress(options: ZstdOptions): ZstdCompress {
return new ZstdCompress(options);
}

export function createZstdDecompress(options: ZstdOptions): ZstdDecompress {
return new ZstdDecompress(options);
}
103 changes: 98 additions & 5 deletions src/node/internal/internal_zlib_base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
default as zlibUtil,
type ZlibOptions,
type BrotliOptions,
type ZstdOptions,
} from 'node-internal:zlib';
import { Buffer, kMaxLength } from 'node-internal:internal_buffer';
import {
Expand All @@ -18,6 +19,7 @@ import {
ERR_BUFFER_TOO_LARGE,
ERR_INVALID_ARG_TYPE,
ERR_BROTLI_INVALID_PARAM,
ERR_ZSTD_INVALID_PARAM,
ERR_ZLIB_INITIALIZATION_FAILED,
NodeError,
} from 'node-internal:internal_errors';
Expand Down Expand Up @@ -62,20 +64,29 @@ const {
CONST_BROTLI_OPERATION_EMIT_METADATA,
CONST_BROTLI_OPERATION_FINISH,
CONST_BROTLI_OPERATION_FLUSH,
CONST_ZSTD_ENCODE,
CONST_ZSTD_DECODE,
CONST_ZSTD_e_continue,
CONST_ZSTD_e_end,
CONST_ZSTD_e_flush,
} = zlibUtil;

// This type contains all possible handler types.
type ZlibHandleType =
| zlibUtil.ZlibStream
| zlibUtil.BrotliEncoder
| zlibUtil.BrotliDecoder;
| zlibUtil.BrotliDecoder
| zlibUtil.ZstdEncoder
| zlibUtil.ZstdDecoder;
export const owner_symbol = Symbol('owner');

const FLUSH_BOUND_IDX_NORMAL: number = 0;
const FLUSH_BOUND_IDX_BROTLI: number = 1;
const FLUSH_BOUND: [[number, number], [number, number]] = [
const FLUSH_BOUND_IDX_ZSTD: number = 2;
const FLUSH_BOUND: [[number, number], [number, number], [number, number]] = [
[CONST_Z_NO_FLUSH, CONST_Z_BLOCK],
[CONST_BROTLI_OPERATION_PROCESS, CONST_BROTLI_OPERATION_EMIT_METADATA],
[CONST_ZSTD_e_continue, CONST_ZSTD_e_end],
];

const kFlushFlag = Symbol('kFlushFlag');
Expand Down Expand Up @@ -369,10 +380,12 @@ export class ZlibBase extends Transform {
let maxOutputLength = kMaxLength;

let flushBoundIdx;
if (mode !== CONST_BROTLI_ENCODE && mode !== CONST_BROTLI_DECODE) {
flushBoundIdx = FLUSH_BOUND_IDX_NORMAL;
} else {
if (mode === CONST_BROTLI_ENCODE || mode === CONST_BROTLI_DECODE) {
flushBoundIdx = FLUSH_BOUND_IDX_BROTLI;
} else if (mode === CONST_ZSTD_ENCODE || mode === CONST_ZSTD_DECODE) {
flushBoundIdx = FLUSH_BOUND_IDX_ZSTD;
} else {
flushBoundIdx = FLUSH_BOUND_IDX_NORMAL;
}

/* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition */
Expand Down Expand Up @@ -798,3 +811,83 @@ export class Brotli extends ZlibBase {
this._writeState = _writeState;
}
}

export const kMaxZstdCParam = Math.max(
...Object.entries(constants).map(([key, value]) =>
key.startsWith('ZSTD_c_') ? value : 0
)
);
export const zstdInitCParamsArray = new Int32Array(kMaxZstdCParam + 1);

export const kMaxZstdDParam = Math.max(
...Object.entries(constants).map(([key, value]) =>
key.startsWith('ZSTD_d_') ? value : 0
)
);
export const zstdInitDParamsArray = new Int32Array(kMaxZstdDParam + 1);

const zstdDefaultOptions: ZlibDefaultOptions = {
flush: CONST_ZSTD_e_continue,
finishFlush: CONST_ZSTD_e_end,
fullFlush: CONST_ZSTD_e_flush,
};

export class Zstd extends ZlibBase {
constructor(
options: ZstdOptions | undefined | null,
mode: number,
initParamsArray: Int32Array,
maxParam: number
) {
ok(mode === CONST_ZSTD_DECODE || mode === CONST_ZSTD_ENCODE);
initParamsArray.fill(-1);

if (options?.params) {
for (const [origKey, value] of Object.entries(options.params)) {
const key = +origKey;
if (
Number.isNaN(key) ||
key < 0 ||
key > maxParam ||
((initParamsArray[key] as number) | 0) !== -1
) {
throw new ERR_ZSTD_INVALID_PARAM(origKey);
}

if (typeof value !== 'number' && typeof value !== 'boolean') {
throw new ERR_INVALID_ARG_TYPE(
'options.params[key]',
'number',
value
);
}
// as number is required to avoid force type coercion on runtime.
// boolean has number representation, but typescript doesn't understand it.
initParamsArray[key] = value as number;
}
}

const handle =
mode === CONST_ZSTD_DECODE
? new zlibUtil.ZstdDecoder(mode)
: new zlibUtil.ZstdEncoder(mode);

const _writeState = new Uint32Array(2);

const pledgedSrcSize = options?.pledgedSrcSize;

if (
!handle.initialize(
initParamsArray,
_writeState,
processCallback.bind(handle),
pledgedSrcSize
)
) {
throw new ERR_ZLIB_INITIALIZATION_FAILED();
}

super(options ?? {}, mode, handle, zstdDefaultOptions);
this._writeState = _writeState;
}
}
Loading
Loading