diff options
Diffstat (limited to '')
-rw-r--r-- | Makefile | 7 | ||||
-rw-r--r-- | bench/snippets/gzip.js | 20 | ||||
-rw-r--r-- | bench/snippets/gzip.node.mjs | 20 | ||||
-rw-r--r-- | integration/bunjs-only-snippets/zlib.test.js | 18 | ||||
-rw-r--r-- | src/fallback.version | 2 | ||||
-rw-r--r-- | src/javascript/jsc/api/bun.zig | 158 | ||||
-rw-r--r-- | src/runtime.version | 2 | ||||
-rw-r--r-- | src/zlib.zig | 430 |
8 files changed, 646 insertions, 11 deletions
@@ -447,7 +447,7 @@ tgz-debug: vendor: require init-submodules vendor-without-check zlib: - cd $(BUN_DEPS_DIR)/zlib; CFLAGS="$(CFLAGS)" cmake $(CMAKE_FLAGS) .; CFLAGS="$(CFLAGS)" make; + cd $(BUN_DEPS_DIR)/zlib; CFLAGS="$(CFLAGS) $(EMIT_LLVM_FOR_RELEASE)" cmake $(CMAKE_FLAGS) .; CFLAGS="$(CFLAGS)" make; cp $(BUN_DEPS_DIR)/zlib/libz.a $(BUN_DEPS_OUT_DIR)/libz.a docker-login: @@ -1128,6 +1128,7 @@ wasm-return1: EMIT_LLVM_FOR_RELEASE= -emit-llvm +EMIT_LLVM_FOR_DEBUG= # We do this outside of build.zig for performance reasons # The C compilation stuff with build.zig is really slow and we don't need to run this as often as the rest @@ -1137,7 +1138,7 @@ $(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp $(OPTIMIZATION_LEVEL) \ -fno-exceptions \ -ferror-limit=1000 \ - $(EMIT_LLVM_FOR_RELEASE) \ + $(EMIT_LLVM_FOR_DEBUG) \ -g3 -c -o $@ $< $(OBJ_DIR)/%.o: $(SRC_DIR)/webcore/%.cpp @@ -1146,7 +1147,7 @@ $(OBJ_DIR)/%.o: $(SRC_DIR)/webcore/%.cpp $(OPTIMIZATION_LEVEL) \ -fno-exceptions \ -ferror-limit=1000 \ - $(EMIT_LLVM_FOR_RELEASE) \ + $(EMIT_LLVM_FOR_DEBUG) \ -g3 -c -o $@ $< sizegen: diff --git a/bench/snippets/gzip.js b/bench/snippets/gzip.js new file mode 100644 index 000000000..6d9971b39 --- /dev/null +++ b/bench/snippets/gzip.js @@ -0,0 +1,20 @@ +import { bench, group, run } from "mitata"; +import { gzipSync, gunzipSync } from "bun"; + +const data = new TextEncoder().encode("Hello World!".repeat(9999)); + +const compressed = gzipSync(data); + +bench(`roundtrip - "Hello World!".repeat(9999))`, () => { + gunzipSync(gzipSync(data)); +}); + +bench(`gzipSync("Hello World!".repeat(9999)))`, () => { + gzipSync(data); +}); + +bench(`gunzipSync("Hello World!".repeat(9999)))`, () => { + gunzipSync(compressed); +}); + +run({ collect: false, percentiles: true }); diff --git a/bench/snippets/gzip.node.mjs b/bench/snippets/gzip.node.mjs new file mode 100644 index 000000000..d93104850 --- /dev/null +++ b/bench/snippets/gzip.node.mjs @@ -0,0 +1,20 @@ +import { bench, group, run } from "mitata"; +import { gzipSync, gunzipSync } from "zlib"; + +const data = new TextEncoder().encode("Hello World!".repeat(9999)); + +const compressed = gzipSync(data); + +bench(`roundtrip - "Hello World!".repeat(9999))`, () => { + gunzipSync(gzipSync(data)); +}); + +bench(`gzipSync("Hello World!".repeat(9999)))`, () => { + gzipSync(data); +}); + +bench(`gunzipSync("Hello World!".repeat(9999)))`, () => { + gunzipSync(compressed); +}); + +run({ collect: false, percentiles: true }); diff --git a/integration/bunjs-only-snippets/zlib.test.js b/integration/bunjs-only-snippets/zlib.test.js new file mode 100644 index 000000000..64708529f --- /dev/null +++ b/integration/bunjs-only-snippets/zlib.test.js @@ -0,0 +1,18 @@ +import { describe, it, expect } from "bun:test"; +import { gzipSync, deflateSync, inflateSync, gunzipSync } from "bun"; + +describe("zlib", () => { + it("should be able to deflate and inflate", () => { + const data = new TextEncoder().encode("Hello World!".repeat(1)); + const compressed = deflateSync(data); + const decompressed = inflateSync(compressed); + expect(decompressed.join("")).toBe(data.join("")); + }); + + it("should be able to compress and decompress", () => { + const data = new TextEncoder().encode("Hello World!".repeat(1)); + const compressed = gzipSync(data); + const decompressed = gunzipSync(compressed); + expect(decompressed.join("")).toBe(data.join("")); + }); +}); diff --git a/src/fallback.version b/src/fallback.version index 36b21d826..b07ec851e 100644 --- a/src/fallback.version +++ b/src/fallback.version @@ -1 +1 @@ -871e1d1d6a2e7805
\ No newline at end of file +3c32b2da4ba87f18
\ No newline at end of file diff --git a/src/javascript/jsc/api/bun.zig b/src/javascript/jsc/api/bun.zig index afb3c9a60..9f0f82515 100644 --- a/src/javascript/jsc/api/bun.zig +++ b/src/javascript/jsc/api/bun.zig @@ -75,6 +75,7 @@ const URL = @import("../../../url.zig").URL; const Transpiler = @import("./transpiler.zig"); const VirtualMachine = @import("../javascript.zig").VirtualMachine; const IOTask = JSC.IOTask; +const zlib = @import("../../../zlib.zig"); const is_bindgen = JSC.is_bindgen; const max_addressible_memory = std.math.maxInt(u56); @@ -1124,6 +1125,18 @@ pub const Class = NewClass( .nanoseconds = .{ .rfn = nanoseconds, }, + .gzipSync = .{ + .rfn = JSC.wrapWithHasContainer(JSZlib, "gzipSync", false, false, true), + }, + .deflateSync = .{ + .rfn = JSC.wrapWithHasContainer(JSZlib, "deflateSync", false, false, true), + }, + .gunzipSync = .{ + .rfn = JSC.wrapWithHasContainer(JSZlib, "gunzipSync", false, false, true), + }, + .inflateSync = .{ + .rfn = JSC.wrapWithHasContainer(JSZlib, "inflateSync", false, false, true), + }, }, .{ .main = .{ @@ -2731,3 +2744,148 @@ comptime { _ = Bun__reportError; } } + +pub const JSZlib = struct { + export fn reader_deallocator(_: ?*anyopaque, ctx: ?*anyopaque) void { + var reader: *zlib.ZlibReaderArrayList = bun.cast(*zlib.ZlibReaderArrayList, ctx.?); + reader.list.deinit(reader.allocator); + reader.deinit(); + } + + export fn compressor_deallocator(_: ?*anyopaque, ctx: ?*anyopaque) void { + var compressor: *zlib.ZlibCompressorArrayList = bun.cast(*zlib.ZlibCompressorArrayList, ctx.?); + compressor.list.deinit(compressor.allocator); + compressor.deinit(); + } + + pub fn gzipSync( + globalThis: *JSGlobalObject, + buffer: JSC.Node.StringOrBuffer, + options_val_: ?JSValue, + ) JSValue { + return gzipOrDeflateSync(globalThis, buffer, options_val_, true); + } + + pub fn deflateSync( + globalThis: *JSGlobalObject, + buffer: JSC.Node.StringOrBuffer, + options_val_: ?JSValue, + ) JSValue { + return gzipOrDeflateSync(globalThis, buffer, options_val_, false); + } + + pub fn gzipOrDeflateSync( + globalThis: *JSGlobalObject, + buffer: JSC.Node.StringOrBuffer, + options_val_: ?JSValue, + is_gzip: bool, + ) JSValue { + var opts = zlib.Options{ .gzip = is_gzip }; + if (options_val_) |options_val| { + if (options_val.isObject()) { + if (options_val.get(globalThis, "windowBits")) |window| { + opts.windowBits = window.toInt32(); + } + + if (options_val.get(globalThis, "level")) |level| { + opts.level = level.toInt32(); + } + + if (options_val.get(globalThis, "memLevel")) |memLevel| { + opts.memLevel = memLevel.toInt32(); + } + + if (options_val.get(globalThis, "strategy")) |strategy| { + opts.strategy = strategy.toInt32(); + } + } + } + + var compressed = buffer.slice(); + const allocator = JSC.VirtualMachine.vm.allocator; + var list = std.ArrayListUnmanaged(u8).initCapacity(allocator, if (compressed.len > 512) compressed.len else 32) catch unreachable; + var reader = zlib.ZlibCompressorArrayList.init(compressed, &list, allocator, opts) catch |err| { + if (err == error.InvalidArgument) { + return JSC.toInvalidArguments("Invalid buffer", .{}, globalThis.ref()); + } + + return JSC.toInvalidArguments("Unexpected", .{}, globalThis.ref()); + }; + + reader.readAll() catch { + defer reader.deinit(); + if (reader.errorMessage()) |msg| { + return ZigString.init(msg).toErrorInstance(globalThis); + } + return ZigString.init("Zlib returned an error").toErrorInstance(globalThis); + }; + reader.list = .{ .items = reader.list.toOwnedSlice(allocator) }; + reader.list.capacity = reader.list.items.len; + reader.list_ptr = &reader.list; + + var array_buffer = JSC.ArrayBuffer.fromBytes(reader.list.items, .Uint8Array); + return array_buffer.toJSWithContext(globalThis.ref(), reader, reader_deallocator, null); + } + + pub fn inflateSync( + globalThis: *JSGlobalObject, + buffer: JSC.Node.StringOrBuffer, + ) JSValue { + var compressed = buffer.slice(); + const allocator = JSC.VirtualMachine.vm.allocator; + var list = std.ArrayListUnmanaged(u8).initCapacity(allocator, if (compressed.len > 512) compressed.len else 32) catch unreachable; + var reader = zlib.ZlibReaderArrayList.initWithOptions(compressed, &list, allocator, .{ + .windowBits = -15, + }) catch |err| { + if (err == error.InvalidArgument) { + return JSC.toInvalidArguments("Invalid buffer", .{}, globalThis.ref()); + } + + return JSC.toInvalidArguments("Unexpected", .{}, globalThis.ref()); + }; + + reader.readAll() catch { + defer reader.deinit(); + if (reader.errorMessage()) |msg| { + return ZigString.init(msg).toErrorInstance(globalThis); + } + return ZigString.init("Zlib returned an error").toErrorInstance(globalThis); + }; + reader.list = .{ .items = reader.list.toOwnedSlice(allocator) }; + reader.list.capacity = reader.list.items.len; + reader.list_ptr = &reader.list; + + var array_buffer = JSC.ArrayBuffer.fromBytes(reader.list.items, .Uint8Array); + return array_buffer.toJSWithContext(globalThis.ref(), reader, reader_deallocator, null); + } + + pub fn gunzipSync( + globalThis: *JSGlobalObject, + buffer: JSC.Node.StringOrBuffer, + ) JSValue { + var compressed = buffer.slice(); + const allocator = JSC.VirtualMachine.vm.allocator; + var list = std.ArrayListUnmanaged(u8).initCapacity(allocator, if (compressed.len > 512) compressed.len else 32) catch unreachable; + var reader = zlib.ZlibReaderArrayList.init(compressed, &list, allocator) catch |err| { + if (err == error.InvalidArgument) { + return JSC.toInvalidArguments("Invalid buffer", .{}, globalThis.ref()); + } + + return JSC.toInvalidArguments("Unexpected", .{}, globalThis.ref()); + }; + + reader.readAll() catch { + defer reader.deinit(); + if (reader.errorMessage()) |msg| { + return ZigString.init(msg).toErrorInstance(globalThis); + } + return ZigString.init("Zlib returned an error").toErrorInstance(globalThis); + }; + reader.list = .{ .items = reader.list.toOwnedSlice(allocator) }; + reader.list.capacity = reader.list.items.len; + reader.list_ptr = &reader.list; + + var array_buffer = JSC.ArrayBuffer.fromBytes(reader.list.items, .Uint8Array); + return array_buffer.toJSWithContext(globalThis.ref(), reader, reader_deallocator, null); + } +}; diff --git a/src/runtime.version b/src/runtime.version index d2b265d1e..f3ecdb065 100644 --- a/src/runtime.version +++ b/src/runtime.version @@ -1 +1 @@ -964af575c60cb851
\ No newline at end of file +271391bfa4089a43
\ No newline at end of file diff --git a/src/zlib.zig b/src/zlib.zig index 0ff2cfb7a..7603c9654 100644 --- a/src/zlib.zig +++ b/src/zlib.zig @@ -32,10 +32,10 @@ test "ZlibArrayList Read" { pub extern fn zlibVersion() [*c]const u8; -pub extern fn compress(dest: [*c]Bytef, destLen: [*c]uLongf, source: [*c]const Bytef, sourceLen: uLong) c_int; -pub extern fn compress2(dest: [*c]Bytef, destLen: [*c]uLongf, source: [*c]const Bytef, sourceLen: uLong, level: c_int) c_int; +pub extern fn compress(dest: [*]Bytef, destLen: *uLongf, source: [*]const Bytef, sourceLen: uLong) c_int; +pub extern fn compress2(dest: [*]Bytef, destLen: *uLongf, source: [*]const Bytef, sourceLen: uLong, level: c_int) c_int; pub extern fn compressBound(sourceLen: uLong) uLong; -pub extern fn uncompress(dest: [*c]Bytef, destLen: [*c]uLongf, source: [*c]const Bytef, sourceLen: uLong) c_int; +pub extern fn uncompress(dest: [*]Bytef, destLen: *uLongf, source: [*]const Bytef, sourceLen: uLong) c_int; pub const struct_gzFile_s = extern struct { have: c_uint, next: [*c]u8, @@ -125,7 +125,7 @@ pub const zStream_struct = extern struct { }; pub const z_stream = zStream_struct; -pub const z_streamp = [*c]z_stream; +pub const z_streamp = *z_stream; // #define Z_BINARY 0 // #define Z_TEXT 1 @@ -446,7 +446,19 @@ pub const ZlibReaderArrayList = struct { } } - pub fn init(input: []const u8, list: *std.ArrayListUnmanaged(u8), allocator: std.mem.Allocator) ZlibError!*ZlibReader { + pub fn init( + input: []const u8, + list: *std.ArrayListUnmanaged(u8), + allocator: std.mem.Allocator, + ) !*ZlibReader { + const options: Options = .{ + .windowBits = 15 + 32, + }; + + return initWithOptions(input, list, allocator, options); + } + + pub fn initWithOptions(input: []const u8, list: *std.ArrayListUnmanaged(u8), allocator: std.mem.Allocator, options: Options) ZlibError!*ZlibReader { var zlib_reader = try allocator.create(ZlibReader); zlib_reader.* = ZlibReader{ .input = input, @@ -478,7 +490,7 @@ pub const ZlibReaderArrayList = struct { .reserved = 0, }; - switch (inflateInit2_(&zlib_reader.zlib, 15 + 32, zlibVersion(), @sizeOf(zStream_struct))) { + switch (inflateInit2_(&zlib_reader.zlib, options.windowBits, zlibVersion(), @sizeOf(zStream_struct))) { ReturnCode.Ok => return zlib_reader, ReturnCode.MemError => { zlib_reader.deinit(); @@ -579,3 +591,409 @@ pub const ZlibReaderArrayList = struct { } } }; + +pub const Options = struct { + gzip: bool = false, + level: c_int = 6, + method: c_int = 8, + windowBits: c_int = 15, + memLevel: c_int = 8, + strategy: c_int = 0, +}; + +/// +/// Initializes the internal stream state for compression. The fields +/// zalloc, zfree and opaque must be initialized before by the caller. If +/// zalloc and zfree are set to Z_NULL, deflateInit updates them to use default +/// allocation functions. +/// +/// The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: +/// 1 gives best speed, 9 gives best compression, 0 gives no compression at all +/// (the input data is simply copied a block at a time). Z_DEFAULT_COMPRESSION +/// requests a default compromise between speed and compression (currently +/// equivalent to level 6). +/// +/// deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough +/// memory, Z_STREAM_ERROR if level is not a valid compression level, or +/// Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible +/// with the version assumed by the caller (ZLIB_VERSION). msg is set to null +/// if there is no error message. deflateInit does not perform any compression: +/// this will be done by deflate(). +extern fn deflateInit_(strm: z_stream, level: c_int, stream_size: c_int) c_int; + +/// +/// deflate compresses as much data as possible, and stops when the input +/// buffer becomes empty or the output buffer becomes full. It may introduce +/// some output latency (reading input without producing any output) except when +/// forced to flush. +/// +/// The detailed semantics are as follows. deflate performs one or both of the +/// following actions: +/// +/// - Compress more input starting at next_in and update next_in and avail_in +/// accordingly. If not all input can be processed (because there is not +/// enough room in the output buffer), next_in and avail_in are updated and +/// processing will resume at this point for the next call of deflate(). +/// +/// - Provide more output starting at next_out and update next_out and avail_out +/// accordingly. This action is forced if the parameter flush is non zero. +/// Forcing flush frequently degrades the compression ratio, so this parameter +/// should be set only when necessary (in interactive applications). Some +/// output may be provided even if flush is not set. +/// +/// Before the call of deflate(), the application should ensure that at least +/// one of the actions is possible, by providing more input and/or consuming more +/// output, and updating avail_in or avail_out accordingly; avail_out should +/// never be zero before the call. The application can consume the compressed +/// output when it wants, for example when the output buffer is full (avail_out +/// == 0), or after each call of deflate(). If deflate returns Z_OK and with +/// zero avail_out, it must be called again after making room in the output +/// buffer because there might be more output pending. +/// +/// Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to +/// decide how much data to accumulate before producing output, in order to +/// maximize compression. +/// +/// If the parameter flush is set to Z_SYNC_FLUSH, all pending output is +/// flushed to the output buffer and the output is aligned on a byte boundary, so +/// that the decompressor can get all input data available so far. (In +/// particular avail_in is zero after the call if enough output space has been +/// provided before the call.) Flushing may degrade compression for some +/// compression algorithms and so it should be used only when necessary. This +/// completes the current deflate block and follows it with an empty stored block +/// that is three bits plus filler bits to the next byte, followed by four bytes +/// (00 00 ff ff). +/// +/// If flush is set to Z_PARTIAL_FLUSH, all pending output is flushed to the +/// output buffer, but the output is not aligned to a byte boundary. All of the +/// input data so far will be available to the decompressor, as for Z_SYNC_FLUSH. +/// This completes the current deflate block and follows it with an empty fixed +/// codes block that is 10 bits long. This assures that enough bytes are output +/// in order for the decompressor to finish the block before the empty fixed code +/// block. +/// +/// If flush is set to Z_BLOCK, a deflate block is completed and emitted, as +/// for Z_SYNC_FLUSH, but the output is not aligned on a byte boundary, and up to +/// seven bits of the current block are held to be written as the next byte after +/// the next deflate block is completed. In this case, the decompressor may not +/// be provided enough bits at this point in order to complete decompression of +/// the data provided so far to the compressor. It may need to wait for the next +/// block to be emitted. This is for advanced applications that need to control +/// the emission of deflate blocks. +/// +/// If flush is set to Z_FULL_FLUSH, all output is flushed as with +/// Z_SYNC_FLUSH, and the compression state is reset so that decompression can +/// restart from this point if previous compressed data has been damaged or if +/// random access is desired. Using Z_FULL_FLUSH too often can seriously degrade +/// compression. +/// +/// If deflate returns with avail_out == 0, this function must be called again +/// with the same value of the flush parameter and more output space (updated +/// avail_out), until the flush is complete (deflate returns with non-zero +/// avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that +/// avail_out is greater than six to avoid repeated flush markers due to +/// avail_out == 0 on return. +/// +/// If the parameter flush is set to Z_FINISH, pending input is processed, +/// pending output is flushed and deflate returns with Z_STREAM_END if there was +/// enough output space; if deflate returns with Z_OK, this function must be +/// called again with Z_FINISH and more output space (updated avail_out) but no +/// more input data, until it returns with Z_STREAM_END or an error. After +/// deflate has returned Z_STREAM_END, the only possible operations on the stream +/// are deflateReset or deflateEnd. +/// +/// Z_FINISH can be used immediately after deflateInit if all the compression +/// is to be done in a single step. In this case, avail_out must be at least the +/// value returned by deflateBound (see below). Then deflate is guaranteed to +/// return Z_STREAM_END. If not enough output space is provided, deflate will +/// not return Z_STREAM_END, and it must be called again as described above. +/// +/// deflate() sets strm->adler to the adler32 checksum of all input read +/// so far (that is, total_in bytes). +/// +/// deflate() may update strm->data_type if it can make a good guess about +/// the input data type (Z_BINARY or Z_TEXT). In doubt, the data is considered +/// binary. This field is only for information purposes and does not affect the +/// compression algorithm in any manner. +/// +/// deflate() returns Z_OK if some progress has been made (more input +/// processed or more output produced), Z_STREAM_END if all input has been +/// consumed and all output has been produced (only when flush is set to +/// Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example +/// if next_in or next_out was Z_NULL), Z_BUF_ERROR if no progress is possible +/// (for example avail_in or avail_out was zero). Note that Z_BUF_ERROR is not +/// fatal, and deflate() can be called again with more input and more output +/// space to continue compressing. +/// +extern fn deflate(strm: z_streamp, flush: FlushValue) ReturnCode; + +/// +/// All dynamically allocated data structures for this stream are freed. +/// This function discards any unprocessed input and does not flush any pending +/// output. +/// +/// deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the +/// stream state was inconsistent, Z_DATA_ERROR if the stream was freed +/// prematurely (some input or output was discarded). In the error case, msg +/// may be set but then points to a static string (which must not be +/// deallocated). +extern fn deflateEnd(stream: z_streamp) ReturnCode; + +// deflateBound() returns an upper bound on the compressed size after +// deflation of sourceLen bytes. It must be called after deflateInit() or +// deflateInit2(), and after deflateSetHeader(), if used. This would be used +// to allocate an output buffer for deflation in a single pass, and so would be +// called before deflate(). If that first deflate() call is provided the +// sourceLen input bytes, an output buffer allocated to the size returned by +// deflateBound(), and the flush value Z_FINISH, then deflate() is guaranteed +// to return Z_STREAM_END. Note that it is possible for the compressed size to +// be larger than the value returned by deflateBound() if flush options other +// than Z_FINISH or Z_NO_FLUSH are used. +extern fn deflateBound(strm: z_streamp, sourceLen: u64) u64; + +/// +/// This is another version of deflateInit with more compression options. The +/// fields next_in, zalloc, zfree and opaque must be initialized before by the +/// caller. +/// +/// The method parameter is the compression method. It must be Z_DEFLATED in +/// this version of the library. +/// +/// The windowBits parameter is the base two logarithm of the window size +/// (the size of the history buffer). It should be in the range 8..15 for this +/// version of the library. Larger values of this parameter result in better +/// compression at the expense of memory usage. The default value is 15 if +/// deflateInit is used instead. +/// +/// windowBits can also be -8..-15 for raw deflate. In this case, -windowBits +/// determines the window size. deflate() will then generate raw deflate data +/// with no zlib header or trailer, and will not compute an adler32 check value. +/// +/// windowBits can also be greater than 15 for optional gzip encoding. Add +/// 16 to windowBits to write a simple gzip header and trailer around the +/// compressed data instead of a zlib wrapper. The gzip header will have no +/// file name, no extra data, no comment, no modification time (set to zero), no +/// header crc, and the operating system will be set to 255 (unknown). If a +/// gzip stream is being written, strm->adler is a crc32 instead of an adler32. +/// +/// The memLevel parameter specifies how much memory should be allocated +/// for the internal compression state. memLevel=1 uses minimum memory but is +/// slow and reduces compression ratio; memLevel=9 uses maximum memory for +/// optimal speed. The default value is 8. See zconf.h for total memory usage +/// as a function of windowBits and memLevel. +/// +/// The strategy parameter is used to tune the compression algorithm. Use the +/// value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a +/// filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no +/// string match), or Z_RLE to limit match distances to one (run-length +/// encoding). Filtered data consists mostly of small values with a somewhat +/// random distribution. In this case, the compression algorithm is tuned to +/// compress them better. The effect of Z_FILTERED is to force more Huffman +/// coding and less string matching; it is somewhat intermediate between +/// Z_DEFAULT_STRATEGY and Z_HUFFMAN_ONLY. Z_RLE is designed to be almost as +/// fast as Z_HUFFMAN_ONLY, but give better compression for PNG image data. The +/// strategy parameter only affects the compression ratio but not the +/// correctness of the compressed output even if it is not set appropriately. +/// Z_FIXED prevents the use of dynamic Huffman codes, allowing for a simpler +/// decoder for special applications. +/// +/// deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough +/// memory, Z_STREAM_ERROR if any parameter is invalid (such as an invalid +/// method), or Z_VERSION_ERROR if the zlib library version (zlib_version) is +/// incompatible with the version assumed by the caller (ZLIB_VERSION). msg is +/// set to null if there is no error message. deflateInit2 does not perform any +/// compression: this will be done by deflate(). +extern fn deflateInit2_(strm: z_streamp, level: c_int, method: c_int, windowBits: c_int, memLevel: c_int, strategy: c_int, version: [*c]const u8, stream_size: c_int) ReturnCode; + +/// Not for streaming! +pub const ZlibCompressorArrayList = struct { + const ZlibCompressor = ZlibCompressorArrayList; + + pub const State = enum { + Uninitialized, + Inflating, + End, + Error, + }; + + input: []const u8, + list: std.ArrayListUnmanaged(u8), + list_ptr: *std.ArrayListUnmanaged(u8), + zlib: zStream_struct, + allocator: std.mem.Allocator, + arena: std.heap.ArenaAllocator, + state: State = State.Uninitialized, + + pub fn alloc(ctx: *anyopaque, items: uInt, len: uInt) callconv(.C) *anyopaque { + var this = @ptrCast(*ZlibCompressor, @alignCast(@alignOf(*ZlibCompressor), ctx)); + const buf = this.allocator.alloc(u8, items * len) catch unreachable; + return buf.ptr; + } + + // we free manually all at once + pub fn free(_: *anyopaque, _: *anyopaque) callconv(.C) void {} + + pub fn deinit(this: *ZlibCompressor) void { + var allocator = this.allocator; + this.end(); + this.arena.deinit(); + allocator.destroy(this); + } + + pub fn end(this: *ZlibCompressor) void { + if (this.state == State.Inflating) { + _ = deflateEnd(&this.zlib); + this.state = State.End; + } + } + + pub fn init(input: []const u8, list: *std.ArrayListUnmanaged(u8), allocator: std.mem.Allocator, options: Options) ZlibError!*ZlibCompressor { + var zlib_reader = try allocator.create(ZlibCompressor); + zlib_reader.* = ZlibCompressor{ + .input = input, + .list = list.*, + .list_ptr = list, + .allocator = allocator, + .zlib = undefined, + .arena = std.heap.ArenaAllocator.init(allocator), + }; + + zlib_reader.zlib = zStream_struct{ + .next_in = input.ptr, + .avail_in = @intCast(uInt, input.len), + .total_in = @intCast(uInt, input.len), + + .next_out = zlib_reader.list.items.ptr, + .avail_out = @intCast(u32, zlib_reader.list.items.len), + .total_out = zlib_reader.list.items.len, + + .err_msg = null, + .alloc_func = ZlibCompressor.alloc, + .free_func = ZlibCompressor.free, + + .internal_state = null, + .user_data = zlib_reader, + + .data_type = DataType.Unknown, + .adler = 0, + .reserved = 0, + }; + + switch (deflateInit2_( + &zlib_reader.zlib, + options.level, + options.method, + if (!options.gzip) -options.windowBits else options.windowBits + 16, + options.memLevel, + options.strategy, + zlibVersion(), + @sizeOf(zStream_struct), + )) { + ReturnCode.Ok => { + try zlib_reader.list.ensureTotalCapacityPrecise(allocator, deflateBound(&zlib_reader.zlib, input.len)); + zlib_reader.list_ptr.* = zlib_reader.list; + zlib_reader.zlib.avail_out = @truncate(uInt, zlib_reader.list.capacity); + zlib_reader.zlib.next_out = zlib_reader.list.items.ptr; + + return zlib_reader; + }, + ReturnCode.MemError => { + zlib_reader.deinit(); + return error.OutOfMemory; + }, + ReturnCode.StreamError => { + zlib_reader.deinit(); + return error.InvalidArgument; + }, + ReturnCode.VersionError => { + zlib_reader.deinit(); + return error.InvalidArgument; + }, + else => unreachable, + } + } + + pub fn errorMessage(this: *ZlibCompressor) ?[]const u8 { + if (this.zlib.err_msg) |msg_ptr| { + return std.mem.sliceTo(msg_ptr, 0); + } + + return null; + } + + pub fn readAll(this: *ZlibCompressor) ZlibError!void { + defer { + this.list.shrinkRetainingCapacity(this.zlib.total_out); + this.list_ptr.* = this.list; + } + + while (this.state == State.Uninitialized or this.state == State.Inflating) { + + // Before the call of inflate(), the application should ensure + // that at least one of the actions is possible, by providing + // more input and/or consuming more output, and updating the + // next_* and avail_* values accordingly. If the caller of + // inflate() does not provide both available input and available + // output space, it is possible that there will be no progress + // made. The application can consume the uncompressed output + // when it wants, for example when the output buffer is full + // (avail_out == 0), or after each call of inflate(). If inflate + // returns Z_OK and with zero avail_out, it must be called again + // after making room in the output buffer because there might be + // more output pending. + + // - Decompress more input starting at next_in and update + // next_in and avail_in accordingly. If not all input can be + // processed (because there is not enough room in the output + // buffer), then next_in and avail_in are updated accordingly, + // and processing will resume at this point for the next call + // of inflate(). + + // - Generate more output starting at next_out and update + // next_out and avail_out accordingly. inflate() provides as + // much output as possible, until there is no more input data + // or no more space in the output buffer (see below about the + // flush parameter). + + if (this.zlib.avail_out == 0) { + const initial = this.list.items.len; + try this.list.ensureUnusedCapacity(this.allocator, 4096); + this.list.expandToCapacity(); + this.zlib.next_out = &this.list.items[initial]; + this.zlib.avail_out = @intCast(u32, this.list.items.len - initial); + } + + if (this.zlib.avail_out == 0) { + return error.ShortRead; + } + + const rc = deflate(&this.zlib, FlushValue.Finish); + this.state = State.Inflating; + + switch (rc) { + ReturnCode.StreamEnd => { + this.state = State.End; + this.list.items.len = this.zlib.total_out; + + this.end(); + return; + }, + ReturnCode.MemError => { + this.state = State.Error; + return error.OutOfMemory; + }, + ReturnCode.StreamError, + ReturnCode.DataError, + ReturnCode.BufError, + ReturnCode.NeedDict, + ReturnCode.VersionError, + ReturnCode.ErrNo, + => { + this.state = State.Error; + return error.ZlibError; + }, + ReturnCode.Ok => {}, + } + } + } +}; |