node:zlib: add zstd (#20313)

Co-authored-by: nektro <5464072+nektro@users.noreply.github.com>
This commit is contained in:
Meghan Denny
2025-06-12 13:20:28 -08:00
committed by GitHub
parent dedd433cbf
commit 5763a8e533
36 changed files with 1049 additions and 268 deletions

View File

@@ -21,16 +21,16 @@ jobs:
set -euo pipefail set -euo pipefail
# Extract the commit hash from the line after COMMIT # Extract the commit hash from the line after COMMIT
CURRENT_VERSION=$(awk '/[[:space:]]*COMMIT[[:space:]]*$/{getline; gsub(/^[[:space:]]+|[[:space:]]+$/,"",$0); print}' cmake/targets/BuildZstd.cmake) CURRENT_VERSION=$(awk '/[[:space:]]*COMMIT[[:space:]]*$/{getline; gsub(/^[[:space:]]+|[[:space:]]+$/,"",$0); print}' cmake/targets/CloneZstd.cmake)
if [ -z "$CURRENT_VERSION" ]; then if [ -z "$CURRENT_VERSION" ]; then
echo "Error: Could not find COMMIT line in BuildZstd.cmake" echo "Error: Could not find COMMIT line in CloneZstd.cmake"
exit 1 exit 1
fi fi
# Validate that it looks like a git hash # Validate that it looks like a git hash
if ! [[ $CURRENT_VERSION =~ ^[0-9a-f]{40}$ ]]; then if ! [[ $CURRENT_VERSION =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid git hash format in BuildZstd.cmake" echo "Error: Invalid git hash format in CloneZstd.cmake"
echo "Found: $CURRENT_VERSION" echo "Found: $CURRENT_VERSION"
echo "Expected: 40 character hexadecimal string" echo "Expected: 40 character hexadecimal string"
exit 1 exit 1
@@ -76,7 +76,7 @@ jobs:
run: | run: |
set -euo pipefail set -euo pipefail
# Handle multi-line format where COMMIT and its value are on separate lines # Handle multi-line format where COMMIT and its value are on separate lines
sed -i -E '/[[:space:]]*COMMIT[[:space:]]*$/{n;s/[[:space:]]*([0-9a-f]+)[[:space:]]*$/ ${{ steps.check-version.outputs.latest }}/}' cmake/targets/BuildZstd.cmake sed -i -E '/[[:space:]]*COMMIT[[:space:]]*$/{n;s/[[:space:]]*([0-9a-f]+)[[:space:]]*$/ ${{ steps.check-version.outputs.latest }}/}' cmake/targets/CloneZstd.cmake
- name: Create Pull Request - name: Create Pull Request
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
@@ -84,7 +84,7 @@ jobs:
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
add-paths: | add-paths: |
cmake/targets/BuildZstd.cmake cmake/targets/CloneZstd.cmake
commit-message: "deps: update zstd to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})" commit-message: "deps: update zstd to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update zstd to ${{ steps.check-version.outputs.tag }}" title: "deps: update zstd to ${{ steps.check-version.outputs.tag }}"
delete-branch: true delete-branch: true

View File

@@ -513,6 +513,8 @@ fn getTranslateC(b: *Build, initial_target: std.Build.ResolvedTarget, optimize:
translate_c.defineCMacroRaw(b.fmt("{s}={d}", .{ str, @intFromBool(value) })); translate_c.defineCMacroRaw(b.fmt("{s}={d}", .{ str, @intFromBool(value) }));
} }
translate_c.addIncludePath(b.path("vendor/zstd/lib"));
if (target.result.os.tag == .windows) { if (target.result.os.tag == .windows) {
// translate-c is unable to translate the unsuffixed windows functions // translate-c is unable to translate the unsuffixed windows functions
// like `SetCurrentDirectory` since they are defined with an odd macro // like `SetCurrentDirectory` since they are defined with an odd macro

View File

@@ -199,6 +199,7 @@ src/bun.js/node/util/parse_args_utils.zig
src/bun.js/node/util/parse_args.zig src/bun.js/node/util/parse_args.zig
src/bun.js/node/util/validators.zig src/bun.js/node/util/validators.zig
src/bun.js/node/win_watcher.zig src/bun.js/node/win_watcher.zig
src/bun.js/node/zlib/NativeZstd.zig
src/bun.js/ProcessAutoKiller.zig src/bun.js/ProcessAutoKiller.zig
src/bun.js/rare_data.zig src/bun.js/rare_data.zig
src/bun.js/ResolveMessage.zig src/bun.js/ResolveMessage.zig

View File

@@ -42,6 +42,29 @@ else()
set(CONFIGURE_DEPENDS "") set(CONFIGURE_DEPENDS "")
endif() endif()
# --- Dependencies ---
set(BUN_DEPENDENCIES
BoringSSL
Brotli
Cares
Highway
LibDeflate
LolHtml
Lshpack
Mimalloc
TinyCC
Zlib
LibArchive # must be loaded after zlib
HdrHistogram # must be loaded after zlib
Zstd
)
include(CloneZstd)
# foreach(dependency ${BUN_DEPENDENCIES})
# include(Clone${dependency})
# endforeach()
# --- Codegen --- # --- Codegen ---
set(BUN_ERROR_SOURCE ${CWD}/packages/bun-error) set(BUN_ERROR_SOURCE ${CWD}/packages/bun-error)
@@ -582,6 +605,7 @@ register_command(
${BUN_ZIG_OUTPUT} ${BUN_ZIG_OUTPUT}
TARGETS TARGETS
clone-zig clone-zig
clone-zstd
SOURCES SOURCES
${BUN_ZIG_SOURCES} ${BUN_ZIG_SOURCES}
${BUN_ZIG_GENERATED_SOURCES} ${BUN_ZIG_GENERATED_SOURCES}
@@ -1054,22 +1078,6 @@ endif()
# --- Dependencies --- # --- Dependencies ---
set(BUN_DEPENDENCIES
BoringSSL
Brotli
Cares
Highway
LibDeflate
LolHtml
Lshpack
Mimalloc
TinyCC
Zlib
LibArchive # must be loaded after zlib
HdrHistogram # must be loaded after zlib
Zstd
)
if(WIN32) if(WIN32)
list(APPEND BUN_DEPENDENCIES Libuv) list(APPEND BUN_DEPENDENCIES Libuv)
endif() endif()

View File

@@ -1,12 +1,3 @@
register_repository(
NAME
zstd
REPOSITORY
facebook/zstd
COMMIT
f8745da6ff1ad1e7bab384bd1f9d742439278e99
)
register_cmake_command( register_cmake_command(
TARGET TARGET
zstd zstd
@@ -23,4 +14,6 @@ register_cmake_command(
LIBRARIES LIBRARIES
zstd_static WIN32 zstd_static WIN32
zstd UNIX zstd UNIX
INCLUDES
lib
) )

View File

@@ -0,0 +1,8 @@
register_repository(
NAME
zstd
REPOSITORY
facebook/zstd
COMMIT
f8745da6ff1ad1e7bab384bd1f9d742439278e99
)

View File

@@ -44,6 +44,7 @@ pub const SocketHandlers = @import("api/bun/socket.zig").Handlers;
pub const UDPSocket = @import("api/bun/udp_socket.zig").UDPSocket; pub const UDPSocket = @import("api/bun/udp_socket.zig").UDPSocket;
pub const Valkey = @import("../valkey/js_valkey.zig").JSValkeyClient; pub const Valkey = @import("../valkey/js_valkey.zig").JSValkeyClient;
pub const BlockList = @import("./node/net/BlockList.zig"); pub const BlockList = @import("./node/net/BlockList.zig");
pub const NativeZstd = @import("./node/zlib/NativeZstd.zig");
pub const napi = @import("../napi/napi.zig"); pub const napi = @import("../napi/napi.zig");

View File

@@ -24,4 +24,4 @@ function generate(name: string) {
}); });
} }
export default [generate("NativeZlib"), generate("NativeBrotli")]; export default [generate("NativeZlib"), generate("NativeBrotli"), generate("NativeZstd")];

View File

@@ -2263,6 +2263,14 @@ JSC_DEFINE_HOST_FUNCTION(Bun::jsFunctionMakeErrorWithCode, (JSC::JSGlobalObject
return JSC::JSValue::encode(error); return JSC::JSValue::encode(error);
} }
case Bun::ErrorCode::ERR_ZSTD_INVALID_PARAM: {
auto arg0 = callFrame->argument(1);
auto str0 = arg0.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, {});
auto message = makeString(str0, " is not a valid zstd parameter"_s);
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_ZSTD_INVALID_PARAM, message));
}
case ErrorCode::ERR_IPC_DISCONNECTED: case ErrorCode::ERR_IPC_DISCONNECTED:
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_IPC_DISCONNECTED, "IPC channel is already disconnected"_s)); return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_IPC_DISCONNECTED, "IPC channel is already disconnected"_s));
case ErrorCode::ERR_SERVER_NOT_RUNNING: case ErrorCode::ERR_SERVER_NOT_RUNNING:

View File

@@ -254,6 +254,7 @@ const errors: ErrorCodeMapping = [
["ERR_UNKNOWN_CREDENTIAL", Error], ["ERR_UNKNOWN_CREDENTIAL", Error],
["ERR_UNKNOWN_ENCODING", TypeError], ["ERR_UNKNOWN_ENCODING", TypeError],
["ERR_UNKNOWN_SIGNAL", TypeError], ["ERR_UNKNOWN_SIGNAL", TypeError],
["ERR_ZSTD_INVALID_PARAM", RangeError],
["ERR_USE_AFTER_CLOSE", Error], ["ERR_USE_AFTER_CLOSE", Error],
["ERR_WASI_NOT_STARTED", Error], ["ERR_WASI_NOT_STARTED", Error],
["ERR_WORKER_INIT_FAILED", Error], ["ERR_WORKER_INIT_FAILED", Error],

View File

@@ -10,6 +10,7 @@
#include <zlib.h> #include <zlib.h>
#include <brotli/encode.h> #include <brotli/encode.h>
#include <brotli/decode.h> #include <brotli/decode.h>
#include <zstd.h>
#include <fcntl.h> #include <fcntl.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
@@ -1016,6 +1017,8 @@ static JSValue processBindingConstantsGetZlib(VM& vm, JSObject* bindingObject)
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "UNZIP"_s)), jsNumber(7)); object->putDirect(vm, PropertyName(Identifier::fromString(vm, "UNZIP"_s)), jsNumber(7));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODE"_s)), jsNumber(8)); object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODE"_s)), jsNumber(8));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_ENCODE"_s)), jsNumber(9)); object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_ENCODE"_s)), jsNumber(9));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_COMPRESS"_s)), jsNumber(10));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_DECOMPRESS"_s)), jsNumber(11));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "Z_MIN_WINDOWBITS"_s)), jsNumber(8)); object->putDirect(vm, PropertyName(Identifier::fromString(vm, "Z_MIN_WINDOWBITS"_s)), jsNumber(8));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "Z_MAX_WINDOWBITS"_s)), jsNumber(15)); object->putDirect(vm, PropertyName(Identifier::fromString(vm, "Z_MAX_WINDOWBITS"_s)), jsNumber(15));
@@ -1092,6 +1095,69 @@ static JSValue processBindingConstantsGetZlib(VM& vm, JSObject* bindingObject)
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES"_s)), jsNumber(BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES)); object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES"_s)), jsNumber(BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_UNREACHABLE"_s)), jsNumber(BROTLI_DECODER_ERROR_UNREACHABLE)); object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_UNREACHABLE"_s)), jsNumber(BROTLI_DECODER_ERROR_UNREACHABLE));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_e_continue"_s)), jsNumber(ZSTD_e_continue));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_e_flush"_s)), jsNumber(ZSTD_e_flush));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_e_end"_s)), jsNumber(ZSTD_e_end));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_fast"_s)), jsNumber(ZSTD_fast));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_dfast"_s)), jsNumber(ZSTD_dfast));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_greedy"_s)), jsNumber(ZSTD_greedy));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_lazy"_s)), jsNumber(ZSTD_lazy));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_lazy2"_s)), jsNumber(ZSTD_lazy2));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_btlazy2"_s)), jsNumber(ZSTD_btlazy2));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_btopt"_s)), jsNumber(ZSTD_btopt));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_btultra"_s)), jsNumber(ZSTD_btultra));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_btultra2"_s)), jsNumber(ZSTD_btultra2));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_c_compressionLevel"_s)), jsNumber(ZSTD_c_compressionLevel));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_c_windowLog"_s)), jsNumber(ZSTD_c_windowLog));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_c_hashLog"_s)), jsNumber(ZSTD_c_hashLog));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_c_chainLog"_s)), jsNumber(ZSTD_c_chainLog));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_c_searchLog"_s)), jsNumber(ZSTD_c_searchLog));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_c_minMatch"_s)), jsNumber(ZSTD_c_minMatch));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_c_targetLength"_s)), jsNumber(ZSTD_c_targetLength));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_c_strategy"_s)), jsNumber(ZSTD_c_strategy));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_c_enableLongDistanceMatching"_s)), jsNumber(ZSTD_c_enableLongDistanceMatching));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_c_ldmHashLog"_s)), jsNumber(ZSTD_c_ldmHashLog));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_c_ldmMinMatch"_s)), jsNumber(ZSTD_c_ldmMinMatch));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_c_ldmBucketSizeLog"_s)), jsNumber(ZSTD_c_ldmBucketSizeLog));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_c_ldmHashRateLog"_s)), jsNumber(ZSTD_c_ldmHashRateLog));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_c_contentSizeFlag"_s)), jsNumber(ZSTD_c_contentSizeFlag));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_c_checksumFlag"_s)), jsNumber(ZSTD_c_checksumFlag));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_c_dictIDFlag"_s)), jsNumber(ZSTD_c_dictIDFlag));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_c_nbWorkers"_s)), jsNumber(ZSTD_c_nbWorkers));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_c_jobSize"_s)), jsNumber(ZSTD_c_jobSize));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_c_overlapLog"_s)), jsNumber(ZSTD_c_overlapLog));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_d_windowLogMax"_s)), jsNumber(ZSTD_d_windowLogMax));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_CLEVEL_DEFAULT"_s)), jsNumber(ZSTD_CLEVEL_DEFAULT));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_no_error"_s)), jsNumber(ZSTD_error_no_error));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_GENERIC"_s)), jsNumber(ZSTD_error_GENERIC));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_prefix_unknown"_s)), jsNumber(ZSTD_error_prefix_unknown));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_version_unsupported"_s)), jsNumber(ZSTD_error_version_unsupported));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_frameParameter_unsupported"_s)), jsNumber(ZSTD_error_frameParameter_unsupported));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_frameParameter_windowTooLarge"_s)), jsNumber(ZSTD_error_frameParameter_windowTooLarge));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_corruption_detected"_s)), jsNumber(ZSTD_error_corruption_detected));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_checksum_wrong"_s)), jsNumber(ZSTD_error_checksum_wrong));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_literals_headerWrong"_s)), jsNumber(ZSTD_error_literals_headerWrong));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_dictionary_corrupted"_s)), jsNumber(ZSTD_error_dictionary_corrupted));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_dictionary_wrong"_s)), jsNumber(ZSTD_error_dictionary_wrong));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_dictionaryCreation_failed"_s)), jsNumber(ZSTD_error_dictionaryCreation_failed));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_parameter_unsupported"_s)), jsNumber(ZSTD_error_parameter_unsupported));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_parameter_combination_unsupported"_s)), jsNumber(ZSTD_error_parameter_combination_unsupported));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_parameter_outOfBound"_s)), jsNumber(ZSTD_error_parameter_outOfBound));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_tableLog_tooLarge"_s)), jsNumber(ZSTD_error_tableLog_tooLarge));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_maxSymbolValue_tooLarge"_s)), jsNumber(ZSTD_error_maxSymbolValue_tooLarge));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_maxSymbolValue_tooSmall"_s)), jsNumber(ZSTD_error_maxSymbolValue_tooSmall));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_stabilityCondition_notRespected"_s)), jsNumber(ZSTD_error_stabilityCondition_notRespected));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_stage_wrong"_s)), jsNumber(ZSTD_error_stage_wrong));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_init_missing"_s)), jsNumber(ZSTD_error_init_missing));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_memory_allocation"_s)), jsNumber(ZSTD_error_memory_allocation));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_workSpace_tooSmall"_s)), jsNumber(ZSTD_error_workSpace_tooSmall));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_dstSize_tooSmall"_s)), jsNumber(ZSTD_error_dstSize_tooSmall));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_srcSize_wrong"_s)), jsNumber(ZSTD_error_srcSize_wrong));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_dstBuffer_null"_s)), jsNumber(ZSTD_error_dstBuffer_null));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_noForwardProgress_destFull"_s)), jsNumber(ZSTD_error_noForwardProgress_destFull));
object->putDirect(vm, PropertyName(Identifier::fromString(vm, "ZSTD_error_noForwardProgress_inputEmpty"_s)), jsNumber(ZSTD_error_noForwardProgress_inputEmpty));
return object; return object;
} }

