Skip to content

Commit

Permalink
node:zlib: Brotli (#10722)
Browse files Browse the repository at this point in the history
Co-authored-by: Jarred Sumner <[email protected]>
  • Loading branch information
nektro and Jarred-Sumner committed May 7, 2024
1 parent 0a54bc0 commit 1da810a
Show file tree
Hide file tree
Showing 18 changed files with 1,450 additions and 73 deletions.
2 changes: 1 addition & 1 deletion docs/runtime/nodejs-apis.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ Some methods are not optimized yet.

### [`node:zlib`](https://nodejs.org/api/zlib.html)

🟡 Missing `BrotliCompress` `BrotliDecompress` `brotliCompressSync` `brotliDecompress` `brotliDecompressSync` `createBrotliCompress` `createBrotliDecompress`. Unoptimized.
🟡 Unoptimized.

## Globals

Expand Down
128 changes: 111 additions & 17 deletions src/brotli.zig
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
const bun = @import("root").bun;
const std = @import("std");
const c = @import("./deps/brotli_decoder.zig");
const c = struct {
pub usingnamespace @import("./deps/brotli_decoder.zig");
pub usingnamespace @import("./deps/brotli_encoder.zig");
};
const BrotliDecoder = c.BrotliDecoder;
const BrotliEncoder = c.BrotliEncoder;

const mimalloc = bun.Mimalloc;

Expand All @@ -15,7 +19,7 @@ const BrotliAllocator = struct {
return mimalloc.mi_malloc(len) orelse unreachable;
}

pub fn free(_: ?*anyopaque, data: *anyopaque) callconv(.C) void {
pub fn free(_: ?*anyopaque, data: ?*anyopaque) callconv(.C) void {
if (comptime bun.is_heap_breakdown_enabled) {
const zone = bun.HeapBreakdown.malloc_zone_t.get(BrotliAllocator);
zone.malloc_zone_free(data);
Expand All @@ -26,7 +30,7 @@ const BrotliAllocator = struct {
}
};

pub const Options = struct {
pub const DecoderOptions = struct {
pub const Params = std.enums.EnumFieldStruct(c.BrotliDecoderParameter, bool, false);

params: Params = Params{
Expand Down Expand Up @@ -54,7 +58,11 @@ pub const BrotliReaderArrayList = struct {

pub usingnamespace bun.New(BrotliReaderArrayList);

pub fn initWithOptions(input: []const u8, list: *std.ArrayListUnmanaged(u8), allocator: std.mem.Allocator, options: Options) !*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));
}

pub fn initWithOptions(input: []const u8, list: *std.ArrayListUnmanaged(u8), allocator: std.mem.Allocator, options: DecoderOptions) !BrotliReaderArrayList {
if (!BrotliDecoder.initializeBrotli()) {
return error.BrotliFailedToLoad;
}
Expand All @@ -67,25 +75,21 @@ pub const BrotliReaderArrayList = struct {

bun.assert(list.items.ptr != input.ptr);

return BrotliReaderArrayList.new(
.{
.input = input,
.list_ptr = list,
.list = list.*,
.list_allocator = allocator,
.brotli = brotli,
},
);
return .{
.input = input,
.list_ptr = list,
.list = list.*,
.list_allocator = allocator,
.brotli = brotli,
};
}

pub fn end(this: *BrotliReaderArrayList) void {
this.state = .End;
}

pub fn readAll(this: *BrotliReaderArrayList, is_done: bool) !void {
defer {
this.list_ptr.* = this.list;
}
defer this.list_ptr.* = this.list;

if (this.state == .End or this.state == .Error) {
return;
Expand Down Expand Up @@ -128,7 +132,6 @@ pub const BrotliReaderArrayList = struct {
if (comptime bun.Environment.allow_assert) {
bun.assert(this.brotli.isFinished());
}

this.end();
return;
},
Expand Down Expand Up @@ -166,3 +169,94 @@ pub const BrotliReaderArrayList = struct {
this.destroy();
}
};

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,

pub fn init() !BrotliCompressionStream {
const instance = BrotliEncoder.createInstance(&BrotliAllocator.alloc, &BrotliAllocator.free, null) orelse return error.BrotliFailedToCreateInstance;

return BrotliCompressionStream{
.brotli = instance,
};
}

pub fn writeChunk(this: *BrotliCompressionStream, input: []const u8, last: bool) ![]const u8 {
const result = this.brotli.compressStream(if (last) BrotliEncoder.Operation.finish else .process, 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.Writer(@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();
}
};
63 changes: 63 additions & 0 deletions src/bun.js/api/brotli.classes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { define } from "../../codegen/class-definitions";

export default [
define({
name: "BrotliEncoder",
construct: true,
noConstructor: true,
finalize: true,
configurable: false,
hasPendingActivity: true,
klass: {},
JSType: "0b11101110",
values: ["callback"],
proto: {
encode: {
fn: "encode",
length: 2,
},
encodeSync: {
fn: "encodeSync",
length: 2,
},
end: {
fn: "end",
length: 2,
},
endSync: {
fn: "endSync",
length: 2,
},
},
}),
define({
name: "BrotliDecoder",
construct: true,
noConstructor: true,
finalize: true,
configurable: false,
hasPendingActivity: true,
klass: {},
JSType: "0b11101110",
values: ["callback"],

proto: {
decode: {
fn: "decode",
length: 2,
},
decodeSync: {
fn: "decodeSync",
length: 2,
},
end: {
fn: "end",
length: 2,
},
endSync: {
fn: "endSync",
length: 2,
},
},
}),
];

0 comments on commit 1da810a

Please sign in to comment.