mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
zig: node:zlib: tidy (#20362)
Co-authored-by: nektro <5464072+nektro@users.noreply.github.com>
This commit is contained in:
@@ -199,6 +199,8 @@ src/bun.js/node/util/parse_args_utils.zig
|
||||
src/bun.js/node/util/parse_args.zig
|
||||
src/bun.js/node/util/validators.zig
|
||||
src/bun.js/node/win_watcher.zig
|
||||
src/bun.js/node/zlib/NativeBrotli.zig
|
||||
src/bun.js/node/zlib/NativeZlib.zig
|
||||
src/bun.js/node/zlib/NativeZstd.zig
|
||||
src/bun.js/ProcessAutoKiller.zig
|
||||
src/bun.js/rare_data.zig
|
||||
|
||||
@@ -28,8 +28,8 @@ pub const JSBundler = @import("api/JSBundler.zig").JSBundler;
|
||||
pub const JSTranspiler = @import("api/JSTranspiler.zig");
|
||||
pub const Listener = @import("api/bun/socket.zig").Listener;
|
||||
pub const MatchedRoute = @import("api/filesystem_router.zig").MatchedRoute;
|
||||
pub const NativeBrotli = @import("node/node_zlib_binding.zig").SNativeBrotli;
|
||||
pub const NativeZlib = @import("node/node_zlib_binding.zig").SNativeZlib;
|
||||
pub const NativeBrotli = @import("node/zlib/NativeBrotli.zig");
|
||||
pub const NativeZlib = @import("node/zlib/NativeZlib.zig");
|
||||
pub const NodeHTTPResponse = @import("api/server.zig").NodeHTTPResponse;
|
||||
pub const Postgres = @import("../sql/postgres.zig");
|
||||
pub const ResolveMessage = @import("ResolveMessage.zig").ResolveMessage;
|
||||
|
||||
@@ -4,7 +4,6 @@ const JSC = bun.JSC;
|
||||
const string = bun.string;
|
||||
const Output = bun.Output;
|
||||
const ZigString = JSC.ZigString;
|
||||
const validators = @import("./util/validators.zig");
|
||||
const debug = bun.Output.scoped(.zlib, true);
|
||||
const Buffer = bun.api.node.Buffer;
|
||||
|
||||
@@ -287,6 +286,10 @@ pub fn CompressionStream(comptime T: type) type {
|
||||
|
||||
pub const NativeZlib = JSC.Codegen.JSNativeZlib.getConstructor;
|
||||
|
||||
pub const NativeBrotli = JSC.Codegen.JSNativeBrotli.getConstructor;
|
||||
|
||||
pub const NativeZstd = JSC.Codegen.JSNativeZstd.getConstructor;
|
||||
|
||||
pub const CountedKeepAlive = struct {
|
||||
keep_alive: bun.Async.KeepAlive = .{},
|
||||
ref_count: u32 = 0,
|
||||
@@ -310,124 +313,6 @@ pub const CountedKeepAlive = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const SNativeZlib = struct {
|
||||
const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{});
|
||||
pub const ref = RefCount.ref;
|
||||
pub const deref = RefCount.deref;
|
||||
|
||||
pub const js = JSC.Codegen.JSNativeZlib;
|
||||
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: ZlibContext = .{},
|
||||
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.argumentsUndef(4).ptr;
|
||||
|
||||
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 < 1 or mode_int > 7) {
|
||||
return globalThis.throwRangeError(mode_int, .{ .field_name = "mode", .min = 1, .max = 7 });
|
||||
}
|
||||
|
||||
const ptr = bun.new(SNativeZlib, .{
|
||||
.ref_count = .init(),
|
||||
.mode = @enumFromInt(mode_int),
|
||||
.globalThis = globalThis,
|
||||
});
|
||||
ptr.stream.mode = ptr.mode;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
//// adding this didnt help much but leaving it here to compare the number with later
|
||||
pub fn estimatedSize(_: *const SNativeZlib) usize {
|
||||
const internal_state_size = 3309; // @sizeOf(@cImport(@cInclude("deflate.h")).internal_state) @ cloudflare/zlib @ 92530568d2c128b4432467b76a3b54d93d6350bd
|
||||
return @sizeOf(SNativeZlib) + internal_state_size;
|
||||
}
|
||||
|
||||
pub fn init(this: *SNativeZlib, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
const arguments = callframe.argumentsUndef(7).slice();
|
||||
const this_value = callframe.this();
|
||||
|
||||
if (arguments.len != 7) {
|
||||
return globalThis.ERR(.MISSING_ARGS, "init(windowBits, level, memLevel, strategy, writeResult, writeCallback, dictionary)", .{}).throw();
|
||||
}
|
||||
|
||||
const windowBits = try validators.validateInt32(globalThis, arguments[0], "windowBits", .{}, null, null);
|
||||
const level = try validators.validateInt32(globalThis, arguments[1], "level", .{}, null, null);
|
||||
const memLevel = try validators.validateInt32(globalThis, arguments[2], "memLevel", .{}, null, null);
|
||||
const strategy = try validators.validateInt32(globalThis, arguments[3], "strategy", .{}, null, null);
|
||||
// this does not get gc'd because it is stored in the JS object's `this._writeState`. and the JS object is tied to the native handle as `_handle[owner_symbol]`.
|
||||
const writeResult = arguments[4].asArrayBuffer(globalThis).?.asU32().ptr;
|
||||
const writeCallback = try validators.validateFunction(globalThis, "writeCallback", arguments[5]);
|
||||
const dictionary = if (arguments[6].isUndefined()) null else arguments[6].asArrayBuffer(globalThis).?.byteSlice();
|
||||
|
||||
this.write_result = writeResult;
|
||||
js.writeCallbackSetCached(this_value, globalThis, writeCallback);
|
||||
|
||||
// Keep the dictionary alive by keeping a reference to it in the JS object.
|
||||
if (dictionary != null) {
|
||||
js.dictionarySetCached(this_value, globalThis, arguments[6]);
|
||||
}
|
||||
|
||||
this.stream.init(level, windowBits, memLevel, strategy, dictionary);
|
||||
|
||||
return .jsUndefined();
|
||||
}
|
||||
|
||||
pub fn params(this: *SNativeZlib, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
const arguments = callframe.argumentsUndef(2).slice();
|
||||
|
||||
if (arguments.len != 2) {
|
||||
return globalThis.ERR(.MISSING_ARGS, "params(level, strategy)", .{}).throw();
|
||||
}
|
||||
|
||||
const level = try validators.validateInt32(globalThis, arguments[0], "level", .{}, null, null);
|
||||
const strategy = try validators.validateInt32(globalThis, arguments[1], "strategy", .{}, null, null);
|
||||
|
||||
const err = this.stream.setParams(level, strategy);
|
||||
if (err.isError()) {
|
||||
try impl.emitError(this, globalThis, callframe.this(), err);
|
||||
}
|
||||
return .jsUndefined();
|
||||
}
|
||||
|
||||
fn deinit(this: *@This()) void {
|
||||
this.this_value.deinit();
|
||||
this.poll_ref.deinit();
|
||||
this.stream.close();
|
||||
bun.destroy(this);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Error = struct {
|
||||
msg: ?[*:0]const u8,
|
||||
err: c_int,
|
||||
@@ -447,519 +332,3 @@ pub const Error = struct {
|
||||
return this.msg != null;
|
||||
}
|
||||
};
|
||||
|
||||
const ZlibContext = struct {
|
||||
const c = bun.zlib;
|
||||
const GZIP_HEADER_ID1: u8 = 0x1f;
|
||||
const GZIP_HEADER_ID2: u8 = 0x8b;
|
||||
|
||||
mode: c.NodeMode = .NONE,
|
||||
state: c.z_stream = std.mem.zeroes(c.z_stream),
|
||||
err: c.ReturnCode = .Ok,
|
||||
flush: c.FlushValue = .NoFlush,
|
||||
dictionary: []const u8 = "",
|
||||
gzip_id_bytes_read: u8 = 0,
|
||||
|
||||
pub fn init(this: *ZlibContext, level: c_int, windowBits: c_int, memLevel: c_int, strategy: c_int, dictionary: ?[]const u8) void {
|
||||
this.flush = .NoFlush;
|
||||
this.err = .Ok;
|
||||
|
||||
const windowBitsActual = switch (this.mode) {
|
||||
.NONE => unreachable,
|
||||
.DEFLATE, .INFLATE => windowBits,
|
||||
.GZIP, .GUNZIP => windowBits + 16,
|
||||
.UNZIP => windowBits + 32,
|
||||
.DEFLATERAW, .INFLATERAW => windowBits * -1,
|
||||
.BROTLI_DECODE, .BROTLI_ENCODE => unreachable,
|
||||
.ZSTD_COMPRESS, .ZSTD_DECOMPRESS => unreachable,
|
||||
};
|
||||
|
||||
this.dictionary = dictionary orelse "";
|
||||
|
||||
switch (this.mode) {
|
||||
.NONE => unreachable,
|
||||
.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)),
|
||||
.BROTLI_DECODE, .BROTLI_ENCODE => unreachable,
|
||||
.ZSTD_COMPRESS, .ZSTD_DECOMPRESS => unreachable,
|
||||
}
|
||||
if (this.err != .Ok) {
|
||||
this.mode = .NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
_ = this.setDictionary();
|
||||
}
|
||||
|
||||
pub fn setDictionary(this: *ZlibContext) Error {
|
||||
const dict = this.dictionary;
|
||||
if (dict.len == 0) return Error.ok;
|
||||
this.err = .Ok;
|
||||
switch (this.mode) {
|
||||
.DEFLATE, .DEFLATERAW => {
|
||||
this.err = c.deflateSetDictionary(&this.state, dict.ptr, @intCast(dict.len));
|
||||
},
|
||||
.INFLATERAW => {
|
||||
this.err = c.inflateSetDictionary(&this.state, dict.ptr, @intCast(dict.len));
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
if (this.err != .Ok) {
|
||||
return this.error_for_message("Failed to set dictionary");
|
||||
}
|
||||
return Error.ok;
|
||||
}
|
||||
|
||||
pub fn setParams(this: *ZlibContext, level: c_int, strategy: c_int) Error {
|
||||
this.err = .Ok;
|
||||
switch (this.mode) {
|
||||
.DEFLATE, .DEFLATERAW => {
|
||||
this.err = c.deflateParams(&this.state, level, strategy);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
if (this.err != .Ok and this.err != .BufError) {
|
||||
return this.error_for_message("Failed to set parameters");
|
||||
}
|
||||
return Error.ok;
|
||||
}
|
||||
|
||||
fn error_for_message(this: *ZlibContext, default: [*:0]const u8) Error {
|
||||
var message = default;
|
||||
if (this.state.err_msg) |msg| message = msg;
|
||||
return .{
|
||||
.msg = message,
|
||||
.err = @intFromEnum(this.err),
|
||||
.code = switch (this.err) {
|
||||
.Ok => "Z_OK",
|
||||
.StreamEnd => "Z_STREAM_END",
|
||||
.NeedDict => "Z_NEED_DICT",
|
||||
.ErrNo => "Z_ERRNO",
|
||||
.StreamError => "Z_STREAM_ERROR",
|
||||
.DataError => "Z_DATA_ERROR",
|
||||
.MemError => "Z_MEM_ERROR",
|
||||
.BufError => "Z_BUF_ERROR",
|
||||
.VersionError => "Z_VERSION_ERROR",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn reset(this: *ZlibContext) Error {
|
||||
this.err = .Ok;
|
||||
switch (this.mode) {
|
||||
.DEFLATE, .DEFLATERAW, .GZIP => {
|
||||
this.err = c.deflateReset(&this.state);
|
||||
},
|
||||
.INFLATE, .INFLATERAW, .GUNZIP => {
|
||||
this.err = c.inflateReset(&this.state);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
if (this.err != .Ok) {
|
||||
return this.error_for_message("Failed to reset stream");
|
||||
}
|
||||
return this.setDictionary();
|
||||
}
|
||||
|
||||
pub fn setBuffers(this: *ZlibContext, in: ?[]const u8, out: ?[]u8) void {
|
||||
this.state.avail_in = if (in) |p| @intCast(p.len) else 0;
|
||||
this.state.next_in = if (in) |p| p.ptr else null;
|
||||
this.state.avail_out = if (out) |p| @intCast(p.len) else 0;
|
||||
this.state.next_out = if (out) |p| p.ptr else null;
|
||||
}
|
||||
|
||||
pub fn setFlush(this: *ZlibContext, flush: c_int) void {
|
||||
this.flush = @enumFromInt(flush);
|
||||
}
|
||||
|
||||
pub fn doWork(this: *ZlibContext) void {
|
||||
var next_expected_header_byte: ?[*]const u8 = null;
|
||||
|
||||
// If the avail_out is left at 0, then it means that it ran out
|
||||
// of room. If there was avail_out left over, then it means
|
||||
// that all of the input was consumed.
|
||||
switch (this.mode) {
|
||||
.DEFLATE, .GZIP, .DEFLATERAW => {
|
||||
return this.doWorkDeflate();
|
||||
},
|
||||
.UNZIP => {
|
||||
if (this.state.avail_in > 0) {
|
||||
next_expected_header_byte = this.state.next_in.?;
|
||||
}
|
||||
if (this.gzip_id_bytes_read == 0) {
|
||||
if (next_expected_header_byte == null) {
|
||||
return this.doWorkInflate();
|
||||
}
|
||||
if (next_expected_header_byte.?[0] == GZIP_HEADER_ID1) {
|
||||
this.gzip_id_bytes_read = 1;
|
||||
next_expected_header_byte.? += 1;
|
||||
if (this.state.avail_in == 1) { // The only available byte was already read.
|
||||
return this.doWorkInflate();
|
||||
}
|
||||
} else {
|
||||
this.mode = .INFLATE;
|
||||
return this.doWorkInflate();
|
||||
}
|
||||
}
|
||||
if (this.gzip_id_bytes_read == 1) {
|
||||
if (next_expected_header_byte == null) {
|
||||
return this.doWorkInflate();
|
||||
}
|
||||
if (next_expected_header_byte.?[0] == GZIP_HEADER_ID2) {
|
||||
this.gzip_id_bytes_read = 2;
|
||||
this.mode = .GUNZIP;
|
||||
} else {
|
||||
this.mode = .INFLATE;
|
||||
}
|
||||
return this.doWorkInflate();
|
||||
}
|
||||
bun.assert(false); // invalid number of gzip magic number bytes read
|
||||
},
|
||||
.INFLATE, .GUNZIP, .INFLATERAW => {
|
||||
return this.doWorkInflate();
|
||||
},
|
||||
.NONE => {},
|
||||
.BROTLI_ENCODE, .BROTLI_DECODE => {},
|
||||
.ZSTD_COMPRESS, .ZSTD_DECOMPRESS => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn doWorkDeflate(this: *ZlibContext) void {
|
||||
this.err = c.deflate(&this.state, this.flush);
|
||||
}
|
||||
|
||||
fn doWorkInflate(this: *ZlibContext) void {
|
||||
this.err = c.inflate(&this.state, this.flush);
|
||||
|
||||
if (this.mode != .INFLATERAW and this.err == .NeedDict and this.dictionary.len > 0) {
|
||||
this.err = c.inflateSetDictionary(&this.state, this.dictionary.ptr, @intCast(this.dictionary.len));
|
||||
|
||||
if (this.err == .Ok) {
|
||||
this.err = c.inflate(&this.state, this.flush);
|
||||
} else if (this.err == .DataError) {
|
||||
this.err = .NeedDict;
|
||||
}
|
||||
}
|
||||
while (this.state.avail_in > 0 and this.mode == .GUNZIP and this.err == .StreamEnd and this.state.next_in.?[0] != 0) {
|
||||
// Bytes remain in input buffer. Perhaps this is another compressed member in the same archive, or just trailing garbage.
|
||||
// Trailing zero bytes are okay, though, since they are frequently used for padding.
|
||||
_ = this.reset();
|
||||
this.err = c.inflate(&this.state, this.flush);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn updateWriteResult(this: *ZlibContext, avail_in: *u32, avail_out: *u32) void {
|
||||
avail_in.* = this.state.avail_in;
|
||||
avail_out.* = this.state.avail_out;
|
||||
}
|
||||
|
||||
pub fn getErrorInfo(this: *ZlibContext) Error {
|
||||
switch (this.err) {
|
||||
.Ok, .BufError => {
|
||||
if (this.state.avail_out != 0 and this.flush == .Finish) {
|
||||
return this.error_for_message("unexpected end of file");
|
||||
}
|
||||
},
|
||||
.StreamEnd => {},
|
||||
.NeedDict => {
|
||||
if (this.dictionary.len == 0) {
|
||||
return this.error_for_message("Missing dictionary");
|
||||
} else {
|
||||
return this.error_for_message("Bad dictionary");
|
||||
}
|
||||
},
|
||||
else => {
|
||||
return this.error_for_message("Zlib error");
|
||||
},
|
||||
}
|
||||
return Error.ok;
|
||||
}
|
||||
|
||||
pub fn close(this: *ZlibContext) void {
|
||||
var status = c.ReturnCode.Ok;
|
||||
switch (this.mode) {
|
||||
.DEFLATE, .DEFLATERAW, .GZIP => {
|
||||
status = c.deflateEnd(&this.state);
|
||||
},
|
||||
.INFLATE, .INFLATERAW, .GUNZIP, .UNZIP => {
|
||||
status = c.inflateEnd(&this.state);
|
||||
},
|
||||
.NONE => {},
|
||||
.BROTLI_ENCODE, .BROTLI_DECODE => {},
|
||||
.ZSTD_COMPRESS, .ZSTD_DECOMPRESS => {},
|
||||
}
|
||||
bun.assert(status == .Ok or status == .DataError);
|
||||
this.mode = .NONE;
|
||||
}
|
||||
};
|
||||
|
||||
pub const NativeBrotli = JSC.Codegen.JSNativeBrotli.getConstructor;
|
||||
|
||||
pub const SNativeBrotli = struct {
|
||||
const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{});
|
||||
pub const ref = RefCount.ref;
|
||||
pub const deref = RefCount.deref;
|
||||
|
||||
pub const js = JSC.Codegen.JSNativeBrotli;
|
||||
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: BrotliContext = .{},
|
||||
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.argumentsUndef(1).ptr;
|
||||
|
||||
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 < 8 or mode_int > 9) {
|
||||
return globalThis.throwRangeError(mode_int, .{ .field_name = "mode", .min = 8, .max = 9 });
|
||||
}
|
||||
|
||||
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 SNativeBrotli) usize {
|
||||
const encoder_state_size: usize = 5143; // @sizeOf(@cImport(@cInclude("brotli/encode.h")).BrotliEncoderStateStruct)
|
||||
const decoder_state_size: usize = 855; // @sizeOf(@cImport(@cInclude("brotli/decode.h")).BrotliDecoderStateStruct)
|
||||
return @sizeOf(SNativeBrotli) + switch (this.mode) {
|
||||
.BROTLI_ENCODE => encoder_state_size,
|
||||
.BROTLI_DECODE => decoder_state_size,
|
||||
else => 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init(this: *SNativeBrotli, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
const arguments = callframe.argumentsUndef(3).slice();
|
||||
const this_value = callframe.this();
|
||||
if (arguments.len != 3) {
|
||||
return globalThis.ERR(.MISSING_ARGS, "init(params, writeResult, writeCallback)", .{}).throw();
|
||||
}
|
||||
|
||||
// this does not get gc'd because it is stored in the JS object's `this._writeState`. and the JS object is tied to the native handle as `_handle[owner_symbol]`.
|
||||
const writeResult = arguments[1].asArrayBuffer(globalThis).?.asU32().ptr;
|
||||
const writeCallback = try validators.validateFunction(globalThis, "writeCallback", arguments[2]);
|
||||
|
||||
this.write_result = writeResult;
|
||||
|
||||
js.writeCallbackSetCached(this_value, globalThis, writeCallback);
|
||||
|
||||
var err = this.stream.init();
|
||||
if (err.isError()) {
|
||||
try impl.emitError(this, globalThis, this_value, err);
|
||||
return JSC.jsBoolean(false);
|
||||
}
|
||||
|
||||
const params_ = arguments[0].asArrayBuffer(globalThis).?.asU32();
|
||||
|
||||
for (params_, 0..) |d, i| {
|
||||
// (d == -1) {
|
||||
if (d == std.math.maxInt(u32)) {
|
||||
continue;
|
||||
}
|
||||
err = this.stream.setParams(@intCast(i), d);
|
||||
if (err.isError()) {
|
||||
// try impl.emitError(this, globalThis, this_value, err); //XXX: onerror isn't set yet
|
||||
return JSC.jsBoolean(false);
|
||||
}
|
||||
}
|
||||
return JSC.jsBoolean(true);
|
||||
}
|
||||
|
||||
pub fn params(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
_ = this;
|
||||
_ = globalThis;
|
||||
_ = callframe;
|
||||
// intentionally left empty
|
||||
return .jsUndefined();
|
||||
}
|
||||
|
||||
fn deinit(this: *@This()) void {
|
||||
this.this_value.deinit();
|
||||
this.poll_ref.deinit();
|
||||
switch (this.stream.mode) {
|
||||
.BROTLI_ENCODE, .BROTLI_DECODE => this.stream.close(),
|
||||
else => {},
|
||||
}
|
||||
bun.destroy(this);
|
||||
}
|
||||
};
|
||||
|
||||
const BrotliContext = struct {
|
||||
const c = bun.brotli.c;
|
||||
const Op = bun.brotli.c.BrotliEncoder.Operation;
|
||||
|
||||
mode: bun.zlib.NodeMode = .NONE,
|
||||
mode_: bun.zlib.NodeMode = .NONE,
|
||||
state: *anyopaque = undefined,
|
||||
|
||||
next_in: ?[*]const u8 = null,
|
||||
next_out: ?[*]u8 = null,
|
||||
avail_in: usize = 0,
|
||||
avail_out: usize = 0,
|
||||
|
||||
flush: Op = .process,
|
||||
|
||||
last_result: extern union { e: c_int, d: c.BrotliDecoderResult } = @bitCast(@as(u32, 0)),
|
||||
error_: c.BrotliDecoderErrorCode2 = .NO_ERROR,
|
||||
|
||||
pub fn init(this: *BrotliContext) Error {
|
||||
switch (this.mode_) {
|
||||
.BROTLI_ENCODE => {
|
||||
const alloc = &bun.brotli.BrotliAllocator.alloc;
|
||||
const free = &bun.brotli.BrotliAllocator.free;
|
||||
const state = c.BrotliEncoderCreateInstance(alloc, free, null);
|
||||
if (state == null) {
|
||||
return Error.init("Could not initialize Brotli instance", -1, "ERR_ZLIB_INITIALIZATION_FAILED");
|
||||
}
|
||||
this.state = @ptrCast(state.?);
|
||||
return Error.ok;
|
||||
},
|
||||
.BROTLI_DECODE => {
|
||||
const alloc = &bun.brotli.BrotliAllocator.alloc;
|
||||
const free = &bun.brotli.BrotliAllocator.free;
|
||||
const state = c.BrotliDecoderCreateInstance(alloc, free, null);
|
||||
if (state == null) {
|
||||
return Error.init("Could not initialize Brotli instance", -1, "ERR_ZLIB_INITIALIZATION_FAILED");
|
||||
}
|
||||
this.state = @ptrCast(state.?);
|
||||
return Error.ok;
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setParams(this: *BrotliContext, key: c_uint, value: u32) Error {
|
||||
switch (this.mode_) {
|
||||
.BROTLI_ENCODE => {
|
||||
if (c.BrotliEncoderSetParameter(@ptrCast(this.state), key, value) == 0) {
|
||||
return Error.init("Setting parameter failed", -1, "ERR_BROTLI_PARAM_SET_FAILED");
|
||||
}
|
||||
return Error.ok;
|
||||
},
|
||||
.BROTLI_DECODE => {
|
||||
if (c.BrotliDecoderSetParameter(@ptrCast(this.state), key, value) == 0) {
|
||||
return Error.init("Setting parameter failed", -1, "ERR_BROTLI_PARAM_SET_FAILED");
|
||||
}
|
||||
return Error.ok;
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(this: *BrotliContext) Error {
|
||||
return this.init();
|
||||
}
|
||||
|
||||
pub fn setBuffers(this: *BrotliContext, in: ?[]const u8, out: ?[]u8) void {
|
||||
this.next_in = if (in) |p| p.ptr else null;
|
||||
this.next_out = if (out) |p| p.ptr else null;
|
||||
this.avail_in = if (in) |p| p.len else 0;
|
||||
this.avail_out = if (out) |p| p.len else 0;
|
||||
}
|
||||
|
||||
pub fn setFlush(this: *BrotliContext, flush: c_int) void {
|
||||
this.flush = @enumFromInt(flush);
|
||||
}
|
||||
|
||||
pub fn doWork(this: *BrotliContext) void {
|
||||
switch (this.mode_) {
|
||||
.BROTLI_ENCODE => {
|
||||
var next_in = this.next_in;
|
||||
this.last_result.e = c.BrotliEncoderCompressStream(@ptrCast(this.state), this.flush, &this.avail_in, &next_in, &this.avail_out, &this.next_out, null);
|
||||
this.next_in.? += @intFromPtr(next_in.?) - @intFromPtr(this.next_in.?);
|
||||
},
|
||||
.BROTLI_DECODE => {
|
||||
var next_in = this.next_in;
|
||||
this.last_result.d = c.BrotliDecoderDecompressStream(@ptrCast(this.state), &this.avail_in, &next_in, &this.avail_out, &this.next_out, null);
|
||||
this.next_in.? += @intFromPtr(next_in.?) - @intFromPtr(this.next_in.?);
|
||||
if (this.last_result.d == .err) {
|
||||
this.error_ = c.BrotliDecoderGetErrorCode(@ptrCast(this.state));
|
||||
}
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn updateWriteResult(this: *BrotliContext, avail_in: *u32, avail_out: *u32) void {
|
||||
avail_in.* = @intCast(this.avail_in);
|
||||
avail_out.* = @intCast(this.avail_out);
|
||||
}
|
||||
|
||||
pub fn getErrorInfo(this: *BrotliContext) Error {
|
||||
switch (this.mode_) {
|
||||
.BROTLI_ENCODE => {
|
||||
if (this.last_result.e == 0) {
|
||||
return Error.init("Compression failed", -1, "ERR_BROTLI_COMPRESSION_FAILED");
|
||||
}
|
||||
return Error.ok;
|
||||
},
|
||||
.BROTLI_DECODE => {
|
||||
if (this.error_ != .NO_ERROR) {
|
||||
return Error.init("Decompression failed", @intFromEnum(this.error_), code_for_error(this.error_));
|
||||
} else if (this.flush == .finish and this.last_result.d == .needs_more_input) {
|
||||
return Error.init("unexpected end of file", @intFromEnum(bun.zlib.ReturnCode.BufError), "Z_BUF_ERROR");
|
||||
}
|
||||
return Error.ok;
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(this: *BrotliContext) void {
|
||||
switch (this.mode_) {
|
||||
.BROTLI_ENCODE => c.BrotliEncoderDestroyInstance(@ptrCast(@alignCast(this.state))),
|
||||
.BROTLI_DECODE => c.BrotliDecoderDestroyInstance(@ptrCast(@alignCast(this.state))),
|
||||
else => unreachable,
|
||||
}
|
||||
this.mode = .NONE;
|
||||
}
|
||||
|
||||
fn code_for_error(err: c.BrotliDecoderErrorCode2) [:0]const u8 {
|
||||
const E = c.BrotliDecoderErrorCode2;
|
||||
const names = comptime std.meta.fieldNames(E);
|
||||
const values = comptime std.enums.values(E);
|
||||
inline for (names, values) |n, v| {
|
||||
if (err == v) {
|
||||
return "ERR_BROTLI_DECODER_" ++ n;
|
||||
}
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
};
|
||||
|
||||
pub const NativeZstd = JSC.Codegen.JSNativeZstd.getConstructor;
|
||||
|
||||
268
src/bun.js/node/zlib/NativeBrotli.zig
Normal file
268
src/bun.js/node/zlib/NativeBrotli.zig
Normal file
@@ -0,0 +1,268 @@
|
||||
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.JSNativeBrotli;
|
||||
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,
|
||||
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.argumentsUndef(1).ptr;
|
||||
|
||||
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 < 8 or mode_int > 9) {
|
||||
return globalThis.throwRangeError(mode_int, .{ .field_name = "mode", .min = 8, .max = 9 });
|
||||
}
|
||||
|
||||
const ptr = bun.new(@This(), .{
|
||||
.ref_count = .init(),
|
||||
.globalThis = globalThis,
|
||||
});
|
||||
ptr.stream.mode = @enumFromInt(mode_int);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
pub fn estimatedSize(this: *const @This()) usize {
|
||||
const encoder_state_size: usize = 5143; // @sizeOf(@cImport(@cInclude("brotli/encode.h")).BrotliEncoderStateStruct)
|
||||
const decoder_state_size: usize = 855; // @sizeOf(@cImport(@cInclude("brotli/decode.h")).BrotliDecoderStateStruct)
|
||||
return @sizeOf(@This()) + switch (this.stream.mode) {
|
||||
.BROTLI_ENCODE => encoder_state_size,
|
||||
.BROTLI_DECODE => decoder_state_size,
|
||||
else => 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
const arguments = callframe.argumentsUndef(3).slice();
|
||||
const this_value = callframe.this();
|
||||
if (arguments.len != 3) {
|
||||
return globalThis.ERR(.MISSING_ARGS, "init(params, writeResult, writeCallback)", .{}).throw();
|
||||
}
|
||||
|
||||
// this does not get gc'd because it is stored in the JS object's `this._writeState`. and the JS object is tied to the native handle as `_handle[owner_symbol]`.
|
||||
const writeResult = arguments[1].asArrayBuffer(globalThis).?.asU32().ptr;
|
||||
const writeCallback = try validators.validateFunction(globalThis, "writeCallback", arguments[2]);
|
||||
|
||||
this.write_result = writeResult;
|
||||
|
||||
js.writeCallbackSetCached(this_value, globalThis, writeCallback);
|
||||
|
||||
var err = this.stream.init();
|
||||
if (err.isError()) {
|
||||
try impl.emitError(this, globalThis, this_value, err);
|
||||
return JSC.jsBoolean(false);
|
||||
}
|
||||
|
||||
const params_ = arguments[0].asArrayBuffer(globalThis).?.asU32();
|
||||
|
||||
for (params_, 0..) |d, i| {
|
||||
// (d == -1) {
|
||||
if (d == std.math.maxInt(u32)) {
|
||||
continue;
|
||||
}
|
||||
err = this.stream.setParams(@intCast(i), d);
|
||||
if (err.isError()) {
|
||||
// try impl.emitError(this, globalThis, this_value, err); //XXX: onerror isn't set yet
|
||||
return JSC.jsBoolean(false);
|
||||
}
|
||||
}
|
||||
return JSC.jsBoolean(true);
|
||||
}
|
||||
|
||||
pub fn params(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
_ = this;
|
||||
_ = globalThis;
|
||||
_ = callframe;
|
||||
// intentionally left empty
|
||||
return .jsUndefined();
|
||||
}
|
||||
|
||||
fn deinit(this: *@This()) void {
|
||||
this.this_value.deinit();
|
||||
this.poll_ref.deinit();
|
||||
switch (this.stream.mode) {
|
||||
.BROTLI_ENCODE, .BROTLI_DECODE => this.stream.close(),
|
||||
else => {},
|
||||
}
|
||||
bun.destroy(this);
|
||||
}
|
||||
|
||||
const Context = struct {
|
||||
const c = bun.brotli.c;
|
||||
const Op = bun.brotli.c.BrotliEncoder.Operation;
|
||||
|
||||
mode: bun.zlib.NodeMode = .NONE,
|
||||
state: ?*anyopaque = null,
|
||||
|
||||
next_in: ?[*]const u8 = null,
|
||||
next_out: ?[*]u8 = null,
|
||||
avail_in: usize = 0,
|
||||
avail_out: usize = 0,
|
||||
|
||||
flush: Op = .process,
|
||||
|
||||
last_result: extern union { e: c_int, d: c.BrotliDecoderResult } = @bitCast(@as(u32, 0)),
|
||||
error_: c.BrotliDecoderErrorCode2 = .NO_ERROR,
|
||||
|
||||
pub fn init(this: *Context) Error {
|
||||
switch (this.mode) {
|
||||
.BROTLI_ENCODE => {
|
||||
const alloc = &bun.brotli.BrotliAllocator.alloc;
|
||||
const free = &bun.brotli.BrotliAllocator.free;
|
||||
const state = c.BrotliEncoderCreateInstance(alloc, free, null);
|
||||
if (state == null) {
|
||||
return Error.init("Could not initialize Brotli instance", -1, "ERR_ZLIB_INITIALIZATION_FAILED");
|
||||
}
|
||||
this.state = @ptrCast(state.?);
|
||||
return Error.ok;
|
||||
},
|
||||
.BROTLI_DECODE => {
|
||||
const alloc = &bun.brotli.BrotliAllocator.alloc;
|
||||
const free = &bun.brotli.BrotliAllocator.free;
|
||||
const state = c.BrotliDecoderCreateInstance(alloc, free, null);
|
||||
if (state == null) {
|
||||
return Error.init("Could not initialize Brotli instance", -1, "ERR_ZLIB_INITIALIZATION_FAILED");
|
||||
}
|
||||
this.state = @ptrCast(state.?);
|
||||
return Error.ok;
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setParams(this: *Context, key: c_uint, value: u32) Error {
|
||||
switch (this.mode) {
|
||||
.BROTLI_ENCODE => {
|
||||
if (c.BrotliEncoderSetParameter(@ptrCast(this.state), key, value) == 0) {
|
||||
return Error.init("Setting parameter failed", -1, "ERR_BROTLI_PARAM_SET_FAILED");
|
||||
}
|
||||
return Error.ok;
|
||||
},
|
||||
.BROTLI_DECODE => {
|
||||
if (c.BrotliDecoderSetParameter(@ptrCast(this.state), key, value) == 0) {
|
||||
return Error.init("Setting parameter failed", -1, "ERR_BROTLI_PARAM_SET_FAILED");
|
||||
}
|
||||
return Error.ok;
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(this: *Context) Error {
|
||||
return this.init();
|
||||
}
|
||||
|
||||
pub fn setBuffers(this: *Context, in: ?[]const u8, out: ?[]u8) void {
|
||||
this.next_in = if (in) |p| p.ptr else null;
|
||||
this.next_out = if (out) |p| p.ptr else null;
|
||||
this.avail_in = if (in) |p| p.len else 0;
|
||||
this.avail_out = if (out) |p| p.len else 0;
|
||||
}
|
||||
|
||||
pub fn setFlush(this: *Context, flush: c_int) void {
|
||||
this.flush = @enumFromInt(flush);
|
||||
}
|
||||
|
||||
pub fn doWork(this: *Context) void {
|
||||
switch (this.mode) {
|
||||
.BROTLI_ENCODE => {
|
||||
var next_in = this.next_in;
|
||||
this.last_result.e = c.BrotliEncoderCompressStream(@ptrCast(this.state), this.flush, &this.avail_in, &next_in, &this.avail_out, &this.next_out, null);
|
||||
this.next_in.? += @intFromPtr(next_in.?) - @intFromPtr(this.next_in.?);
|
||||
},
|
||||
.BROTLI_DECODE => {
|
||||
var next_in = this.next_in;
|
||||
this.last_result.d = c.BrotliDecoderDecompressStream(@ptrCast(this.state), &this.avail_in, &next_in, &this.avail_out, &this.next_out, null);
|
||||
this.next_in.? += @intFromPtr(next_in.?) - @intFromPtr(this.next_in.?);
|
||||
if (this.last_result.d == .err) {
|
||||
this.error_ = c.BrotliDecoderGetErrorCode(@ptrCast(this.state));
|
||||
}
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn updateWriteResult(this: *Context, avail_in: *u32, avail_out: *u32) void {
|
||||
avail_in.* = @intCast(this.avail_in);
|
||||
avail_out.* = @intCast(this.avail_out);
|
||||
}
|
||||
|
||||
pub fn getErrorInfo(this: *Context) Error {
|
||||
switch (this.mode) {
|
||||
.BROTLI_ENCODE => {
|
||||
if (this.last_result.e == 0) {
|
||||
return Error.init("Compression failed", -1, "ERR_BROTLI_COMPRESSION_FAILED");
|
||||
}
|
||||
return Error.ok;
|
||||
},
|
||||
.BROTLI_DECODE => {
|
||||
if (this.error_ != .NO_ERROR) {
|
||||
return Error.init("Decompression failed", @intFromEnum(this.error_), code_for_error(this.error_));
|
||||
} else if (this.flush == .finish and this.last_result.d == .needs_more_input) {
|
||||
return Error.init("unexpected end of file", @intFromEnum(bun.zlib.ReturnCode.BufError), "Z_BUF_ERROR");
|
||||
}
|
||||
return Error.ok;
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(this: *Context) void {
|
||||
switch (this.mode) {
|
||||
.BROTLI_ENCODE => c.BrotliEncoderDestroyInstance(@ptrCast(@alignCast(this.state))),
|
||||
.BROTLI_DECODE => c.BrotliDecoderDestroyInstance(@ptrCast(@alignCast(this.state))),
|
||||
else => unreachable,
|
||||
}
|
||||
this.mode = .NONE;
|
||||
}
|
||||
|
||||
fn code_for_error(err: c.BrotliDecoderErrorCode2) [:0]const u8 {
|
||||
const E = c.BrotliDecoderErrorCode2;
|
||||
const names = comptime std.meta.fieldNames(E);
|
||||
const values = comptime std.enums.values(E);
|
||||
inline for (names, values) |n, v| {
|
||||
if (err == v) {
|
||||
return "ERR_BROTLI_DECODER_" ++ n;
|
||||
}
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
};
|
||||
366
src/bun.js/node/zlib/NativeZlib.zig
Normal file
366
src/bun.js/node/zlib/NativeZlib.zig
Normal file
@@ -0,0 +1,366 @@
|
||||
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.JSNativeZlib;
|
||||
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,
|
||||
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.argumentsUndef(4).ptr;
|
||||
|
||||
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 < 1 or mode_int > 7) {
|
||||
return globalThis.throwRangeError(mode_int, .{ .field_name = "mode", .min = 1, .max = 7 });
|
||||
}
|
||||
|
||||
const ptr = bun.new(@This(), .{
|
||||
.ref_count = .init(),
|
||||
.globalThis = globalThis,
|
||||
});
|
||||
ptr.stream.mode = @enumFromInt(mode_int);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
//// adding this didnt help much but leaving it here to compare the number with later
|
||||
pub fn estimatedSize(_: *const @This()) usize {
|
||||
const internal_state_size = 3309; // @sizeOf(@cImport(@cInclude("deflate.h")).internal_state) @ cloudflare/zlib @ 92530568d2c128b4432467b76a3b54d93d6350bd
|
||||
return @sizeOf(@This()) + internal_state_size;
|
||||
}
|
||||
|
||||
pub fn init(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
const arguments = callframe.argumentsUndef(7).slice();
|
||||
const this_value = callframe.this();
|
||||
|
||||
if (arguments.len != 7) {
|
||||
return globalThis.ERR(.MISSING_ARGS, "init(windowBits, level, memLevel, strategy, writeResult, writeCallback, dictionary)", .{}).throw();
|
||||
}
|
||||
|
||||
const windowBits = try validators.validateInt32(globalThis, arguments[0], "windowBits", .{}, null, null);
|
||||
const level = try validators.validateInt32(globalThis, arguments[1], "level", .{}, null, null);
|
||||
const memLevel = try validators.validateInt32(globalThis, arguments[2], "memLevel", .{}, null, null);
|
||||
const strategy = try validators.validateInt32(globalThis, arguments[3], "strategy", .{}, null, null);
|
||||
// this does not get gc'd because it is stored in the JS object's `this._writeState`. and the JS object is tied to the native handle as `_handle[owner_symbol]`.
|
||||
const writeResult = arguments[4].asArrayBuffer(globalThis).?.asU32().ptr;
|
||||
const writeCallback = try validators.validateFunction(globalThis, "writeCallback", arguments[5]);
|
||||
const dictionary = if (arguments[6].isUndefined()) null else arguments[6].asArrayBuffer(globalThis).?.byteSlice();
|
||||
|
||||
this.write_result = writeResult;
|
||||
js.writeCallbackSetCached(this_value, globalThis, writeCallback);
|
||||
|
||||
// Keep the dictionary alive by keeping a reference to it in the JS object.
|
||||
if (dictionary != null) {
|
||||
js.dictionarySetCached(this_value, globalThis, arguments[6]);
|
||||
}
|
||||
|
||||
this.stream.init(level, windowBits, memLevel, strategy, dictionary);
|
||||
|
||||
return .jsUndefined();
|
||||
}
|
||||
|
||||
pub fn params(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
const arguments = callframe.argumentsUndef(2).slice();
|
||||
|
||||
if (arguments.len != 2) {
|
||||
return globalThis.ERR(.MISSING_ARGS, "params(level, strategy)", .{}).throw();
|
||||
}
|
||||
|
||||
const level = try validators.validateInt32(globalThis, arguments[0], "level", .{}, null, null);
|
||||
const strategy = try validators.validateInt32(globalThis, arguments[1], "strategy", .{}, null, null);
|
||||
|
||||
const err = this.stream.setParams(level, strategy);
|
||||
if (err.isError()) {
|
||||
try impl.emitError(this, globalThis, callframe.this(), err);
|
||||
}
|
||||
return .jsUndefined();
|
||||
}
|
||||
|
||||
fn deinit(this: *@This()) void {
|
||||
this.this_value.deinit();
|
||||
this.poll_ref.deinit();
|
||||
this.stream.close();
|
||||
bun.destroy(this);
|
||||
}
|
||||
|
||||
const Context = struct {
|
||||
const c = bun.zlib;
|
||||
const GZIP_HEADER_ID1: u8 = 0x1f;
|
||||
const GZIP_HEADER_ID2: u8 = 0x8b;
|
||||
|
||||
mode: c.NodeMode = .NONE,
|
||||
state: c.z_stream = std.mem.zeroes(c.z_stream),
|
||||
err: c.ReturnCode = .Ok,
|
||||
flush: c.FlushValue = .NoFlush,
|
||||
dictionary: []const u8 = "",
|
||||
gzip_id_bytes_read: u8 = 0,
|
||||
|
||||
pub fn init(this: *Context, level: c_int, windowBits: c_int, memLevel: c_int, strategy: c_int, dictionary: ?[]const u8) void {
|
||||
this.flush = .NoFlush;
|
||||
this.err = .Ok;
|
||||
|
||||
const windowBitsActual = switch (this.mode) {
|
||||
.NONE => unreachable,
|
||||
.DEFLATE, .INFLATE => windowBits,
|
||||
.GZIP, .GUNZIP => windowBits + 16,
|
||||
.UNZIP => windowBits + 32,
|
||||
.DEFLATERAW, .INFLATERAW => windowBits * -1,
|
||||
.BROTLI_DECODE, .BROTLI_ENCODE => unreachable,
|
||||
.ZSTD_COMPRESS, .ZSTD_DECOMPRESS => unreachable,
|
||||
};
|
||||
|
||||
this.dictionary = dictionary orelse "";
|
||||
|
||||
switch (this.mode) {
|
||||
.NONE => unreachable,
|
||||
.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)),
|
||||
.BROTLI_DECODE, .BROTLI_ENCODE => unreachable,
|
||||
.ZSTD_COMPRESS, .ZSTD_DECOMPRESS => unreachable,
|
||||
}
|
||||
if (this.err != .Ok) {
|
||||
this.mode = .NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
_ = this.setDictionary();
|
||||
}
|
||||
|
||||
pub fn setDictionary(this: *Context) Error {
|
||||
const dict = this.dictionary;
|
||||
if (dict.len == 0) return Error.ok;
|
||||
this.err = .Ok;
|
||||
switch (this.mode) {
|
||||
.DEFLATE, .DEFLATERAW => {
|
||||
this.err = c.deflateSetDictionary(&this.state, dict.ptr, @intCast(dict.len));
|
||||
},
|
||||
.INFLATERAW => {
|
||||
this.err = c.inflateSetDictionary(&this.state, dict.ptr, @intCast(dict.len));
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
if (this.err != .Ok) {
|
||||
return this.error_for_message("Failed to set dictionary");
|
||||
}
|
||||
return Error.ok;
|
||||
}
|
||||
|
||||
pub fn setParams(this: *Context, level: c_int, strategy: c_int) Error {
|
||||
this.err = .Ok;
|
||||
switch (this.mode) {
|
||||
.DEFLATE, .DEFLATERAW => {
|
||||
this.err = c.deflateParams(&this.state, level, strategy);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
if (this.err != .Ok and this.err != .BufError) {
|
||||
return this.error_for_message("Failed to set parameters");
|
||||
}
|
||||
return Error.ok;
|
||||
}
|
||||
|
||||
fn error_for_message(this: *Context, default: [*:0]const u8) Error {
|
||||
var message = default;
|
||||
if (this.state.err_msg) |msg| message = msg;
|
||||
return .{
|
||||
.msg = message,
|
||||
.err = @intFromEnum(this.err),
|
||||
.code = switch (this.err) {
|
||||
.Ok => "Z_OK",
|
||||
.StreamEnd => "Z_STREAM_END",
|
||||
.NeedDict => "Z_NEED_DICT",
|
||||
.ErrNo => "Z_ERRNO",
|
||||
.StreamError => "Z_STREAM_ERROR",
|
||||
.DataError => "Z_DATA_ERROR",
|
||||
.MemError => "Z_MEM_ERROR",
|
||||
.BufError => "Z_BUF_ERROR",
|
||||
.VersionError => "Z_VERSION_ERROR",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn reset(this: *Context) Error {
|
||||
this.err = .Ok;
|
||||
switch (this.mode) {
|
||||
.DEFLATE, .DEFLATERAW, .GZIP => {
|
||||
this.err = c.deflateReset(&this.state);
|
||||
},
|
||||
.INFLATE, .INFLATERAW, .GUNZIP => {
|
||||
this.err = c.inflateReset(&this.state);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
if (this.err != .Ok) {
|
||||
return this.error_for_message("Failed to reset stream");
|
||||
}
|
||||
return this.setDictionary();
|
||||
}
|
||||
|
||||
pub fn setBuffers(this: *Context, in: ?[]const u8, out: ?[]u8) void {
|
||||
this.state.avail_in = if (in) |p| @intCast(p.len) else 0;
|
||||
this.state.next_in = if (in) |p| p.ptr else null;
|
||||
this.state.avail_out = if (out) |p| @intCast(p.len) else 0;
|
||||
this.state.next_out = if (out) |p| p.ptr else null;
|
||||
}
|
||||
|
||||
pub fn setFlush(this: *Context, flush: c_int) void {
|
||||
this.flush = @enumFromInt(flush);
|
||||
}
|
||||
|
||||
pub fn doWork(this: *Context) void {
|
||||
var next_expected_header_byte: ?[*]const u8 = null;
|
||||
|
||||
// If the avail_out is left at 0, then it means that it ran out
|
||||
// of room. If there was avail_out left over, then it means
|
||||
// that all of the input was consumed.
|
||||
switch (this.mode) {
|
||||
.DEFLATE, .GZIP, .DEFLATERAW => {
|
||||
return this.doWorkDeflate();
|
||||
},
|
||||
.UNZIP => {
|
||||
if (this.state.avail_in > 0) {
|
||||
next_expected_header_byte = this.state.next_in.?;
|
||||
}
|
||||
if (this.gzip_id_bytes_read == 0) {
|
||||
if (next_expected_header_byte == null) {
|
||||
return this.doWorkInflate();
|
||||
}
|
||||
if (next_expected_header_byte.?[0] == GZIP_HEADER_ID1) {
|
||||
this.gzip_id_bytes_read = 1;
|
||||
next_expected_header_byte.? += 1;
|
||||
if (this.state.avail_in == 1) { // The only available byte was already read.
|
||||
return this.doWorkInflate();
|
||||
}
|
||||
} else {
|
||||
this.mode = .INFLATE;
|
||||
return this.doWorkInflate();
|
||||
}
|
||||
}
|
||||
if (this.gzip_id_bytes_read == 1) {
|
||||
if (next_expected_header_byte == null) {
|
||||
return this.doWorkInflate();
|
||||
}
|
||||
if (next_expected_header_byte.?[0] == GZIP_HEADER_ID2) {
|
||||
this.gzip_id_bytes_read = 2;
|
||||
this.mode = .GUNZIP;
|
||||
} else {
|
||||
this.mode = .INFLATE;
|
||||
}
|
||||
return this.doWorkInflate();
|
||||
}
|
||||
bun.assert(false); // invalid number of gzip magic number bytes read
|
||||
},
|
||||
.INFLATE, .GUNZIP, .INFLATERAW => {
|
||||
return this.doWorkInflate();
|
||||
},
|
||||
.NONE => {},
|
||||
.BROTLI_ENCODE, .BROTLI_DECODE => {},
|
||||
.ZSTD_COMPRESS, .ZSTD_DECOMPRESS => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn doWorkDeflate(this: *Context) void {
|
||||
this.err = c.deflate(&this.state, this.flush);
|
||||
}
|
||||
|
||||
fn doWorkInflate(this: *Context) void {
|
||||
this.err = c.inflate(&this.state, this.flush);
|
||||
|
||||
if (this.mode != .INFLATERAW and this.err == .NeedDict and this.dictionary.len > 0) {
|
||||
this.err = c.inflateSetDictionary(&this.state, this.dictionary.ptr, @intCast(this.dictionary.len));
|
||||
|
||||
if (this.err == .Ok) {
|
||||
this.err = c.inflate(&this.state, this.flush);
|
||||
} else if (this.err == .DataError) {
|
||||
this.err = .NeedDict;
|
||||
}
|
||||
}
|
||||
while (this.state.avail_in > 0 and this.mode == .GUNZIP and this.err == .StreamEnd and this.state.next_in.?[0] != 0) {
|
||||
// Bytes remain in input buffer. Perhaps this is another compressed member in the same archive, or just trailing garbage.
|
||||
// Trailing zero bytes are okay, though, since they are frequently used for padding.
|
||||
_ = this.reset();
|
||||
this.err = c.inflate(&this.state, this.flush);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn updateWriteResult(this: *Context, avail_in: *u32, avail_out: *u32) void {
|
||||
avail_in.* = this.state.avail_in;
|
||||
avail_out.* = this.state.avail_out;
|
||||
}
|
||||
|
||||
pub fn getErrorInfo(this: *Context) Error {
|
||||
switch (this.err) {
|
||||
.Ok, .BufError => {
|
||||
if (this.state.avail_out != 0 and this.flush == .Finish) {
|
||||
return this.error_for_message("unexpected end of file");
|
||||
}
|
||||
},
|
||||
.StreamEnd => {},
|
||||
.NeedDict => {
|
||||
if (this.dictionary.len == 0) {
|
||||
return this.error_for_message("Missing dictionary");
|
||||
} else {
|
||||
return this.error_for_message("Bad dictionary");
|
||||
}
|
||||
},
|
||||
else => {
|
||||
return this.error_for_message("Zlib error");
|
||||
},
|
||||
}
|
||||
return Error.ok;
|
||||
}
|
||||
|
||||
pub fn close(this: *Context) void {
|
||||
var status = c.ReturnCode.Ok;
|
||||
switch (this.mode) {
|
||||
.DEFLATE, .DEFLATERAW, .GZIP => {
|
||||
status = c.deflateEnd(&this.state);
|
||||
},
|
||||
.INFLATE, .INFLATERAW, .GUNZIP, .UNZIP => {
|
||||
status = c.inflateEnd(&this.state);
|
||||
},
|
||||
.NONE => {},
|
||||
.BROTLI_ENCODE, .BROTLI_DECODE => {},
|
||||
.ZSTD_COMPRESS, .ZSTD_DECOMPRESS => {},
|
||||
}
|
||||
bun.assert(status == .Ok or status == .DataError);
|
||||
this.mode = .NONE;
|
||||
}
|
||||
};
|
||||
@@ -26,7 +26,6 @@ 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,
|
||||
@@ -55,16 +54,14 @@ pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) b
|
||||
|
||||
const ptr = bun.new(@This(), .{
|
||||
.ref_count = .init(),
|
||||
.mode = @enumFromInt(mode_int),
|
||||
.globalThis = globalThis,
|
||||
});
|
||||
ptr.stream.mode = ptr.mode;
|
||||
ptr.stream.mode_ = ptr.mode;
|
||||
ptr.stream.mode = @enumFromInt(mode_int);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
pub fn estimatedSize(this: *const @This()) usize {
|
||||
return @sizeOf(@This()) + switch (this.stream.mode_) {
|
||||
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,
|
||||
@@ -131,7 +128,6 @@ 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 },
|
||||
@@ -140,7 +136,7 @@ const Context = struct {
|
||||
remaining: u64 = 0,
|
||||
|
||||
pub fn init(this: *Context, pledged_src_size: u64) Error {
|
||||
switch (this.mode_) {
|
||||
switch (this.mode) {
|
||||
.ZSTD_COMPRESS => {
|
||||
this.pledged_src_size = pledged_src_size;
|
||||
const state = c.ZSTD_createCCtx();
|
||||
@@ -161,7 +157,7 @@ const Context = struct {
|
||||
}
|
||||
|
||||
pub fn setParams(this: *Context, key: c_uint, value: u32) Error {
|
||||
switch (this.mode_) {
|
||||
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");
|
||||
@@ -194,7 +190,7 @@ const Context = struct {
|
||||
}
|
||||
|
||||
pub fn doWork(this: *Context) void {
|
||||
this.remaining = switch (this.mode_) {
|
||||
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"),
|
||||
@@ -250,12 +246,12 @@ const Context = struct {
|
||||
}
|
||||
|
||||
pub fn close(this: *Context) void {
|
||||
_ = switch (this.mode_) {
|
||||
_ = 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_) {
|
||||
_ = switch (this.mode) {
|
||||
.ZSTD_COMPRESS => c.ZSTD_freeCCtx(@ptrCast(this.state)),
|
||||
.ZSTD_DECOMPRESS => c.ZSTD_freeDCtx(@ptrCast(this.state)),
|
||||
else => unreachable,
|
||||
|
||||
@@ -32,7 +32,7 @@ const words: Record<string, { reason: string; limit?: number; regex?: boolean }>
|
||||
"== alloc.ptr": { reason: "The std.mem.Allocator context pointer can be undefined, which makes this comparison undefined behavior" },
|
||||
"!= alloc.ptr": { reason: "The std.mem.Allocator context pointer can be undefined, which makes this comparison undefined behavior" },
|
||||
|
||||
[String.raw`: [a-zA-Z0-9_\.\*\?\[\]\(\)]+ = undefined,`]: { reason: "Do not default a struct field to undefined", limit: 243, regex: true },
|
||||
[String.raw`: [a-zA-Z0-9_\.\*\?\[\]\(\)]+ = undefined,`]: { reason: "Do not default a struct field to undefined", limit: 242, regex: true },
|
||||
"usingnamespace": { reason: "Zig 0.15 will remove `usingnamespace`" },
|
||||
"catch unreachable": { reason: "For out-of-memory, prefer 'catch bun.outOfMemory()'", limit: 1857 },
|
||||
|
||||
|
||||
Reference in New Issue
Block a user