View File

@@ -86,4 +86,5 @@ pub const Classes = struct {
pub const HTMLBundle = api.HTMLBundle; pub const HTMLBundle = api.HTMLBundle;
pub const RedisClient = api.Valkey; pub const RedisClient = api.Valkey;
pub const BlockList = api.BlockList; pub const BlockList = api.BlockList;
pub const NativeZstd = api.NativeZstd;
}; };

View File

@@ -42,6 +42,7 @@ pub const Task = TaggedPointerUnion(.{
NapiFinalizerTask, NapiFinalizerTask,
NativeBrotli, NativeBrotli,
NativeZlib, NativeZlib,
NativeZstd,
Open, Open,
PollPendingModulesTask, PollPendingModulesTask,
PosixSignalTask, PosixSignalTask,
@@ -448,6 +449,10 @@ pub fn tickQueueWithCount(this: *EventLoop, virtual_machine: *VirtualMachine) u3
var any: *NativeBrotli = task.get(NativeBrotli).?; var any: *NativeBrotli = task.get(NativeBrotli).?;
any.runFromJSThread(); any.runFromJSThread();
}, },
@field(Task.Tag, @typeName(NativeZstd)) => {
var any: *NativeZstd = task.get(NativeZstd).?;
any.runFromJSThread();
},
@field(Task.Tag, @typeName(ProcessWaiterThreadTask)) => { @field(Task.Tag, @typeName(ProcessWaiterThreadTask)) => {
bun.markPosixOnly(); bun.markPosixOnly();
var any: *ProcessWaiterThreadTask = task.get(ProcessWaiterThreadTask).?; var any: *ProcessWaiterThreadTask = task.get(ProcessWaiterThreadTask).?;
@@ -558,6 +563,7 @@ const StatFS = AsyncFS.statfs;
const Unlink = AsyncFS.unlink; const Unlink = AsyncFS.unlink;
const NativeZlib = JSC.API.NativeZlib; const NativeZlib = JSC.API.NativeZlib;
const NativeBrotli = JSC.API.NativeBrotli; const NativeBrotli = JSC.API.NativeBrotli;
const NativeZstd = JSC.API.NativeZstd;
const ShellGlobTask = shell.interpret.Interpreter.Expansion.ShellGlobTask; const ShellGlobTask = shell.interpret.Interpreter.Expansion.ShellGlobTask;
const ShellRmTask = shell.Interpreter.Builtin.Rm.ShellRmTask; const ShellRmTask = shell.Interpreter.Builtin.Rm.ShellRmTask;

View File

@@ -264,7 +264,7 @@ pub fn CompressionStream(comptime T: type) type {
return false; return false;
} }
fn emitError(this: *T, globalThis: *JSC.JSGlobalObject, this_value: JSC.JSValue, err_: Error) !void { pub fn emitError(this: *T, globalThis: *JSC.JSGlobalObject, this_value: JSC.JSValue, err_: Error) !void {
var msg_str = bun.String.createFormat("{s}", .{std.mem.sliceTo(err_.msg, 0) orelse ""}) catch bun.outOfMemory(); var msg_str = bun.String.createFormat("{s}", .{std.mem.sliceTo(err_.msg, 0) orelse ""}) catch bun.outOfMemory();
const msg_value = msg_str.transferToJS(globalThis); const msg_value = msg_str.transferToJS(globalThis);
const err_value = JSC.jsNumber(err_.err); const err_value = JSC.jsNumber(err_.err);
@@ -287,7 +287,7 @@ pub fn CompressionStream(comptime T: type) type {
pub const NativeZlib = JSC.Codegen.JSNativeZlib.getConstructor; pub const NativeZlib = JSC.Codegen.JSNativeZlib.getConstructor;
const CountedKeepAlive = struct { pub const CountedKeepAlive = struct {
keep_alive: bun.Async.KeepAlive = .{}, keep_alive: bun.Async.KeepAlive = .{},
ref_count: u32 = 0, ref_count: u32 = 0,
@@ -428,7 +428,7 @@ pub const SNativeZlib = struct {
} }
}; };
const Error = struct { pub const Error = struct {
msg: ?[*:0]const u8, msg: ?[*:0]const u8,
err: c_int, err: c_int,
code: ?[*:0]const u8, code: ?[*:0]const u8,
@@ -471,6 +471,7 @@ const ZlibContext = struct {
.UNZIP => windowBits + 32, .UNZIP => windowBits + 32,
.DEFLATERAW, .INFLATERAW => windowBits * -1, .DEFLATERAW, .INFLATERAW => windowBits * -1,
.BROTLI_DECODE, .BROTLI_ENCODE => unreachable, .BROTLI_DECODE, .BROTLI_ENCODE => unreachable,
.ZSTD_COMPRESS, .ZSTD_DECOMPRESS => unreachable,
}; };
this.dictionary = dictionary orelse ""; this.dictionary = dictionary orelse "";
@@ -479,8 +480,8 @@ const ZlibContext = struct {
.NONE => unreachable, .NONE => unreachable,
.DEFLATE, .GZIP, .DEFLATERAW => this.err = c.deflateInit2_(&this.state, level, 8, windowBitsActual, memLevel, strategy, c.zlibVersion(), @sizeOf(c.z_stream)), .DEFLATE, .GZIP, .DEFLATERAW => this.err = c.deflateInit2_(&this.state, level, 8, windowBitsActual, memLevel, strategy, c.zlibVersion(), @sizeOf(c.z_stream)),
.INFLATE, .GUNZIP, .UNZIP, .INFLATERAW => this.err = c.inflateInit2_(&this.state, windowBitsActual, c.zlibVersion(), @sizeOf(c.z_stream)), .INFLATE, .GUNZIP, .UNZIP, .INFLATERAW => this.err = c.inflateInit2_(&this.state, windowBitsActual, c.zlibVersion(), @sizeOf(c.z_stream)),
.BROTLI_DECODE => @panic("TODO"), .BROTLI_DECODE, .BROTLI_ENCODE => unreachable,
.BROTLI_ENCODE => @panic("TODO"), .ZSTD_COMPRESS, .ZSTD_DECOMPRESS => unreachable,
} }
if (this.err != .Ok) { if (this.err != .Ok) {
this.mode = .NONE; this.mode = .NONE;
@@ -619,6 +620,7 @@ const ZlibContext = struct {
}, },
.NONE => {}, .NONE => {},
.BROTLI_ENCODE, .BROTLI_DECODE => {}, .BROTLI_ENCODE, .BROTLI_DECODE => {},
.ZSTD_COMPRESS, .ZSTD_DECOMPRESS => {},
} }
} }
@@ -684,6 +686,7 @@ const ZlibContext = struct {
}, },
.NONE => {}, .NONE => {},
.BROTLI_ENCODE, .BROTLI_DECODE => {}, .BROTLI_ENCODE, .BROTLI_DECODE => {},
.ZSTD_COMPRESS, .ZSTD_DECOMPRESS => {},
} }
bun.assert(status == .Ok or status == .DataError); bun.assert(status == .Ok or status == .DataError);
this.mode = .NONE; this.mode = .NONE;
@@ -722,9 +725,7 @@ pub const SNativeBrotli = struct {
write_in_progress: bool = false, write_in_progress: bool = false,
pending_close: bool = false, pending_close: bool = false,
closed: bool = false, closed: bool = false,
task: JSC.WorkPoolTask = .{ task: JSC.WorkPoolTask = .{ .callback = undefined },
.callback = undefined,
},
pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!*@This() { pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!*@This() {
const arguments = callframe.argumentsUndef(1).ptr; const arguments = callframe.argumentsUndef(1).ptr;
@@ -960,3 +961,5 @@ const BrotliContext = struct {
unreachable; unreachable;
} }
}; };
pub const NativeZstd = JSC.Codegen.JSNativeZstd.getConstructor;

View File

