pub const c = @import("./deps/brotli_c.zig"); const BrotliDecoder = c.BrotliDecoder; const BrotliEncoder = c.BrotliEncoder; pub const BrotliAllocator = struct { pub fn alloc(_: ?*anyopaque, len: usize) callconv(.c) *anyopaque { if (bun.heap_breakdown.enabled) { const zone = bun.heap_breakdown.getZone("brotli"); return zone.malloc_zone_malloc(len) orelse bun.outOfMemory(); } return mimalloc.mi_malloc(len) orelse bun.outOfMemory(); } pub fn free(_: ?*anyopaque, data: ?*anyopaque) callconv(.c) void { if (bun.heap_breakdown.enabled) { const zone = bun.heap_breakdown.getZone("brotli"); zone.malloc_zone_free(data); return; } mimalloc.mi_free(data); } }; pub const DecoderOptions = struct { pub const Params = std.enums.EnumFieldStruct(c.BrotliDecoderParameter, bool, false); params: Params = Params{ .LARGE_WINDOW = true, .DISABLE_RING_BUFFER_REALLOCATION = false, }, }; pub const BrotliReaderArrayList = struct { pub const State = enum { Uninitialized, Inflating, End, Error, }; input: []const u8, list: std.ArrayListUnmanaged(u8), list_allocator: std.mem.Allocator, list_ptr: *std.ArrayListUnmanaged(u8), brotli: *BrotliDecoder, state: State = State.Uninitialized, total_out: usize = 0, total_in: usize = 0, flushOp: BrotliEncoder.Operation, finishFlushOp: BrotliEncoder.Operation, fullFlushOp: BrotliEncoder.Operation, pub const new = bun.TrivialNew(BrotliReaderArrayList); pub fn newWithOptions(input: []const u8, list: *std.ArrayListUnmanaged(u8), allocator: std.mem.Allocator, options: DecoderOptions) !*BrotliReaderArrayList { return BrotliReaderArrayList.new(try initWithOptions(input, list, allocator, options, .process, .finish, .flush)); } pub fn initWithOptions( input: []const u8, list: *std.ArrayListUnmanaged(u8), allocator: std.mem.Allocator, options: DecoderOptions, flushOp: BrotliEncoder.Operation, finishFlushOp: BrotliEncoder.Operation, fullFlushOp: BrotliEncoder.Operation, ) !BrotliReaderArrayList { if (!BrotliDecoder.initializeBrotli()) { return error.BrotliFailedToLoad; } var brotli = BrotliDecoder.createInstance(&BrotliAllocator.alloc, &BrotliAllocator.free, null) orelse return error.BrotliFailedToCreateInstance; if (options.params.LARGE_WINDOW) _ = brotli.setParameter(c.BrotliDecoderParameter.LARGE_WINDOW, 1); if (options.params.DISABLE_RING_BUFFER_REALLOCATION) _ = brotli.setParameter(c.BrotliDecoderParameter.DISABLE_RING_BUFFER_REALLOCATION, 1); bun.assert(list.items.ptr != input.ptr); return .{ .input = input, .list_ptr = list, .list = list.*, .list_allocator = allocator, .brotli = brotli, .flushOp = flushOp, .finishFlushOp = finishFlushOp, .fullFlushOp = fullFlushOp, }; } pub fn end(this: *BrotliReaderArrayList) void { this.state = .End; } pub fn readAll(this: *BrotliReaderArrayList, is_done: bool) !void { defer this.list_ptr.* = this.list; if (this.state == .End or this.state == .Error) { return; } bun.assert(this.list.items.ptr != this.input.ptr); while (this.state == State.Uninitialized or this.state == State.Inflating) { var unused_capacity = this.list.unusedCapacitySlice(); if (unused_capacity.len < 4096) { try this.list.ensureUnusedCapacity(this.list_allocator, 4096); unused_capacity = this.list.unusedCapacitySlice(); } bun.assert(unused_capacity.len > 0); var next_in = this.input[this.total_in..]; var in_remaining = next_in.len; var out_remaining = unused_capacity.len; // https://github.com/google/brotli/blob/fef82ea10435abb1500b615b1b2c6175d429ec6c/go/cbrotli/reader.go#L15-L27 const result = this.brotli.decompressStream( &in_remaining, @ptrCast(&next_in), &out_remaining, @ptrCast(&unused_capacity.ptr), null, ); const bytes_written = unused_capacity.len -| out_remaining; const bytes_read = next_in.len -| in_remaining; this.list.items.len += bytes_written; this.total_in += bytes_read; switch (result) { .success => { if (comptime bun.Environment.allow_assert) { bun.assert(this.brotli.isFinished()); } this.end(); return; }, .err => { this.state = .Error; if (comptime bun.Environment.allow_assert) { const code = this.brotli.getErrorCode(); bun.Output.debugWarn("Brotli error: {s} ({d})", .{ @tagName(code), @intFromEnum(code) }); } return error.BrotliDecompressionError; }, .needs_more_input => { if (in_remaining > 0) { @panic("Brotli wants more data"); } this.state = .Inflating; if (is_done) { // Stream is truncated - we're at EOF but decoder needs more data this.state = .Error; return error.BrotliDecompressionError; } // Not at EOF - we can retry with more data return error.ShortRead; }, .needs_more_output => { try this.list.ensureTotalCapacity(this.list_allocator, this.list.capacity + 4096); this.state = .Inflating; }, } } } pub fn deinit(this: *BrotliReaderArrayList) void { this.brotli.destroyInstance(); bun.destroy(this); } }; pub const BrotliCompressionStream = struct { pub const State = enum { Inflating, End, Error, }; brotli: *BrotliEncoder, state: State = State.Inflating, total_out: usize = 0, total_in: usize = 0, flushOp: BrotliEncoder.Operation, finishFlushOp: BrotliEncoder.Operation, fullFlushOp: BrotliEncoder.Operation, pub fn init( flushOp: BrotliEncoder.Operation, finishFlushOp: BrotliEncoder.Operation, fullFlushOp: BrotliEncoder.Operation, ) !BrotliCompressionStream { const instance = BrotliEncoder.createInstance(&BrotliAllocator.alloc, &BrotliAllocator.free, null) orelse return error.BrotliFailedToCreateInstance; return BrotliCompressionStream{ .brotli = instance, .flushOp = flushOp, .finishFlushOp = finishFlushOp, .fullFlushOp = fullFlushOp, }; } pub fn writeChunk(this: *BrotliCompressionStream, input: []const u8, last: bool) ![]const u8 { this.total_in += input.len; const result = this.brotli.compressStream(if (last) this.finishFlushOp else this.flushOp, input); if (!result.success) { this.state = .Error; return error.BrotliCompressionError; } return result.output; } pub fn write(this: *BrotliCompressionStream, input: []const u8, last: bool) ![]const u8 { if (this.state == .End or this.state == .Error) { return ""; } return this.writeChunk(input, last); } pub fn end(this: *BrotliCompressionStream) ![]const u8 { defer this.state = .End; return try this.write("", true); } pub fn deinit(this: *BrotliCompressionStream) void { this.brotli.destroyInstance(); } fn NewWriter(comptime InputWriter: type) type { return struct { compressor: *BrotliCompressionStream, input_writer: InputWriter, const Self = @This(); pub const WriteError = error{BrotliCompressionError} || InputWriter.Error; pub const Writer = std.Io.GenericWriter(@This(), WriteError, Self.write); pub fn init(compressor: *BrotliCompressionStream, input_writer: InputWriter) Self { return Self{ .compressor = compressor, .input_writer = input_writer, }; } pub fn write(self: Self, to_compress: []const u8) WriteError!usize { const decompressed = try self.compressor.write(to_compress, false); try self.input_writer.writeAll(decompressed); return to_compress.len; } pub fn end(self: Self) !usize { const decompressed = try self.compressor.end(); try self.input_writer.writeAll(decompressed); } pub fn writer(self: Self) Writer { return Writer{ .context = self }; } }; } pub fn writerContext(this: *BrotliCompressionStream, writable: anytype) NewWriter(@TypeOf(writable)) { return NewWriter(@TypeOf(writable)).init(this, writable); } pub fn writer(this: *BrotliCompressionStream, writable: anytype) NewWriter(@TypeOf(writable)).Writer { return this.writerContext(writable).writer(); } }; const std = @import("std"); const bun = @import("bun"); const mimalloc = bun.mimalloc;