@@ -0,0 +1,266 @@
const std = @import("std");
const bun = @import("bun");
const JSC = bun.JSC;
const CompressionStream = @import("./../node_zlib_binding.zig").CompressionStream;
const CountedKeepAlive = @import("./../node_zlib_binding.zig").CountedKeepAlive;
const Error = @import("./../node_zlib_binding.zig").Error;
const validators = @import("./../util/validators.zig");
const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{});
pub const ref = RefCount.ref;
pub const deref = RefCount.deref;
pub const js = JSC.Codegen.JSNativeZstd;
pub const toJS = js.toJS;
pub const fromJS = js.fromJS;
pub const fromJSDirect = js.fromJSDirect;
const impl = CompressionStream(@This());
pub const write = impl.write;
pub const runFromJSThread = impl.runFromJSThread;
pub const writeSync = impl.writeSync;
pub const reset = impl.reset;
pub const close = impl.close;
pub const setOnError = impl.setOnError;
pub const getOnError = impl.getOnError;
pub const finalize = impl.finalize;
ref_count: RefCount,
mode: bun.zlib.NodeMode,
globalThis: *JSC.JSGlobalObject,
stream: Context = .{},
write_result: ?[*]u32 = null,
poll_ref: CountedKeepAlive = .{},
this_value: JSC.Strong.Optional = .empty,
write_in_progress: bool = false,
pending_close: bool = false,
closed: bool = false,
task: JSC.WorkPoolTask = .{ .callback = undefined },
pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!*@This() {
const arguments = callframe.argumentsAsArray(1);
var mode = arguments[0];
if (!mode.isNumber()) {
return globalThis.throwInvalidArgumentTypeValue("mode", "number", mode);
}
const mode_double = mode.asNumber();
if (@mod(mode_double, 1.0) != 0.0) {
return globalThis.throwInvalidArgumentTypeValue("mode", "integer", mode);
}
const mode_int: i64 = @intFromFloat(mode_double);
if (mode_int < 10 or mode_int > 11) {
return globalThis.throwRangeError(mode_int, .{ .field_name = "mode", .min = 10, .max = 11 });
}
const ptr = bun.new(@This(), .{
.ref_count = .init(),
.mode = @enumFromInt(mode_int),
.globalThis = globalThis,
});
ptr.stream.mode = ptr.mode;
ptr.stream.mode_ = ptr.mode;
return ptr;
}
pub fn estimatedSize(this: *const @This()) usize {
return @sizeOf(@This()) + switch (this.stream.mode_) {
.ZSTD_COMPRESS => bun.c.ZSTD_sizeof_CCtx(@ptrCast(this.stream.state)),
.ZSTD_DECOMPRESS => bun.c.ZSTD_sizeof_DCtx(@ptrCast(this.stream.state)),
else => 0,
};
}
pub fn init(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const arguments = callframe.argumentsAsArray(4);
const this_value = callframe.this();
if (callframe.argumentsCount() != 4) return globalThis.ERR(.MISSING_ARGS, "init(initParamsArray, pledgedSrcSize, writeState, processCallback)", .{}).throw();
const initParamsArray_value = arguments[0];
const pledgedSrcSize_value = arguments[1];
const writeState_value = arguments[2];
const processCallback_value = arguments[3];
const writeState = writeState_value.asArrayBuffer(globalThis) orelse return globalThis.throwInvalidArgumentTypeValue("writeState", "Uint32Array", writeState_value);
if (writeState.typed_array_type != .Uint32Array) return globalThis.throwInvalidArgumentTypeValue("writeState", "Uint32Array", writeState_value);
this.write_result = writeState.asU32().ptr;
const write_js_callback = try validators.validateFunction(globalThis, "processCallback", processCallback_value);
js.writeCallbackSetCached(this_value, globalThis, write_js_callback);
var pledged_src_size: u64 = std.math.maxInt(u64);
if (pledgedSrcSize_value.isNumber()) {
pledged_src_size = try validators.validateUint32(globalThis, pledgedSrcSize_value, "pledgedSrcSize", .{}, false);
}
var err = this.stream.init(pledged_src_size);
if (err.isError()) {
try impl.emitError(this, globalThis, this_value, err);
return .jsBoolean(false);
}
const params_ = initParamsArray_value.asArrayBuffer(globalThis) orelse return globalThis.throwInvalidArgumentTypeValue("initParamsArray", "Uint32Array", initParamsArray_value);
if (params_.typed_array_type != .Uint32Array) return globalThis.throwInvalidArgumentTypeValue("initParamsArray", "Uint32Array", initParamsArray_value);
for (params_.asU32(), 0..) |x, i| {
if (x == std.math.maxInt(u32)) continue;
const err_ = this.stream.setParams(@intCast(i), x);
if (err_.isError()) return globalThis.ERR(.ZLIB_INITIALIZATION_FAILED, "{s}", .{std.mem.sliceTo(err_.msg.?, 0)}).throw();
}
return .jsBoolean(true);
}
pub fn params(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
_ = this;
_ = globalThis;
_ = callframe;
// intentionally left empty
return .undefined;
}
fn deinit(this: *@This()) void {
this.poll_ref.deinit();
switch (this.stream.mode) {
.ZSTD_COMPRESS, .ZSTD_DECOMPRESS => this.stream.close(),
else => {},
}
bun.destroy(this);
}
const Context = struct {
const c = bun.c;
mode: bun.zlib.NodeMode = .NONE,
mode_: bun.zlib.NodeMode = .NONE,
state: ?*anyopaque = null,
flush: c_int = c.ZSTD_e_continue,
input: c.ZSTD_inBuffer = .{ .src = null, .size = 0, .pos = 0 },
output: c.ZSTD_outBuffer = .{ .dst = null, .size = 0, .pos = 0 },
pledged_src_size: u64 = std.math.maxInt(u64),
remaining: u64 = 0,
pub fn init(this: *Context, pledged_src_size: u64) Error {
switch (this.mode_) {
.ZSTD_COMPRESS => {
this.pledged_src_size = pledged_src_size;
const state = c.ZSTD_createCCtx();
if (state == null) return .init("Could not initialize zstd instance", -1, "ERR_ZLIB_INITIALIZATION_FAILED");
this.state = state.?;
const result = c.ZSTD_CCtx_setPledgedSrcSize(state, pledged_src_size);
if (c.ZSTD_isError(result) > 0) return .init("Could not set pledged src size", -1, "ERR_ZLIB_INITIALIZATION_FAILED");
return .ok;
},
.ZSTD_DECOMPRESS => {
const state = c.ZSTD_createDCtx();
if (state == null) return .init("Could not initialize zstd instance", -1, "ERR_ZLIB_INITIALIZATION_FAILED");
this.state = state.?;
return .ok;
},
else => @panic("unreachable"),
}
}
pub fn setParams(this: *Context, key: c_uint, value: u32) Error {
switch (this.mode_) {
.ZSTD_COMPRESS => {
const result = c.ZSTD_CCtx_setParameter(@ptrCast(this.state), key, @bitCast(value));
if (c.ZSTD_isError(result) > 0) return .init("Setting parameter failed", -1, "ERR_ZSTD_PARAM_SET_FAILED");
return .ok;
},
.ZSTD_DECOMPRESS => {
const result = c.ZSTD_DCtx_setParameter(@ptrCast(this.state), key, @bitCast(value));
if (c.ZSTD_isError(result) > 0) return .init("Setting parameter failed", -1, "ERR_ZSTD_PARAM_SET_FAILED");
return .ok;
},
else => @panic("unreachable"),
}
}
pub fn reset(this: *Context) Error {
return this.init(this.pledged_src_size);
}
pub fn setBuffers(this: *Context, in: ?[]const u8, out: ?[]u8) void {
this.input.src = if (in) |p| p.ptr else null;
this.input.size = if (in) |p| p.len else 0;
this.input.pos = 0;
this.output.dst = if (out) |p| p.ptr else null;
this.output.size = if (out) |p| p.len else 0;
this.output.pos = 0;
}
pub fn setFlush(this: *Context, flush: c_int) void {
this.flush = flush;
}
pub fn doWork(this: *Context) void {
this.remaining = switch (this.mode_) {
.ZSTD_COMPRESS => c.ZSTD_compressStream2(@ptrCast(this.state), &this.output, &this.input, @intCast(this.flush)),
.ZSTD_DECOMPRESS => c.ZSTD_decompressStream(@ptrCast(this.state), &this.output, &this.input),
else => @panic("unreachable"),
};
}
pub fn updateWriteResult(this: *Context, avail_in: *u32, avail_out: *u32) void {
avail_in.* = @intCast(this.input.size - this.input.pos);
avail_out.* = @intCast(this.output.size - this.output.pos);
}
pub fn getErrorInfo(this: *Context) Error {
defer this.remaining = 0;
const err = c.ZSTD_getErrorCode(this.remaining);
if (err == 0) {
return .ok;
}
return Error{
.err = @intCast(err),
.msg = c.ZSTD_getErrorString(err),
.code = switch (err) {
c.ZSTD_error_no_error => "ZSTD_error_no_error",
c.ZSTD_error_GENERIC => "ZSTD_error_GENERIC",
c.ZSTD_error_prefix_unknown => "ZSTD_error_prefix_unknown",
c.ZSTD_error_version_unsupported => "ZSTD_error_version_unsupported",
c.ZSTD_error_frameParameter_unsupported => "ZSTD_error_frameParameter_unsupported",
c.ZSTD_error_frameParameter_windowTooLarge => "ZSTD_error_frameParameter_windowTooLarge",
c.ZSTD_error_corruption_detected => "ZSTD_error_corruption_detected",
c.ZSTD_error_checksum_wrong => "ZSTD_error_checksum_wrong",
c.ZSTD_error_literals_headerWrong => "ZSTD_error_literals_headerWrong",
c.ZSTD_error_dictionary_corrupted => "ZSTD_error_dictionary_corrupted",
c.ZSTD_error_dictionary_wrong => "ZSTD_error_dictionary_wrong",
c.ZSTD_error_dictionaryCreation_failed => "ZSTD_error_dictionaryCreation_failed",
c.ZSTD_error_parameter_unsupported => "ZSTD_error_parameter_unsupported",
c.ZSTD_error_parameter_combination_unsupported => "ZSTD_error_parameter_combination_unsupported",
c.ZSTD_error_parameter_outOfBound => "ZSTD_error_parameter_outOfBound",
c.ZSTD_error_tableLog_tooLarge => "ZSTD_error_tableLog_tooLarge",
c.ZSTD_error_maxSymbolValue_tooLarge => "ZSTD_error_maxSymbolValue_tooLarge",
c.ZSTD_error_maxSymbolValue_tooSmall => "ZSTD_error_maxSymbolValue_tooSmall",
c.ZSTD_error_stabilityCondition_notRespected => "ZSTD_error_stabilityCondition_notRespected",
c.ZSTD_error_stage_wrong => "ZSTD_error_stage_wrong",
c.ZSTD_error_init_missing => "ZSTD_error_init_missing",
c.ZSTD_error_memory_allocation => "ZSTD_error_memory_allocation",
c.ZSTD_error_workSpace_tooSmall => "ZSTD_error_workSpace_tooSmall",
c.ZSTD_error_dstSize_tooSmall => "ZSTD_error_dstSize_tooSmall",
c.ZSTD_error_srcSize_wrong => "ZSTD_error_srcSize_wrong",
c.ZSTD_error_dstBuffer_null => "ZSTD_error_dstBuffer_null",
c.ZSTD_error_noForwardProgress_destFull => "ZSTD_error_noForwardProgress_destFull",
c.ZSTD_error_noForwardProgress_inputEmpty => "ZSTD_error_noForwardProgress_inputEmpty",
else => "ZSTD_error_GENERIC",
},
};
}
pub fn close(this: *Context) void {
_ = switch (this.mode_) {
.ZSTD_COMPRESS => c.ZSTD_CCtx_reset(@ptrCast(this.state), c.ZSTD_reset_session_and_parameters),
.ZSTD_DECOMPRESS => c.ZSTD_DCtx_reset(@ptrCast(this.state), c.ZSTD_reset_session_and_parameters),
else => unreachable,
};
_ = switch (this.mode_) {
.ZSTD_COMPRESS => c.ZSTD_freeCCtx(@ptrCast(this.state)),
.ZSTD_DECOMPRESS => c.ZSTD_freeDCtx(@ptrCast(this.state)),
else => unreachable,
};
this.mode = .NONE;
this.state = null;
}
};

View File

@@ -61,11 +61,9 @@
#include <winternl.h> #include <winternl.h>
#endif #endif
#if WINDOWS
#include <windows.h>
#include <winternl.h>
#endif
#undef lstat #undef lstat
#undef fstat #undef fstat
#undef stat #undef stat
#include <zstd.h>
#include <zstd_errors.h>

View File

@@ -1,186 +1,6 @@
const std = @import("std"); const std = @import("std");
const bun = @import("bun"); const bun = @import("bun");
pub extern fn ZSTD_versionNumber() c_uint; const c = bun.c;
pub extern fn ZSTD_versionString() [*c]const u8;
pub extern fn ZSTD_compress(dst: ?*anyopaque, dstCapacity: usize, src: ?*const anyopaque, srcSize: usize, compressionLevel: c_int) usize;
pub extern fn ZSTD_decompress(dst: ?*anyopaque, dstCapacity: usize, src: ?*const anyopaque, compressedSize: usize) usize;
pub extern fn ZSTD_getFrameContentSize(src: ?*const anyopaque, srcSize: usize) c_ulonglong;
pub extern fn ZSTD_getDecompressedSize(src: ?*const anyopaque, srcSize: usize) c_ulonglong;
pub extern fn ZSTD_findFrameCompressedSize(src: ?*const anyopaque, srcSize: usize) usize;
pub extern fn ZSTD_compressBound(srcSize: usize) usize;
pub extern fn ZSTD_isError(code: usize) c_uint;
pub extern fn ZSTD_getErrorName(code: usize) [*:0]const u8;
pub extern fn ZSTD_minCLevel() c_int;
pub extern fn ZSTD_maxCLevel() c_int;
pub extern fn ZSTD_defaultCLevel() c_int;
pub const struct_ZSTD_CCtx_s = opaque {};
pub const ZSTD_CCtx = struct_ZSTD_CCtx_s;
pub extern fn ZSTD_createCCtx() ?*ZSTD_CCtx;
pub extern fn ZSTD_freeCCtx(cctx: ?*ZSTD_CCtx) usize;
pub extern fn ZSTD_compressCCtx(cctx: ?*ZSTD_CCtx, dst: ?*anyopaque, dstCapacity: usize, src: ?*const anyopaque, srcSize: usize, compressionLevel: c_int) usize;
pub const struct_ZSTD_DCtx_s = opaque {};
pub const ZSTD_DCtx = struct_ZSTD_DCtx_s;
pub extern fn ZSTD_createDCtx() ?*ZSTD_DCtx;
pub extern fn ZSTD_freeDCtx(dctx: ?*ZSTD_DCtx) usize;
pub extern fn ZSTD_decompressDCtx(dctx: ?*ZSTD_DCtx, dst: ?*anyopaque, dstCapacity: usize, src: ?*const anyopaque, srcSize: usize) usize;
pub const ZSTD_fast: c_int = 1;
pub const ZSTD_dfast: c_int = 2;
pub const ZSTD_greedy: c_int = 3;
pub const ZSTD_lazy: c_int = 4;
pub const ZSTD_lazy2: c_int = 5;
pub const ZSTD_btlazy2: c_int = 6;
pub const ZSTD_btopt: c_int = 7;
pub const ZSTD_btultra: c_int = 8;
pub const ZSTD_btultra2: c_int = 9;
pub const ZSTD_strategy = c_uint;
pub const ZSTD_c_compressionLevel: c_int = 100;
pub const ZSTD_c_windowLog: c_int = 101;
pub const ZSTD_c_hashLog: c_int = 102;
pub const ZSTD_c_chainLog: c_int = 103;
pub const ZSTD_c_searchLog: c_int = 104;
pub const ZSTD_c_minMatch: c_int = 105;
pub const ZSTD_c_targetLength: c_int = 106;
pub const ZSTD_c_strategy: c_int = 107;
pub const ZSTD_c_enableLongDistanceMatching: c_int = 160;
pub const ZSTD_c_ldmHashLog: c_int = 161;
pub const ZSTD_c_ldmMinMatch: c_int = 162;
pub const ZSTD_c_ldmBucketSizeLog: c_int = 163;
pub const ZSTD_c_ldmHashRateLog: c_int = 164;
pub const ZSTD_c_contentSizeFlag: c_int = 200;
pub const ZSTD_c_checksumFlag: c_int = 201;
pub const ZSTD_c_dictIDFlag: c_int = 202;
pub const ZSTD_c_nbWorkers: c_int = 400;
pub const ZSTD_c_jobSize: c_int = 401;
pub const ZSTD_c_overlapLog: c_int = 402;
pub const ZSTD_c_experimentalParam1: c_int = 500;
pub const ZSTD_c_experimentalParam2: c_int = 10;
pub const ZSTD_c_experimentalParam3: c_int = 1000;
pub const ZSTD_c_experimentalParam4: c_int = 1001;
pub const ZSTD_c_experimentalParam5: c_int = 1002;
pub const ZSTD_c_experimentalParam6: c_int = 1003;
pub const ZSTD_c_experimentalParam7: c_int = 1004;
pub const ZSTD_c_experimentalParam8: c_int = 1005;
pub const ZSTD_c_experimentalParam9: c_int = 1006;
pub const ZSTD_c_experimentalParam10: c_int = 1007;
pub const ZSTD_c_experimentalParam11: c_int = 1008;
pub const ZSTD_c_experimentalParam12: c_int = 1009;
pub const ZSTD_c_experimentalParam13: c_int = 1010;
pub const ZSTD_c_experimentalParam14: c_int = 1011;
pub const ZSTD_c_experimentalParam15: c_int = 1012;
pub const ZSTD_c_experimentalParam16: c_int = 1013;
pub const ZSTD_c_experimentalParam17: c_int = 1014;
pub const ZSTD_c_experimentalParam18: c_int = 1015;
pub const ZSTD_c_experimentalParam19: c_int = 1016;
pub const ZSTD_cParameter = c_uint;
pub const ZSTD_bounds = extern struct {
@"error": usize,
lowerBound: c_int,
upperBound: c_int,
};
pub extern fn ZSTD_cParam_getBounds(cParam: ZSTD_cParameter) ZSTD_bounds;
pub extern fn ZSTD_CCtx_setParameter(cctx: ?*ZSTD_CCtx, param: ZSTD_cParameter, value: c_int) usize;
pub extern fn ZSTD_CCtx_setPledgedSrcSize(cctx: ?*ZSTD_CCtx, pledgedSrcSize: c_ulonglong) usize;
pub const ZSTD_reset_session_only: c_int = 1;
pub const ZSTD_reset_parameters: c_int = 2;
pub const ZSTD_reset_session_and_parameters: c_int = 3;
pub const ZSTD_ResetDirective = c_uint;
pub extern fn ZSTD_CCtx_reset(cctx: ?*ZSTD_CCtx, reset: ZSTD_ResetDirective) usize;
pub extern fn ZSTD_compress2(cctx: ?*ZSTD_CCtx, dst: ?*anyopaque, dstCapacity: usize, src: ?*const anyopaque, srcSize: usize) usize;
pub const ZSTD_d_windowLogMax: c_int = 100;
pub const ZSTD_d_experimentalParam1: c_int = 1000;
pub const ZSTD_d_experimentalParam2: c_int = 1001;
pub const ZSTD_d_experimentalParam3: c_int = 1002;
pub const ZSTD_d_experimentalParam4: c_int = 1003;
pub const ZSTD_d_experimentalParam5: c_int = 1004;
pub const ZSTD_dParameter = c_uint;
pub extern fn ZSTD_dParam_getBounds(dParam: ZSTD_dParameter) ZSTD_bounds;
pub extern fn ZSTD_DCtx_setParameter(dctx: ?*ZSTD_DCtx, param: ZSTD_dParameter, value: c_int) usize;
pub extern fn ZSTD_DCtx_reset(dctx: ?*ZSTD_DCtx, reset: ZSTD_ResetDirective) usize;
pub const struct_ZSTD_inBuffer_s = extern struct {
src: ?*const anyopaque,
size: usize,
pos: usize,
};
pub const ZSTD_inBuffer = struct_ZSTD_inBuffer_s;
pub const struct_ZSTD_outBuffer_s = extern struct {
dst: ?*anyopaque,
size: usize,
pos: usize,
};
pub const ZSTD_outBuffer = struct_ZSTD_outBuffer_s;
pub const ZSTD_CStream = ZSTD_CCtx;
pub extern fn ZSTD_createCStream() ?*ZSTD_CStream;
pub extern fn ZSTD_freeCStream(zcs: ?*ZSTD_CStream) usize;
pub const ZSTD_e_continue: c_int = 0;
pub const ZSTD_e_flush: c_int = 1;
pub const ZSTD_e_end: c_int = 2;
pub const ZSTD_EndDirective = c_uint;
pub extern fn ZSTD_compressStream2(cctx: ?*ZSTD_CCtx, output: [*c]ZSTD_outBuffer, input: [*c]ZSTD_inBuffer, endOp: ZSTD_EndDirective) usize;
pub extern fn ZSTD_CStreamInSize() usize;
pub extern fn ZSTD_CStreamOutSize() usize;
pub extern fn ZSTD_initCStream(zcs: ?*ZSTD_CStream, compressionLevel: c_int) usize;
pub extern fn ZSTD_compressStream(zcs: ?*ZSTD_CStream, output: [*c]ZSTD_outBuffer, input: [*c]ZSTD_inBuffer) usize;
pub extern fn ZSTD_flushStream(zcs: ?*ZSTD_CStream, output: [*c]ZSTD_outBuffer) usize;
pub extern fn ZSTD_endStream(zcs: ?*ZSTD_CStream, output: [*c]ZSTD_outBuffer) usize;
pub const ZSTD_DStream = ZSTD_DCtx;
pub extern fn ZSTD_createDStream() ?*ZSTD_DStream;
pub extern fn ZSTD_freeDStream(zds: ?*ZSTD_DStream) usize;
pub extern fn ZSTD_initDStream(zds: ?*ZSTD_DStream) usize;
pub extern fn ZSTD_decompressStream(zds: ?*ZSTD_DStream, output: [*c]ZSTD_outBuffer, input: [*c]ZSTD_inBuffer) usize;
pub extern fn ZSTD_DStreamInSize() usize;
pub extern fn ZSTD_DStreamOutSize() usize;
pub extern fn ZSTD_compress_usingDict(ctx: ?*ZSTD_CCtx, dst: ?*anyopaque, dstCapacity: usize, src: ?*const anyopaque, srcSize: usize, dict: ?*const anyopaque, dictSize: usize, compressionLevel: c_int) usize;
pub extern fn ZSTD_decompress_usingDict(dctx: ?*ZSTD_DCtx, dst: ?*anyopaque, dstCapacity: usize, src: ?*const anyopaque, srcSize: usize, dict: ?*const anyopaque, dictSize: usize) usize;
pub const struct_ZSTD_CDict_s = opaque {};
pub const ZSTD_CDict = struct_ZSTD_CDict_s;
pub extern fn ZSTD_createCDict(dictBuffer: ?*const anyopaque, dictSize: usize, compressionLevel: c_int) ?*ZSTD_CDict;
pub extern fn ZSTD_freeCDict(CDict: ?*ZSTD_CDict) usize;
pub extern fn ZSTD_compress_usingCDict(cctx: ?*ZSTD_CCtx, dst: ?*anyopaque, dstCapacity: usize, src: ?*const anyopaque, srcSize: usize, cdict: ?*const ZSTD_CDict) usize;
pub const struct_ZSTD_DDict_s = opaque {};
pub const ZSTD_DDict = struct_ZSTD_DDict_s;
pub extern fn ZSTD_createDDict(dictBuffer: ?*const anyopaque, dictSize: usize) ?*ZSTD_DDict;
pub extern fn ZSTD_freeDDict(ddict: ?*ZSTD_DDict) usize;
pub extern fn ZSTD_decompress_usingDDict(dctx: ?*ZSTD_DCtx, dst: ?*anyopaque, dstCapacity: usize, src: ?*const anyopaque, srcSize: usize, ddict: ?*const ZSTD_DDict) usize;
pub extern fn ZSTD_getDictID_fromDict(dict: ?*const anyopaque, dictSize: usize) c_uint;
pub extern fn ZSTD_getDictID_fromCDict(cdict: ?*const ZSTD_CDict) c_uint;
pub extern fn ZSTD_getDictID_fromDDict(ddict: ?*const ZSTD_DDict) c_uint;
pub extern fn ZSTD_getDictID_fromFrame(src: ?*const anyopaque, srcSize: usize) c_uint;
pub extern fn ZSTD_CCtx_loadDictionary(cctx: ?*ZSTD_CCtx, dict: ?*const anyopaque, dictSize: usize) usize;
pub extern fn ZSTD_CCtx_refCDict(cctx: ?*ZSTD_CCtx, cdict: ?*const ZSTD_CDict) usize;
pub extern fn ZSTD_CCtx_refPrefix(cctx: ?*ZSTD_CCtx, prefix: ?*const anyopaque, prefixSize: usize) usize;
pub extern fn ZSTD_DCtx_loadDictionary(dctx: ?*ZSTD_DCtx, dict: ?*const anyopaque, dictSize: usize) usize;
pub extern fn ZSTD_DCtx_refDDict(dctx: ?*ZSTD_DCtx, ddict: ?*const ZSTD_DDict) usize;
pub extern fn ZSTD_DCtx_refPrefix(dctx: ?*ZSTD_DCtx, prefix: ?*const anyopaque, prefixSize: usize) usize;
pub extern fn ZSTD_sizeof_CCtx(cctx: ?*const ZSTD_CCtx) usize;
pub extern fn ZSTD_sizeof_DCtx(dctx: ?*const ZSTD_DCtx) usize;
pub extern fn ZSTD_sizeof_CStream(zcs: ?*const ZSTD_CStream) usize;
pub extern fn ZSTD_sizeof_DStream(zds: ?*const ZSTD_DStream) usize;
pub extern fn ZSTD_sizeof_CDict(cdict: ?*const ZSTD_CDict) usize;
pub extern fn ZSTD_sizeof_DDict(ddict: ?*const ZSTD_DDict) usize;
pub const ZSTD_VERSION_MAJOR = @as(c_int, 1);
pub const ZSTD_VERSION_MINOR = @as(c_int, 5);
pub const ZSTD_VERSION_RELEASE = @as(c_int, 5);
pub const ZSTD_VERSION_NUMBER = (((ZSTD_VERSION_MAJOR * @as(c_int, 100)) * @as(c_int, 100)) + (ZSTD_VERSION_MINOR * @as(c_int, 100))) + ZSTD_VERSION_RELEASE;
pub const ZSTD_LIB_VERSION = ZSTD_VERSION_MAJOR.ZSTD_VERSION_MINOR.ZSTD_VERSION_RELEASE;
pub const ZSTD_CLEVEL_DEFAULT = @as(c_int, 3);
pub const ZSTD_MAGICNUMBER = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xFD2FB528, .hex);
pub const ZSTD_MAGIC_DICTIONARY = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xEC30A437, .hex);
pub const ZSTD_MAGIC_SKIPPABLE_START = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x184D2A50, .hex);
pub const ZSTD_MAGIC_SKIPPABLE_MASK = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xFFFFFFF0, .hex);
pub const ZSTD_BLOCKSIZELOG_MAX = @as(c_int, 17);
pub const ZSTD_BLOCKSIZE_MAX = @as(c_int, 1) << ZSTD_BLOCKSIZELOG_MAX;
pub const ZSTD_CONTENTSIZE_UNKNOWN = @as(c_ulonglong, 0) - @as(c_int, 1);
pub const ZSTD_CONTENTSIZE_ERROR = @as(c_ulonglong, 0) - @as(c_int, 2);
pub const ZSTD_MAX_INPUT_SIZE = if (@import("std").zig.c_translation.sizeof(usize) == @as(c_int, 8)) @as(c_ulonglong, 0xFF00FF00FF00FF00) else @import("std").zig.c_translation.promoteIntLiteral(c_uint, 0xFF00FF00, .hex);
pub inline fn ZSTD_COMPRESSBOUND(srcSize: anytype) @TypeOf(if (@import("std").zig.c_translation.cast(usize, srcSize) >= ZSTD_MAX_INPUT_SIZE) @as(c_int, 0) else (srcSize + (srcSize >> @as(c_int, 8))) + (if (srcSize < (@as(c_int, 128) << @as(c_int, 10))) ((@as(c_int, 128) << @as(c_int, 10)) - srcSize) >> @as(c_int, 11) else @as(c_int, 0))) {
return if (@import("std").zig.c_translation.cast(usize, srcSize) >= ZSTD_MAX_INPUT_SIZE) @as(c_int, 0) else (srcSize + (srcSize >> @as(c_int, 8))) + (if (srcSize < (@as(c_int, 128) << @as(c_int, 10))) ((@as(c_int, 128) << @as(c_int, 10)) - srcSize) >> @as(c_int, 11) else @as(c_int, 0));
}
pub const ZSTD_CCtx_s = struct_ZSTD_CCtx_s;
pub const ZSTD_DCtx_s = struct_ZSTD_DCtx_s;
pub const ZSTD_inBuffer_s = struct_ZSTD_inBuffer_s;
pub const ZSTD_outBuffer_s = struct_ZSTD_outBuffer_s;
pub const ZSTD_CDict_s = struct_ZSTD_CDict_s;
pub const ZSTD_DDict_s = struct_ZSTD_DDict_s;
// ----------------------------------- // -----------------------------------
@@ -194,13 +14,13 @@ pub const ZSTD_DDict_s = struct_ZSTD_DDict_s;
// const void* src, size_t srcSize, // const void* src, size_t srcSize,
// int compressionLevel); // int compressionLevel);
pub fn compress(dest: []u8, src: []const u8, level: ?i32) Result { pub fn compress(dest: []u8, src: []const u8, level: ?i32) Result {
const result = ZSTD_compress(dest.ptr, dest.len, src.ptr, src.len, level orelse ZSTD_defaultCLevel()); const result = c.ZSTD_compress(dest.ptr, dest.len, src.ptr, src.len, level orelse c.ZSTD_defaultCLevel());
if (ZSTD_isError(result) != 0) return .{ .err = bun.sliceTo(ZSTD_getErrorName(result), 0) }; if (c.ZSTD_isError(result) != 0) return .{ .err = bun.sliceTo(c.ZSTD_getErrorName(result), 0) };
return .{ .success = result }; return .{ .success = result };
} }
pub fn compressBound(srcSize: usize) usize { pub fn compressBound(srcSize: usize) usize {
return ZSTD_compressBound(srcSize); return c.ZSTD_compressBound(srcSize);
} }
/// ZSTD_decompress() : /// ZSTD_decompress() :
@@ -212,8 +32,8 @@ pub fn compressBound(srcSize: usize) usize {
// ZSTDLIB_API size_t ZSTD_decompress( void* dst, size_t dstCapacity, // ZSTDLIB_API size_t ZSTD_decompress( void* dst, size_t dstCapacity,
// const void* src, size_t compressedSize); // const void* src, size_t compressedSize);
pub fn decompress(dest: []u8, src: []const u8) Result { pub fn decompress(dest: []u8, src: []const u8) Result {
const result = ZSTD_decompress(dest.ptr, dest.len, src.ptr, src.len); const result = c.ZSTD_decompress(dest.ptr, dest.len, src.ptr, src.len);
if (ZSTD_isError(result) != 0) return .{ .err = bun.sliceTo(ZSTD_getErrorName(result), 0) }; if (c.ZSTD_isError(result) != 0) return .{ .err = bun.sliceTo(c.ZSTD_getErrorName(result), 0) };
return .{ .success = result }; return .{ .success = result };
} }
@@ -262,7 +82,7 @@ pub const ZstdReaderArrayList = struct {
list_allocator: std.mem.Allocator, list_allocator: std.mem.Allocator,
list_ptr: *std.ArrayListUnmanaged(u8), list_ptr: *std.ArrayListUnmanaged(u8),
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
zstd: *ZSTD_DStream, zstd: *c.ZSTD_DStream,
state: State = State.Uninitialized, state: State = State.Uninitialized,
total_out: usize = 0, total_out: usize = 0,
total_in: usize = 0, total_in: usize = 0,
@@ -293,17 +113,17 @@ pub const ZstdReaderArrayList = struct {
.zstd = undefined, .zstd = undefined,
}; };
reader.zstd = ZSTD_createDStream() orelse { reader.zstd = c.ZSTD_createDStream() orelse {
allocator.destroy(reader); allocator.destroy(reader);
return error.ZstdFailedToCreateInstance; return error.ZstdFailedToCreateInstance;
}; };
_ = ZSTD_initDStream(reader.zstd); _ = c.ZSTD_initDStream(reader.zstd);
return reader; return reader;
} }
pub fn end(this: *ZstdReaderArrayList) void { pub fn end(this: *ZstdReaderArrayList) void {
if (this.state != .End) { if (this.state != .End) {
_ = ZSTD_freeDStream(this.zstd); _ = c.ZSTD_freeDStream(this.zstd);
this.state = .End; this.state = .End;
} }
} }
@@ -327,19 +147,19 @@ pub const ZstdReaderArrayList = struct {
} }
const next_in = this.input[this.total_in..]; const next_in = this.input[this.total_in..];
var in_buf = ZSTD_inBuffer{ var in_buf: c.ZSTD_inBuffer = .{
.src = if (next_in.len > 0) next_in.ptr else null, .src = if (next_in.len > 0) next_in.ptr else null,
.size = next_in.len, .size = next_in.len,
.pos = 0, .pos = 0,
}; };
var out_buf = ZSTD_outBuffer{ var out_buf: c.ZSTD_outBuffer = .{
.dst = if (unused.len > 0) unused.ptr else null, .dst = if (unused.len > 0) unused.ptr else null,
.size = unused.len, .size = unused.len,
.pos = 0, .pos = 0,
}; };
const rc = ZSTD_decompressStream(this.zstd, &out_buf, &in_buf); const rc = c.ZSTD_decompressStream(this.zstd, &out_buf, &in_buf);
if (ZSTD_isError(rc) != 0) { if (c.ZSTD_isError(rc) != 0) {
this.state = .Error; this.state = .Error;
return error.ZstdDecompressionError; return error.ZstdDecompressionError;
} }

View File

@@ -6,6 +6,7 @@ const BufferModule = require("node:buffer");
const crc32 = $newZigFunction("node_zlib_binding.zig", "crc32", 1); const crc32 = $newZigFunction("node_zlib_binding.zig", "crc32", 1);
const NativeZlib = $zig("node_zlib_binding.zig", "NativeZlib"); const NativeZlib = $zig("node_zlib_binding.zig", "NativeZlib");
const NativeBrotli = $zig("node_zlib_binding.zig", "NativeBrotli"); const NativeBrotli = $zig("node_zlib_binding.zig", "NativeBrotli");
const NativeZstd = $zig("node_zlib_binding.zig", "NativeZstd");
const ObjectKeys = Object.keys; const ObjectKeys = Object.keys;
const ArrayPrototypePush = Array.prototype.push; const ArrayPrototypePush = Array.prototype.push;
@@ -15,6 +16,7 @@ const ObjectFreeze = Object.freeze;
const TypedArrayPrototypeFill = Uint8Array.prototype.fill; const TypedArrayPrototypeFill = Uint8Array.prototype.fill;
const ArrayPrototypeForEach = Array.prototype.forEach; const ArrayPrototypeForEach = Array.prototype.forEach;
const NumberIsNaN = Number.isNaN; const NumberIsNaN = Number.isNaN;
const MathMax = Math.max;
const ArrayBufferIsView = ArrayBuffer.isView; const ArrayBufferIsView = ArrayBuffer.isView;
const isArrayBufferView = ArrayBufferIsView; const isArrayBufferView = ArrayBufferIsView;
@@ -37,9 +39,11 @@ const {
Z_MIN_CHUNK, Z_MIN_WINDOWBITS, Z_MAX_WINDOWBITS, Z_MIN_LEVEL, Z_MAX_LEVEL, Z_MIN_MEMLEVEL, Z_MAX_MEMLEVEL, 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, Z_DEFAULT_CHUNK, Z_DEFAULT_COMPRESSION, Z_DEFAULT_STRATEGY, Z_DEFAULT_WINDOWBITS, Z_DEFAULT_MEMLEVEL, Z_FIXED,
// Node's compression stream modes (node_zlib_mode) // Node's compression stream modes (node_zlib_mode)
DEFLATE, DEFLATERAW, INFLATE, INFLATERAW, GZIP, GUNZIP, UNZIP, BROTLI_DECODE, BROTLI_ENCODE, DEFLATE, DEFLATERAW, INFLATE, INFLATERAW, GZIP, GUNZIP, UNZIP, BROTLI_DECODE, BROTLI_ENCODE, ZSTD_COMPRESS, ZSTD_DECOMPRESS,
// Brotli operations (~flush levels) // Brotli operations (~flush levels)
BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_FLUSH, BROTLI_OPERATION_FINISH, BROTLI_OPERATION_EMIT_METADATA, 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; } = constants;
// Translation table for return codes. // Translation table for return codes.
@@ -132,9 +136,11 @@ function zlibOnError(message, errno, code) {
const FLUSH_BOUND = [ const FLUSH_BOUND = [
[Z_NO_FLUSH, Z_BLOCK], [Z_NO_FLUSH, Z_BLOCK],
[BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_EMIT_METADATA], [BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_EMIT_METADATA],
[ZSTD_e_continue, ZSTD_e_end],
]; ];
const FLUSH_BOUND_IDX_NORMAL = 0; const FLUSH_BOUND_IDX_NORMAL = 0;
const FLUSH_BOUND_IDX_BROTLI = 1; const FLUSH_BOUND_IDX_BROTLI = 1;
const FLUSH_BOUND_IDX_ZSTD = 2;
// The base class for all Zlib-style streams. // The base class for all Zlib-style streams.
function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) { function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
@@ -142,13 +148,15 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
let maxOutputLength = kMaxLength; let maxOutputLength = kMaxLength;
// The ZlibBase class is not exported to user land, the mode should only be passed in by us. // The ZlibBase class is not exported to user land, the mode should only be passed in by us.
assert(typeof mode === "number"); assert(typeof mode === "number");
assert(mode >= DEFLATE && mode <= BROTLI_ENCODE); assert(mode >= DEFLATE && mode <= ZSTD_DECOMPRESS);
let flushBoundIdx; let flushBoundIdx;
if (mode !== BROTLI_ENCODE && mode !== BROTLI_DECODE) { if (mode === BROTLI_ENCODE || mode === BROTLI_DECODE) {
flushBoundIdx = FLUSH_BOUND_IDX_NORMAL;
} else {
flushBoundIdx = FLUSH_BOUND_IDX_BROTLI; 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) { if (opts) {
@@ -592,44 +600,44 @@ Zlib.prototype.params = function params(level, strategy, callback) {
} }
}; };
function Deflate(opts) { function Deflate(opts): void {
if (!(this instanceof Deflate)) return new Deflate(opts); if (!(this instanceof Deflate)) return new Deflate(opts);
Zlib.$apply(this, [opts, DEFLATE]); Zlib.$apply(this, [opts, DEFLATE]);
} }
$toClass(Deflate, "Deflate", Zlib); $toClass(Deflate, "Deflate", Zlib);
function Inflate(opts) { function Inflate(opts): void {
if (!(this instanceof Inflate)) return new Inflate(opts); if (!(this instanceof Inflate)) return new Inflate(opts);
Zlib.$apply(this, [opts, INFLATE]); Zlib.$apply(this, [opts, INFLATE]);
} }
$toClass(Inflate, "Inflate", Zlib); $toClass(Inflate, "Inflate", Zlib);
function Gzip(opts) { function Gzip(opts): void {
if (!(this instanceof Gzip)) return new Gzip(opts); if (!(this instanceof Gzip)) return new Gzip(opts);
Zlib.$apply(this, [opts, GZIP]); Zlib.$apply(this, [opts, GZIP]);
} }
$toClass(Gzip, "Gzip", Zlib); $toClass(Gzip, "Gzip", Zlib);
function Gunzip(opts) { function Gunzip(opts): void {
if (!(this instanceof Gunzip)) return new Gunzip(opts); if (!(this instanceof Gunzip)) return new Gunzip(opts);
Zlib.$apply(this, [opts, GUNZIP]); Zlib.$apply(this, [opts, GUNZIP]);
} }
$toClass(Gunzip, "Gunzip", Zlib); $toClass(Gunzip, "Gunzip", Zlib);
function DeflateRaw(opts) { function DeflateRaw(opts): void {
if (opts && opts.windowBits === 8) opts.windowBits = 9; if (opts && opts.windowBits === 8) opts.windowBits = 9;
if (!(this instanceof DeflateRaw)) return new DeflateRaw(opts); if (!(this instanceof DeflateRaw)) return new DeflateRaw(opts);
Zlib.$apply(this, [opts, DEFLATERAW]); Zlib.$apply(this, [opts, DEFLATERAW]);
} }
$toClass(DeflateRaw, "DeflateRaw", Zlib); $toClass(DeflateRaw, "DeflateRaw", Zlib);
function InflateRaw(opts) { function InflateRaw(opts): void {
if (!(this instanceof InflateRaw)) return new InflateRaw(opts); if (!(this instanceof InflateRaw)) return new InflateRaw(opts);
Zlib.$apply(this, [opts, INFLATERAW]); Zlib.$apply(this, [opts, INFLATERAW]);
} }
$toClass(InflateRaw, "InflateRaw", Zlib); $toClass(InflateRaw, "InflateRaw", Zlib);
function Unzip(opts) { function Unzip(opts): void {
if (!(this instanceof Unzip)) return new Unzip(opts); if (!(this instanceof Unzip)) return new Unzip(opts);
Zlib.$apply(this, [opts, UNZIP]); Zlib.$apply(this, [opts, UNZIP]);
} }
@@ -694,18 +702,75 @@ function Brotli(opts, mode) {
} }
$toClass(Brotli, "Brotli", Zlib); $toClass(Brotli, "Brotli", Zlib);
function BrotliCompress(opts) { function BrotliCompress(opts): void {
if (!(this instanceof BrotliCompress)) return new BrotliCompress(opts); if (!(this instanceof BrotliCompress)) return new BrotliCompress(opts);
Brotli.$apply(this, [opts, BROTLI_ENCODE]); Brotli.$apply(this, [opts, BROTLI_ENCODE]);
} }
$toClass(BrotliCompress, "BrotliCompress", Brotli); $toClass(BrotliCompress, "BrotliCompress", Brotli);
function BrotliDecompress(opts) { function BrotliDecompress(opts): void {
if (!(this instanceof BrotliDecompress)) return new BrotliDecompress(opts); if (!(this instanceof BrotliDecompress)) return new BrotliDecompress(opts);
Brotli.$apply(this, [opts, BROTLI_DECODE]); Brotli.$apply(this, [opts, BROTLI_DECODE]);
} }
$toClass(BrotliDecompress, "BrotliDecompress", Brotli); $toClass(BrotliDecompress, "BrotliDecompress", Brotli);
const zstdDefaultOpts = {
flush: ZSTD_e_continue,
finishFlush: ZSTD_e_end,
fullFlush: ZSTD_e_flush,
};
class Zstd extends ZlibBase {
constructor(opts, mode, initParamsArray, maxParam) {
assert(mode === ZSTD_COMPRESS || mode === ZSTD_DECOMPRESS);
initParamsArray.fill(-1);
if (opts?.params) {
ObjectKeys(opts.params).forEach(origKey => {
const key = +origKey;
if (NumberIsNaN(key) || key < 0 || key > maxParam || (initParamsArray[key] | 0) !== -1) {
throw $ERR_ZSTD_INVALID_PARAM(origKey);
}
const value = opts.params[origKey];
if (typeof value !== "number" && typeof value !== "boolean") {
throw $ERR_INVALID_ARG_TYPE("options.params[key]", "number", opts.params[origKey]);
}
initParamsArray[key] = value;
});
}
const handle = new NativeZstd(mode);
const pledgedSrcSize = opts?.pledgedSrcSize ?? undefined;
const writeState = new Uint32Array(2);
handle.init(initParamsArray, pledgedSrcSize, writeState, processCallback);
super(opts, mode, handle, zstdDefaultOpts);
this._writeState = writeState;
}
}
const kMaxZstdCParam = MathMax(...ObjectKeys(constants).map(key => (key.startsWith("ZSTD_c_") ? constants[key] : 0)));
const zstdInitCParamsArray = new Uint32Array(kMaxZstdCParam + 1);
class ZstdCompress extends Zstd {
constructor(opts) {
super(opts, ZSTD_COMPRESS, zstdInitCParamsArray, kMaxZstdCParam);
}
}
const kMaxZstdDParam = MathMax(...ObjectKeys(constants).map(key => (key.startsWith("ZSTD_d_") ? constants[key] : 0)));
const zstdInitDParamsArray = new Uint32Array(kMaxZstdDParam + 1);
class ZstdDecompress extends Zstd {
constructor(opts) {
super(opts, ZSTD_DECOMPRESS, zstdInitDParamsArray, kMaxZstdDParam);
}
}
// Legacy alias on the C++ wrapper object. // Legacy alias on the C++ wrapper object.
ObjectDefineProperty(NativeZlib.prototype, "jsref", { ObjectDefineProperty(NativeZlib.prototype, "jsref", {
__proto__: null, __proto__: null,
@@ -728,6 +793,8 @@ const zlib = {
Unzip, Unzip,
BrotliCompress, BrotliCompress,
BrotliDecompress, BrotliDecompress,
ZstdCompress,
ZstdDecompress,
deflate: createConvenienceMethod(Deflate, false, "deflate"), deflate: createConvenienceMethod(Deflate, false, "deflate"),
deflateSync: createConvenienceMethod(Deflate, true, "deflateSync"), deflateSync: createConvenienceMethod(Deflate, true, "deflateSync"),
@@ -747,6 +814,10 @@ const zlib = {
brotliCompressSync: createConvenienceMethod(BrotliCompress, true, "brotliCompressSync"), brotliCompressSync: createConvenienceMethod(BrotliCompress, true, "brotliCompressSync"),
brotliDecompress: createConvenienceMethod(BrotliDecompress, false, "brotliDecompress"), brotliDecompress: createConvenienceMethod(BrotliDecompress, false, "brotliDecompress"),
brotliDecompressSync: createConvenienceMethod(BrotliDecompress, true, "brotliDecompressSync"), brotliDecompressSync: createConvenienceMethod(BrotliDecompress, true, "brotliDecompressSync"),
zstdCompress: createConvenienceMethod(ZstdCompress, false, "zstdCompress"),
zstdCompressSync: createConvenienceMethod(ZstdCompress, true, "zstdCompressSync"),
zstdDecompress: createConvenienceMethod(ZstdDecompress, false, "zstdDecompress"),
zstdDecompressSync: createConvenienceMethod(ZstdDecompress, true, "zstdDecompressSync"),
createDeflate: function (options) { createDeflate: function (options) {
return new Deflate(options); return new Deflate(options);
@@ -775,6 +846,12 @@ const zlib = {
createBrotliDecompress: function (options) { createBrotliDecompress: function (options) {
return new BrotliDecompress(options); return new BrotliDecompress(options);
}, },
createZstdCompress: function (options) {
return new ZstdCompress(options);
},
createZstdDecompress: function (options) {
return new ZstdDecompress(options);
},
}; };
ObjectDefineProperties(zlib, { ObjectDefineProperties(zlib, {

View File

@@ -737,6 +737,8 @@ pub const NodeMode = enum(u8) {
UNZIP = 7, UNZIP = 7,
BROTLI_DECODE = 8, BROTLI_DECODE = 8,
BROTLI_ENCODE = 9, BROTLI_ENCODE = 9,
ZSTD_COMPRESS = 10,
ZSTD_DECOMPRESS = 11,
}; };
/// Not for streaming! /// Not for streaming!

Binary file not shown.

View File

@@ -54,6 +54,8 @@ for (const [type, expect] of [
['deflateRaw', 'inflateRaw', 'DeflateRaw', 'InflateRaw'], ['deflateRaw', 'inflateRaw', 'DeflateRaw', 'InflateRaw'],
['brotliCompress', 'brotliDecompress', ['brotliCompress', 'brotliDecompress',
'BrotliCompress', 'BrotliDecompress'], 'BrotliCompress', 'BrotliDecompress'],
['zstdCompress', 'zstdDecompress',
'ZstdCompress', 'ZstdDecompress'],
]) { ]) {
zlib[method[0]](expect, opts, common.mustCall((err, result) => { zlib[method[0]](expect, opts, common.mustCall((err, result) => {
zlib[method[1]](result, opts, common.mustCall((err, result) => { zlib[method[1]](result, opts, common.mustCall((err, result) => {

View File

@@ -11,10 +11,12 @@ const emptyBuffer = Buffer.alloc(0);
[ zlib.deflateSync, zlib.inflateSync, 'deflate sync' ], [ zlib.deflateSync, zlib.inflateSync, 'deflate sync' ],
[ zlib.gzipSync, zlib.gunzipSync, 'gzip sync' ], [ zlib.gzipSync, zlib.gunzipSync, 'gzip sync' ],
[ zlib.brotliCompressSync, zlib.brotliDecompressSync, 'br sync' ], [ zlib.brotliCompressSync, zlib.brotliDecompressSync, 'br sync' ],
[ zlib.zstdCompressSync, zlib.zstdDecompressSync, 'zstd sync' ],
[ promisify(zlib.deflateRaw), promisify(zlib.inflateRaw), 'raw' ], [ promisify(zlib.deflateRaw), promisify(zlib.inflateRaw), 'raw' ],
[ promisify(zlib.deflate), promisify(zlib.inflate), 'deflate' ], [ promisify(zlib.deflate), promisify(zlib.inflate), 'deflate' ],
[ promisify(zlib.gzip), promisify(zlib.gunzip), 'gzip' ], [ promisify(zlib.gzip), promisify(zlib.gunzip), 'gzip' ],
[ promisify(zlib.brotliCompress), promisify(zlib.brotliDecompress), 'br' ], [ promisify(zlib.brotliCompress), promisify(zlib.brotliDecompress), 'br' ],
[ promisify(zlib.zstdCompress), promisify(zlib.zstdDecompress), 'zstd' ],
]) { ]) {
const compressed = await compress(emptyBuffer); const compressed = await compress(emptyBuffer);
const decompressed = await decompress(compressed); const decompressed = await decompress(compressed);

View File

@@ -40,6 +40,7 @@ const unzips = [
zlib.Inflate(), zlib.Inflate(),
zlib.InflateRaw(), zlib.InflateRaw(),
zlib.BrotliDecompress(), zlib.BrotliDecompress(),
new zlib.ZstdDecompress(),
]; ];
nonStringInputs.forEach(common.mustCall((input) => { nonStringInputs.forEach(common.mustCall((input) => {

View File

@@ -144,6 +144,7 @@ class HashStream extends Stream {
for (const [ createCompress, createDecompress ] of [ for (const [ createCompress, createDecompress ] of [
[ zlib.createGzip, zlib.createGunzip ], [ zlib.createGzip, zlib.createGunzip ],
[ zlib.createBrotliCompress, zlib.createBrotliDecompress ], [ zlib.createBrotliCompress, zlib.createBrotliDecompress ],
[ zlib.createZstdCompress, zlib.createZstdDecompress ],
]) { ]) {
const inp = new RandomReadStream({ total: 1024, block: 256, jitter: 16 }); const inp = new RandomReadStream({ total: 1024, block: 256, jitter: 16 });
const out = new HashStream(); const out = new HashStream();

View File

@@ -27,6 +27,7 @@ const zlib = require('zlib');
for (const [ createCompress, createDecompress ] of [ for (const [ createCompress, createDecompress ] of [
[ zlib.createGzip, zlib.createGunzip ], [ zlib.createGzip, zlib.createGunzip ],
[ zlib.createBrotliCompress, zlib.createBrotliDecompress ], [ zlib.createBrotliCompress, zlib.createBrotliDecompress ],
[ zlib.createZstdCompress, zlib.createZstdDecompress ],
]) { ]) {
const gzip = createCompress(); const gzip = createCompress();
const gunz = createDecompress(); const gunz = createDecompress();

View File

@@ -24,8 +24,14 @@ const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const zlib = require('zlib'); const zlib = require('zlib');
for (const Compressor of [ zlib.Gzip, zlib.BrotliCompress ]) { const compressors = [
const gz = Compressor(); [zlib.Gzip, 20],
[zlib.BrotliCompress, 1],
[zlib.ZstdCompress, 9],
];
for (const [Compressor, expected] of compressors) {
const gz = new Compressor();
const emptyBuffer = Buffer.alloc(0); const emptyBuffer = Buffer.alloc(0);
let received = 0; let received = 0;
gz.on('data', function(c) { gz.on('data', function(c) {
@@ -33,7 +39,6 @@ for (const Compressor of [ zlib.Gzip, zlib.BrotliCompress ]) {
}); });
gz.on('end', common.mustCall(function() { gz.on('end', common.mustCall(function() {
const expected = Compressor === zlib.Gzip ? 20 : 1;
assert.strictEqual(received, expected, assert.strictEqual(received, expected,
`${received}, ${expected}, ${Compressor.name}`); `${received}, ${expected}, ${Compressor.name}`);
})); }));

View File

@@ -0,0 +1,28 @@
'use strict';
require('../common');
const assert = require('assert');
const zlib = require('zlib');
const fixtures = require('../common/fixtures');
const file = fixtures.readSync('person.jpg');
const chunkSize = 16;
const compress = new zlib.ZstdCompress();
const chunk = file.slice(0, chunkSize);
const expectedFull = Buffer.from('KLUv/QBYgAAA/9j/4AAQSkZJRgABAQEASA==', 'base64');
let actualFull;
compress.write(chunk, function() {
compress.flush(function() {
const bufs = [];
let buf;
while ((buf = compress.read()) !== null)
bufs.push(buf);
actualFull = Buffer.concat(bufs);
});
});
process.once('exit', function() {
assert.deepStrictEqual(actualFull.toString('base64'), expectedFull.toString('base64'));
assert.deepStrictEqual(actualFull, expectedFull);
});

View File

@@ -0,0 +1,38 @@
'use strict';
// Test compressing and uncompressing a string with zstd
const common = require('../common');
const assert = require('assert');
const zlib = require('zlib');
const inputString = 'ΩΩLorem ipsum dolor sit amet, consectetur adipiscing eli' +
't. Morbi faucibus, purus at gravida dictum, libero arcu ' +
'convallis lacus, in commodo libero metus eu nisi. Nullam' +
' commodo, neque nec porta placerat, nisi est fermentum a' +
'ugue, vitae gravida tellus sapien sit amet tellus. Aenea' +
'n non diam orci. Proin quis elit turpis. Suspendisse non' +
' diam ipsum. Suspendisse nec ullamcorper odio. Vestibulu' +
'm arcu mi, sodales non suscipit id, ultrices ut massa. S' +
'ed ac sem sit amet arcu malesuada fermentum. Nunc sed. ';
const compressedString = 'KLUv/QRYRQkA9tc9H6AlhTb/z/7/gbTI3kaWLKnbCtkZu/hXm0j' +
'FpNz/VQM2ADMANQBHTuQOpIYzfVv7XGwXrpoIfgXNAB98xW4wV3' +
'vnCF2bjcvWZF2wIZ1vr1mSHHvPHU0TgMGBwUFrF0xqReWcWPO8z' +
'Ny6wMwFUilN+Lg987Zvs2GSRMy6uYvtovK9Uuhgst6l9FQrXLnA' +
'5gpZL7PdI8bO9sDH3tHm73XBzaUK+LjSPNKRmzQ3ZMYEPozdof1' +
'2KcZGfIcLa0PTsdkYqhGcAx/E9mWa8EGEeq0Qou2LTmzgg3YJz/' +
'21OuXSF+TOd662d60Qyb04xC5dOF4b8JFH8mpHAxAAELu3tg1oa' +
'bBEIWaRHdE0l/+0RdEWWIVMAku8TgbiX/4bU+OpLo4UuY1FKDR8' +
'RgBc';
zlib.zstdCompress(inputString, common.mustCall((err, buffer) => {
assert(inputString.length > buffer.length);
zlib.zstdDecompress(buffer, common.mustCall((err, buffer) => {
assert.strictEqual(buffer.toString(), inputString);
}));
}));
const buffer = Buffer.from(compressedString, 'base64');
zlib.zstdDecompress(buffer, common.mustCall((err, buffer) => {
assert.strictEqual(buffer.toString(), inputString);
}));

View File

@@ -0,0 +1,34 @@
'use strict';
// Test unzipping a file that was created with a non-node zstd lib,
// piped in as fast as possible.
//
// The compressed fixture was created using the reference CLI:
// $ zstd -19 test/fixtures/person.jpg -o test/fixtures/person.jpg.zst
const common = require('../common');
const assert = require('assert');
const zlib = require('zlib');
const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
const decompress = new zlib.ZstdDecompress();
const fs = require('fs');
const fixture = fixtures.path('person.jpg.zst');
const unzippedFixture = fixtures.path('person.jpg');
const outputFile = tmpdir.resolve('person.jpg');
const expect = fs.readFileSync(unzippedFixture);
const inp = fs.createReadStream(fixture);
const out = fs.createWriteStream(outputFile);
inp.pipe(decompress).pipe(out);
out.on('close', common.mustCall(() => {
const actual = fs.readFileSync(outputFile);
assert.strictEqual(actual.length, expect.length);
for (let i = 0, l = actual.length; i < l; i++) {
assert.strictEqual(actual[i], expect[i], `byte[${i}]`);
}
}));

View File

@@ -0,0 +1,29 @@
'use strict';
require('../common');
// This test ensures that zlib throws a RangeError if the final buffer needs to
// be larger than kMaxLength and concatenation fails.
// https://github.com/nodejs/node/pull/1811
const assert = require('assert');
// Change kMaxLength for zlib to trigger the error without having to allocate
// large Buffers.
const buffer = require('buffer');
const oldkMaxLength = buffer.kMaxLength;
buffer.kMaxLength = 64;
const zlib = require('zlib');
buffer.kMaxLength = oldkMaxLength;
// "a".repeat(128), compressed using zstd.
const encoded = Buffer.from('KLUv/SCARQAAEGFhAQA7BVg=', 'base64');
// Async
zlib.zstdDecompress(encoded, function(err) {
assert.ok(err instanceof RangeError);
});
// Sync
assert.throws(function() {
zlib.zstdDecompressSync(encoded);
}, RangeError);

View File

@@ -0,0 +1,37 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const zlib = require('zlib');
function compressWithPledgedSrcSize({ pledgedSrcSize, actualSrcSize }) {
return new Promise((resolve, reject) => {
const compressor = zlib.createZstdCompress({ pledgedSrcSize });
compressor.on('error', (e) => {
reject(e);
});
compressor.on('end', resolve);
compressor.write('x'.repeat(actualSrcSize), () => {
compressor.end();
compressor.resume();
});
}).then(() => {
// Compression should only succeed if sizes match
assert.strictEqual(pledgedSrcSize, actualSrcSize);
}, (error) => {
assert.strictEqual(error.code, 'ZSTD_error_srcSize_wrong');
// Size error should only happen when sizes do not match
assert.notStrictEqual(pledgedSrcSize, actualSrcSize);
}).then(common.mustCall());
}
compressWithPledgedSrcSize({ pledgedSrcSize: 0, actualSrcSize: 0 });
compressWithPledgedSrcSize({ pledgedSrcSize: 0, actualSrcSize: 42 });
compressWithPledgedSrcSize({ pledgedSrcSize: 13, actualSrcSize: 42 });
compressWithPledgedSrcSize({ pledgedSrcSize: 42, actualSrcSize: 0 });
compressWithPledgedSrcSize({ pledgedSrcSize: 42, actualSrcSize: 13 });
compressWithPledgedSrcSize({ pledgedSrcSize: 42, actualSrcSize: 42 });

View File

@@ -0,0 +1,134 @@
'use strict';
require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const zlib = require('zlib');
// Test some zstd-specific properties of the zstd streams that can not
// be easily covered through expanding zlib-only tests.
const sampleBuffer = fixtures.readSync('/pss-vectors.json');
{
// Test setting the quality parameter at stream creation:
const sizes = [];
for (let quality = 1;
quality <= 22;
quality++) {
const encoded = zlib.zstdCompressSync(sampleBuffer, {
params: {
[zlib.constants.ZSTD_c_compressionLevel]: quality
}
});
sizes.push(encoded.length);
}
// Increasing quality should roughly correspond to decreasing compressed size:
for (let i = 0; i < sizes.length - 1; i++) {
assert(sizes[i + 1] <= sizes[i] * 1.05, sizes); // 5 % margin of error.
}
assert(sizes[0] > sizes[sizes.length - 1], sizes);
}
{
// Test that setting out-of-bounds option values or keys fails.
assert.throws(() => {
zlib.createZstdCompress({
params: {
10000: 0
}
});
}, {
code: 'ERR_ZSTD_INVALID_PARAM',
name: 'RangeError',
message: '10000 is not a valid zstd parameter'
});
// Test that accidentally using duplicate keys fails.
assert.throws(() => {
zlib.createZstdCompress({
params: {
'0': 0,
'00': 0
}
});
}, {
code: 'ERR_ZSTD_INVALID_PARAM',
name: 'RangeError',
message: '00 is not a valid zstd parameter'
});
assert.throws(() => {
zlib.createZstdCompress({
params: {
// This param must be a valid ZSTD_strategy value.
[zlib.constants.ZSTD_c_strategy]: 130
}
});
}, {
code: 'ERR_ZLIB_INITIALIZATION_FAILED',
name: 'Error',
message: 'Setting parameter failed'
});
// Test that setting out-of-bounds option values or keys fails.
assert.throws(() => {
zlib.createZstdDecompress({
params: {
10000: 0
}
});
}, {
code: 'ERR_ZSTD_INVALID_PARAM',
name: 'RangeError',
message: '10000 is not a valid zstd parameter'
});
// Test that accidentally using duplicate keys fails.
assert.throws(() => {
zlib.createZstdDecompress({
params: {
'0': 0,
'00': 0
}
});
}, {
code: 'ERR_ZSTD_INVALID_PARAM',
name: 'RangeError',
message: '00 is not a valid zstd parameter'
});
assert.throws(() => {
zlib.createZstdDecompress({
params: {
// This param must be >= 10 (ZSTD_WINDOWLOG_ABSOLUTEMIN).
[zlib.constants.ZSTD_d_windowLogMax]: 1
}
});
}, {
code: 'ERR_ZLIB_INITIALIZATION_FAILED',
name: 'Error',
message: 'Setting parameter failed'
});
}
{
// Test options.flush range
assert.throws(() => {
zlib.zstdCompressSync('', { flush: zlib.constants.Z_FINISH });
}, {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "options.flush" is out of range. It must be >= 0 ' +
'and <= 2. Received 4',
});
assert.throws(() => {
zlib.zstdCompressSync('', { finishFlush: zlib.constants.Z_FINISH });
}, {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "options.finishFlush" is out of range. It must be ' +
'>= 0 and <= 2. Received 4',
});
}

View File

@@ -42,6 +42,7 @@ let zlibPairs = [
[zlib.Gzip, zlib.Unzip], [zlib.Gzip, zlib.Unzip],
[zlib.DeflateRaw, zlib.InflateRaw], [zlib.DeflateRaw, zlib.InflateRaw],
[zlib.BrotliCompress, zlib.BrotliDecompress], [zlib.BrotliCompress, zlib.BrotliDecompress],
[zlib.ZstdCompress, zlib.ZstdDecompress],
]; ];
// How fast to trickle through the slowstream // How fast to trickle through the slowstream

View File

@@ -1,10 +1,13 @@
import { beforeAll, describe, expect, test } from "bun:test"; import { beforeAll, describe, expect, test } from "bun:test";
import { isASAN } from "harness";
import { promisify } from "node:util"; import { promisify } from "node:util";
import zlib from "node:zlib"; import zlib from "node:zlib";
const input = Buffer.alloc(50000); const input = Buffer.alloc(50000);
for (let i = 0; i < input.length; i++) input[i] = Math.random(); for (let i = 0; i < input.length; i++) input[i] = Math.random();
const upper = 1024 * 1024 * (isASAN ? 15 : 10);
describe("zlib compression does not leak memory", () => { describe("zlib compression does not leak memory", () => {
beforeAll(() => { beforeAll(() => {
for (let index = 0; index < 10_000; index++) { for (let index = 0; index < 10_000; index++) {
@@ -30,8 +33,8 @@ describe("zlib compression does not leak memory", () => {
const after = process.memoryUsage.rss(); const after = process.memoryUsage.rss();
console.log(after); console.log(after);
console.log("-", after - baseline); console.log("-", after - baseline);
console.log("-", 1024 * 1024 * 10); console.log("-", upper);
expect(after - baseline).toBeLessThan(1024 * 1024 * 10); expect(after - baseline).toBeLessThan(upper);
}, },
0, 0,
); );
@@ -53,8 +56,8 @@ describe("zlib compression does not leak memory", () => {
const after = process.memoryUsage.rss(); const after = process.memoryUsage.rss();
console.log(after); console.log(after);
console.log("-", after - baseline); console.log("-", after - baseline);
console.log("-", 1024 * 1024 * 10); console.log("-", upper);
expect(after - baseline).toBeLessThan(1024 * 1024 * 10); expect(after - baseline).toBeLessThan(upper);
}, },
0, 0,
); );
@@ -73,8 +76,8 @@ describe("zlib compression does not leak memory", () => {
const after = process.memoryUsage.rss(); const after = process.memoryUsage.rss();
console.log(after); console.log(after);
console.log("-", after - baseline); console.log("-", after - baseline);
console.log("-", 1024 * 1024 * 10); console.log("-", upper);
expect(after - baseline).toBeLessThan(1024 * 1024 * 10); expect(after - baseline).toBeLessThan(upper);
}, 0); }, 0);
test("brotliCompressSync", async () => { test("brotliCompressSync", async () => {
@@ -90,7 +93,41 @@ describe("zlib compression does not leak memory", () => {
const after = process.memoryUsage.rss(); const after = process.memoryUsage.rss();
console.log(after); console.log(after);
console.log("-", after - baseline); console.log("-", after - baseline);
console.log("-", 1024 * 1024 * 10); console.log("-", upper);
expect(after - baseline).toBeLessThan(1024 * 1024 * 10); expect(after - baseline).toBeLessThan(upper);
}, 0);
test("zstdCompress", async () => {
for (let index = 0; index < 1_000; index++) {
await promisify(zlib.zstdCompress)(input);
}
const baseline = process.memoryUsage.rss();
console.log(baseline);
for (let index = 0; index < 1_000; index++) {
await promisify(zlib.zstdCompress)(input);
}
Bun.gc(true);
const after = process.memoryUsage.rss();
console.log(after);
console.log("-", after - baseline);
console.log("-", upper);
expect(after - baseline).toBeLessThan(upper);
}, 0);
test("zstdCompressSync", async () => {
for (let index = 0; index < 1_000; index++) {
zlib.zstdCompressSync(input);
}
const baseline = process.memoryUsage.rss();
console.log(baseline);
for (let index = 0; index < 1_000; index++) {
zlib.zstdCompressSync(input);
}
Bun.gc(true);
const after = process.memoryUsage.rss();
console.log(after);
console.log("-", after - baseline);
console.log("-", upper);
expect(after - baseline).toBeLessThan(upper);
}, 0); }, 0);
}); });

View File

@@ -51,6 +51,26 @@ describe("prototype and name and constructor", () => {
}); });
}); });
} }
for (let [name, Class] of [
["ZstdCompress", zlib.ZstdCompress],
["ZstdDecompress", zlib.ZstdDecompress],
]) {
describe(`${name}`, () => {
it(`${name}.prototype should be instanceof ${name}.__proto__`, () => {
expect(Class.prototype).toBeInstanceOf(Class.__proto__);
});
it(`${name}.prototype.constructor should be ${name}`, () => {
expect(Class.prototype.constructor).toBe(Class);
});
it(`${name}.name should be ${name}`, () => {
expect(Class.name).toBe(name);
});
it(`${name}.prototype.__proto__.constructor.name should be Zstd`, () => {
expect(Class.prototype.__proto__.constructor.name).toBe("Zstd");
});
});
}
}); });
describe("zlib", () => { describe("zlib", () => {
@@ -198,21 +218,23 @@ describe("zlib.brotli", () => {
const out_path_d = resolve(x_dir, "this.js"); const out_path_d = resolve(x_dir, "this.js");
{ {
const { resolve, promise } = Promise.withResolvers(); const { resolve, reject, promise } = Promise.withResolvers();
const readStream = fs.createReadStream(import.meta.filename); const readStream = fs.createReadStream(import.meta.filename);
const writeStream = fs.createWriteStream(out_path_c); const writeStream = fs.createWriteStream(out_path_c);
const brStream = zlib.createBrotliCompress(); const brStream = zlib.createBrotliCompress();
const the_stream = readStream.pipe(brStream).pipe(writeStream); const the_stream = readStream.pipe(brStream).pipe(writeStream);
the_stream.on("finish", resolve); the_stream.on("finish", resolve);
the_stream.on("error", reject);
await promise; await promise;
} }
{ {
const { resolve, promise } = Promise.withResolvers(); const { resolve, reject, promise } = Promise.withResolvers();
const readStream = fs.createReadStream(out_path_c); const readStream = fs.createReadStream(out_path_c);
const writeStream = fs.createWriteStream(out_path_d); const writeStream = fs.createWriteStream(out_path_d);
const brStream = zlib.createBrotliDecompress(); const brStream = zlib.createBrotliDecompress();
const the_stream = readStream.pipe(brStream).pipe(writeStream); const the_stream = readStream.pipe(brStream).pipe(writeStream);
the_stream.on("finish", resolve); the_stream.on("finish", resolve);
the_stream.on("error", reject);
await promise; await promise;
} }
{ {
@@ -232,8 +254,10 @@ describe("zlib.brotli", () => {
const rand = createPRNG(1); const rand = createPRNG(1);
let all = []; let all = [];
const { promise, resolve, reject } = Promise.withResolvers();
brotliStream.on("data", chunk => all.push(chunk.length)); brotliStream.on("data", chunk => all.push(chunk.length));
brotliStream.on("end", () => expect(all).toEqual([11180, 13, 14, 13, 13, 13, 14])); brotliStream.on("end", resolve);
brotliStream.on("error", reject);
for (let i = 0; i < 50; i++) { for (let i = 0; i < 50; i++) {
let buf = Buffer.alloc(1024 * 1024); let buf = Buffer.alloc(1024 * 1024);
@@ -242,6 +266,8 @@ describe("zlib.brotli", () => {
} }
readStream.push(null); readStream.push(null);
readStream.pipe(brotliStream); readStream.pipe(brotliStream);
await promise;
expect(all.length).toBeGreaterThanOrEqual(7);
}, 15_000); }, 15_000);
it("should accept params", async () => { it("should accept params", async () => {
@@ -337,6 +363,7 @@ for (const [compress, decompressor] of [
[zlib.deflateRawSync, zlib.createInflateRaw], [zlib.deflateRawSync, zlib.createInflateRaw],
[zlib.deflateSync, zlib.createInflate], [zlib.deflateSync, zlib.createInflate],
[zlib.brotliCompressSync, zlib.createBrotliDecompress], [zlib.brotliCompressSync, zlib.createBrotliDecompress],
[zlib.zstdCompressSync, zlib.createZstdDecompress],
// [zlib.gzipSync, zlib.createGunzip], // [zlib.gzipSync, zlib.createGunzip],
// [zlib.gzipSync, zlib.createUnzip], // [zlib.gzipSync, zlib.createUnzip],
]) { ]) {
@@ -364,7 +391,11 @@ for (const [compress, decompressor] of [
}, },
]; ];
for (const i in variants) { for (const i in variants) {
it(`premature end handles bytesWritten properly: ${compress.name} + ${decompressor.name}: variant ${i}`, async () => { let should_skip = false;
if (decompressor === zlib.createZstdDecompress && i == 1) should_skip = true; // fails in node too
if (decompressor === zlib.createZstdDecompress && i == 2) should_skip = true; // fails in node too
// prettier-ignore
it.skipIf(should_skip)(`premature end handles bytesWritten properly: ${compress.name} + ${decompressor.name}: variant ${i}`, async () => {
const variant = variants[i]; const variant = variants[i];
const { promise, resolve, reject } = Promise.withResolvers(); const { promise, resolve, reject } = Promise.withResolvers();
let output = ""; let output = "";
@@ -469,3 +500,137 @@ for (const C of [zlib.BrotliCompress, zlib.BrotliDecompress]) {
}); });
} }
} }
describe("zlib.zstd", () => {
const inputString =
"ΩΩLorem ipsum dolor sit amet, consectetur adipiscing eli" +
"t. Morbi faucibus, purus at gravida dictum, libero arcu " +
"convallis lacus, in commodo libero metus eu nisi. Nullam" +
" commodo, neque nec porta placerat, nisi est fermentum a" +
"ugue, vitae gravida tellus sapien sit amet tellus. Aenea" +
"n non diam orci. Proin quis elit turpis. Suspendisse non" +
" diam ipsum. Suspendisse nec ullamcorper odio. Vestibulu" +
"m arcu mi, sodales non suscipit id, ultrices ut massa. S" +
"ed ac sem sit amet arcu malesuada fermentum. Nunc sed. ";
const compressedString =
"KLUv/WD5AF0JAGbXPCCgJUkH/8+rqgA3KaVsW+6LfK3JLcnP+I/" +
"Gy1/3Qv9XDTQAMwA0AK+Ch9LCub6tnT62C7QuwrHQHDhhNPcCQl" +
"tMWOrafGy3KO2D79QZ95omy09vwp/TFEAkEIlHOO99cOlZmfRiz" +
"XQ79GvDoY9TxrTgBBfR+77Nd7LkOWlHaGW+aEwd2rSeegWaj9Ns" +
"WAJJ0253u1jQpe3ByWLS5i+24QhTAZygaf4UlqNER3XoAk7QYar" +
"9tjHHV4yHj+tC108zuqMBJ+X2hlpwUqX6vE3r3N7q5QYntVvn3N" +
"8zVDb9UfCMCW1790yV3A88pgvkvQAniSWvFxMAELvECFu0tC1R9" +
"Ijsri5bt2kE/2mLoi2wCpkElnidDMS//DemxlNdHClyl6KeNTCugmAG";
const compressedBuffer = Buffer.from(compressedString, "base64");
it("zstdDecompress", async () => {
const roundtrip = await util.promisify(zlib.zstdDecompress)(compressedBuffer);
expect(roundtrip.toString()).toEqual(inputString);
});
it("zstdCompressSync", () => {
const compressed = zlib.zstdCompressSync(inputString);
expect(compressed.toString("base64")).toEqual(compressedString);
});
it("zstdDecompressSync", () => {
const roundtrip = zlib.zstdDecompressSync(compressedBuffer);
expect(roundtrip.toString()).toEqual(inputString);
});
it("can compress streaming", async () => {
const encoder = zlib.createZstdCompress();
for (const chunk of window(inputString, 55)) {
encoder.push(chunk);
}
encoder.push(null);
const buf = await new Response(encoder).text();
expect(buf).toEqual(inputString);
});
it("can decompress streaming", async () => {
const decoder = zlib.createZstdDecompress();
for (const chunk of window(compressedBuffer, 10)) {
decoder.push(chunk);
}
decoder.push(null);
const buf = await new Response(decoder).bytes();
expect(buf).toEqual(compressedBuffer);
});
it("can roundtrip an empty string", async () => {
const input = "";
const compressed = await util.promisify(zlib.zstdCompress)(input);
const roundtrip = await util.promisify(zlib.zstdDecompress)(compressed);
expect(roundtrip.toString()).toEqual(input);
});
it("can compress streaming big", async () => {
const encoder = zlib.createZstdCompress();
const input = inputString + inputString + inputString + inputString;
for (const chunk of window(input, 65)) {
encoder.push(chunk);
}
encoder.push(null);
const buf = await new Response(encoder).text();
expect(buf).toEqual(input);
});
it("fully works as a stream.Transform", async () => {
const x_dir = tmpdirSync();
const out_path_c = resolve(x_dir, "this.js.br");
const out_path_d = resolve(x_dir, "this.js");
{
const { resolve, reject, promise } = Promise.withResolvers();
const readStream = fs.createReadStream(import.meta.filename);
const writeStream = fs.createWriteStream(out_path_c);
const brStream = zlib.createZstdCompress();
const the_stream = readStream.pipe(brStream).pipe(writeStream);
the_stream.on("finish", resolve);
the_stream.on("error", reject);
await promise;
}
{
const { resolve, reject, promise } = Promise.withResolvers();
const readStream = fs.createReadStream(out_path_c);
const writeStream = fs.createWriteStream(out_path_d);
const brStream = zlib.createZstdDecompress();
const the_stream = readStream.pipe(brStream).pipe(writeStream);
the_stream.on("finish", resolve);
the_stream.on("error", reject);
await promise;
}
{
const expected = await Bun.file(import.meta.filename).text();
const actual = await Bun.file(out_path_d).text();
expect(actual).toEqual(expected);
}
});
it("streaming encode doesn't wait for entire input", async () => {
const createPRNG = seed => {
let state = seed ?? Math.floor(Math.random() * 0x7fffffff);
return () => (state = (1103515245 * state + 12345) % 0x80000000) / 0x7fffffff;
};
const readStream = new stream.Readable();
const zstdStream = zlib.createZstdCompress();
const rand = createPRNG(1);
let all = [];
const { promise, resolve, reject } = Promise.withResolvers();
zstdStream.on("data", chunk => all.push(chunk.length));
zstdStream.on("end", resolve);
zstdStream.on("error", reject);
for (let i = 0; i < 50; i++) {
let buf = Buffer.alloc(1024 * 1024);
for (let j = 0; j < buf.length; j++) buf[j] = (rand() * 256) | 0;
readStream.push(buf);
}
readStream.push(null);
readStream.pipe(zstdStream);
await promise;
expect(all.length).toBeGreaterThanOrEqual(7);
}, 15_000);
});

View File

@@ -657,6 +657,7 @@ describe("fetch() with streaming", () => {
{ headers: { "Content-Encoding": "deflate" }, compression: "deflate-libdeflate" }, { headers: { "Content-Encoding": "deflate" }, compression: "deflate-libdeflate" },
{ headers: { "Content-Encoding": "deflate" }, compression: "deflate_with_headers" }, { headers: { "Content-Encoding": "deflate" }, compression: "deflate_with_headers" },
{ headers: { "Content-Encoding": "br" }, compression: "br" }, { headers: { "Content-Encoding": "br" }, compression: "br" },
{ headers: { "Content-Encoding": "zstd" }, compression: "zstd" },
] as const; ] as const;
function compress(compression, data: Uint8Array) { function compress(compression, data: Uint8Array) {
@@ -685,6 +686,8 @@ describe("fetch() with streaming", () => {
[zlib.constants.BROTLI_PARAM_SIZE_HINT]: 0, [zlib.constants.BROTLI_PARAM_SIZE_HINT]: 0,
}, },
}); });
case "zstd":
return zlib.zstdCompressSync(data, {});
default: default:
return data; return data;
} }
@@ -1241,9 +1244,11 @@ describe("fetch() with streaming", () => {
expect((err as Error).name).toBe("Error"); expect((err as Error).name).toBe("Error");
expect((err as Error).code).toBe("BrotliDecompressionError"); expect((err as Error).code).toBe("BrotliDecompressionError");
} else if (compression === "deflate-libdeflate") { } else if (compression === "deflate-libdeflate") {
// Since the compressed data is different, the error ends up different.
expect((err as Error).name).toBe("Error"); expect((err as Error).name).toBe("Error");
expect((err as Error).code).toBe("ShortRead"); expect((err as Error).code).toBe("ShortRead");
} else if (compression === "zstd") {
expect((err as Error).name).toBe("Error");
expect((err as Error).code).toBe("ZstdDecompressionError");
} else { } else {
expect((err as Error).name).toBe("Error"); expect((err as Error).name).toBe("Error");
expect((err as Error).code).toBe("ZlibError"); expect((err as Error).code).toBe("ZlibError");