diff --git a/examples/spec/streams-compression.js b/examples/spec/streams-compression.js index 16a8142..7f59423 100644 --- a/examples/spec/streams-compression.js +++ b/examples/spec/streams-compression.js @@ -24,6 +24,9 @@ const ds0 = new DecompressionStream('gzip'); test('DS readable is ReadableStream', ds0.readable instanceof ReadableStream, true); test('DS writable is WritableStream', ds0.writable instanceof WritableStream, true); +const csBrotli = new CompressionStream('brotli'); +test('CS brotli toStringTag', Object.prototype.toString.call(csBrotli), '[object CompressionStream]'); + async function collectStream(readable) { const reader = readable.getReader(); const chunks = []; @@ -96,6 +99,25 @@ async function testDeflateRawRoundTrip() { test('deflate-raw roundtrip', new TextDecoder().decode(decompressed), 'Raw deflate test'); } +async function testBrotliRoundTrip() { + const input = new TextEncoder().encode('brotli stream test data'); + const cs = new CompressionStream('brotli'); + const writer = cs.writable.getWriter(); + writer.write(input); + writer.close(); + const compressed = await collectStream(cs.readable); + test('brotli compressed length > 0', compressed.length > 0, true); + + const ds = new DecompressionStream('brotli'); + const writer2 = ds.writable.getWriter(); + writer2.write(compressed); + writer2.close(); + const reader = ds.readable.getReader(); + const first = await reader.read(); + test('brotli decompressed chunk is Uint8Array', first.value.constructor, Uint8Array); + test('brotli roundtrip', new TextDecoder().decode(first.value), 'brotli stream test data'); +} + async function testPipeThrough() { const input = 'pipe through compression test'; const encoded = new TextEncoder().encode(input); @@ -155,6 +177,7 @@ async function testLargeData() { await testGzipRoundTrip(); await testDeflateRoundTrip(); await testDeflateRawRoundTrip(); +await testBrotliRoundTrip(); await testPipeThrough(); await testMultipleChunks(); await testLargeData(); diff --git a/include/streams/brotli.h b/include/streams/brotli.h new file mode 100644 index 0000000..36b52c5 --- /dev/null +++ b/include/streams/brotli.h @@ -0,0 +1,23 @@ +#ifndef STREAMS_BROTLI_H +#define STREAMS_BROTLI_H + +#include +#include +#include "types.h" + +typedef struct brotli_stream_state brotli_stream_state_t; + +brotli_stream_state_t *brotli_stream_state_new(bool decompress); +void brotli_stream_state_destroy(brotli_stream_state_t *st); + +ant_value_t brotli_stream_transform( + ant_t *js, brotli_stream_state_t *st, + ant_value_t ctrl_obj, const uint8_t *input, size_t input_len +); + +ant_value_t brotli_stream_flush( + ant_t *js, brotli_stream_state_t *st, + ant_value_t ctrl_obj +); + +#endif diff --git a/meson/deps/meson.build b/meson/deps/meson.build index 708d09a..b185f3d 100644 --- a/meson/deps/meson.build +++ b/meson/deps/meson.build @@ -76,21 +76,15 @@ zlib_dep = zlib_ng_sub.get_variable('zlib_ng_dep') zlib_ng_srcdir = subprojects_src / deps_info['zlib-ng']['dir'] zlib_ng_builddir = subprojects_build / deps_info['zlib-ng']['dir'] -brotli_common_dep = dependency('libbrotlicommon', - fallback: ['google-brotli', 'libbrotlicommon'], - required: true, - static: is_static, -) -brotli_dec_dep = dependency('libbrotlidec', - fallback: ['google-brotli', 'libbrotlidec'], - required: true, - static: is_static, -) -brotli_enc_dep = dependency('libbrotlienc', - fallback: ['google-brotli', 'libbrotlienc'], - required: true, - static: is_static, -) +brotli_sub = subproject('google-brotli', default_options: [ + 'default_library=static', + 'b_lto=false', + 'warning_level=0', +]) + +brotli_common_dep = brotli_sub.get_variable('brotli_common_dep') +brotli_dec_dep = brotli_sub.get_variable('brotli_decoder_dep') +brotli_enc_dep = brotli_sub.get_variable('brotli_encoder_dep') cmake_prefix = get_option('deps_prefix_cmake') diff --git a/src/modules/buffer.c b/src/modules/buffer.c index 3c76dbb..f4f75bf 100644 --- a/src/modules/buffer.c +++ b/src/modules/buffer.c @@ -2057,7 +2057,10 @@ void init_buffer_module() { js_mkprop_fast(js, arraybuffer_ctor_obj, "name", 4, ANT_STRING("ArrayBuffer")); js_set_descriptor(js, arraybuffer_ctor_obj, "name", 4, 0); js_define_species_getter(js, arraybuffer_ctor_obj); - js_set(js, glob, "ArrayBuffer", js_obj_to_func(arraybuffer_ctor_obj)); + ant_value_t arraybuffer_ctor = js_obj_to_func(arraybuffer_ctor_obj); + js_set(js, arraybuffer_proto, "constructor", arraybuffer_ctor); + js_set_descriptor(js, arraybuffer_proto, "constructor", 11, JS_DESC_W | JS_DESC_C); + js_set(js, glob, "ArrayBuffer", arraybuffer_ctor); ant_value_t typedarray_proto = js_mkobj(js); js_set_proto_init(typedarray_proto, object_proto); @@ -2091,15 +2094,16 @@ void init_buffer_module() { ant_value_t name##_ctor_obj = js_mkobj(js); \ ant_value_t name##_proto = js_mkobj(js); \ js_set_proto_init(name##_proto, typedarray_proto); \ - js_setprop(js, name##_proto, ANT_STRING("constructor"), js_obj_to_func(name##_ctor_obj)); \ - js_set_descriptor(js, name##_proto, "constructor", 11, JS_DESC_W | JS_DESC_C); \ js_set_sym(js, name##_proto, get_toStringTag_sym(), js_mkstr(js, #name, sizeof(#name) - 1)); \ js_set_slot(name##_ctor_obj, SLOT_CFUNC, js_mkfun(js_##name##_constructor)); \ js_setprop(js, name##_ctor_obj, js_mkstr(js, "prototype", 9), name##_proto); \ js_mkprop_fast(js, name##_ctor_obj, "name", 4, ANT_STRING(#name)); \ js_set_descriptor(js, name##_ctor_obj, "name", 4, 0); \ js_define_species_getter(js, name##_ctor_obj); \ - js_set(js, glob, #name, js_obj_to_func(name##_ctor_obj)); \ + ant_value_t name##_ctor = js_obj_to_func(name##_ctor_obj); \ + js_setprop(js, name##_proto, ANT_STRING("constructor"), name##_ctor); \ + js_set_descriptor(js, name##_proto, "constructor", 11, JS_DESC_W | JS_DESC_C); \ + js_set(js, glob, #name, name##_ctor); \ } while(0) SETUP_TYPEDARRAY(Int8Array); @@ -2134,7 +2138,11 @@ void init_buffer_module() { js_mkprop_fast(js, dataview_ctor_obj, "prototype", 9, dataview_proto); js_mkprop_fast(js, dataview_ctor_obj, "name", 4, ANT_STRING("DataView")); js_set_descriptor(js, dataview_ctor_obj, "name", 4, 0); - js_set(js, glob, "DataView", js_obj_to_func(dataview_ctor_obj)); + + ant_value_t dataview_ctor = js_obj_to_func(dataview_ctor_obj); + js_set(js, dataview_proto, "constructor", dataview_ctor); + js_set_descriptor(js, dataview_proto, "constructor", 11, JS_DESC_W | JS_DESC_C); + js_set(js, glob, "DataView", dataview_ctor); ant_value_t sharedarraybuffer_ctor_obj = js_mkobj(js); ant_value_t sharedarraybuffer_proto = js_mkobj(js); @@ -2147,7 +2155,11 @@ void init_buffer_module() { js_mkprop_fast(js, sharedarraybuffer_ctor_obj, "prototype", 9, sharedarraybuffer_proto); js_mkprop_fast(js, sharedarraybuffer_ctor_obj, "name", 4, ANT_STRING("SharedArrayBuffer")); js_set_descriptor(js, sharedarraybuffer_ctor_obj, "name", 4, 0); - js_set(js, glob, "SharedArrayBuffer", js_obj_to_func(sharedarraybuffer_ctor_obj)); + + ant_value_t sharedarraybuffer_ctor = js_obj_to_func(sharedarraybuffer_ctor_obj); + js_set(js, sharedarraybuffer_proto, "constructor", sharedarraybuffer_ctor); + js_set_descriptor(js, sharedarraybuffer_proto, "constructor", 11, JS_DESC_W | JS_DESC_C); + js_set(js, glob, "SharedArrayBuffer", sharedarraybuffer_ctor); ant_value_t buffer_ctor_obj = js_mkobj(js); ant_value_t buffer_proto = js_mkobj(js); @@ -2180,7 +2192,11 @@ void init_buffer_module() { js_mkprop_fast(js, buffer_ctor_obj, "prototype", 9, buffer_proto); js_mkprop_fast(js, buffer_ctor_obj, "name", 4, ANT_STRING("Buffer")); js_set_descriptor(js, buffer_ctor_obj, "name", 4, 0); - js_set(js, glob, "Buffer", js_obj_to_func(buffer_ctor_obj)); + + ant_value_t buffer_ctor = js_obj_to_func(buffer_ctor_obj); + js_set(js, buffer_proto, "constructor", buffer_ctor); + js_set_descriptor(js, buffer_proto, "constructor", 11, JS_DESC_W | JS_DESC_C); + js_set(js, glob, "Buffer", buffer_ctor); } void cleanup_buffer_module(void) { diff --git a/src/streams/brotli.c b/src/streams/brotli.c new file mode 100644 index 0000000..dbbef31 --- /dev/null +++ b/src/streams/brotli.c @@ -0,0 +1,150 @@ +#include +#include + +#include +#include + +#include "ant.h" +#include "errors.h" +#include "internal.h" +#include "modules/buffer.h" +#include "streams/brotli.h" +#include "streams/transform.h" + +#define BROTLI_CHUNK_SIZE 16384 + +struct brotli_stream_state { + bool decompress; + union { + BrotliEncoderState *enc; + BrotliDecoderState *dec; + } u; +}; + +static ant_value_t brotli_enqueue_buffer( + ant_t *js, ant_value_t ctrl_obj, const uint8_t *data, size_t len +) { + ArrayBufferData *ab = create_array_buffer_data(len); + if (!ab) return js_mkerr(js, "out of memory"); + memcpy(ab->data, data, len); + ant_value_t arr = create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, len, "Uint8Array"); + if (!ts_is_controller(ctrl_obj)) + return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TransformStreamDefaultController"); + return ts_ctrl_enqueue(js, ctrl_obj, arr); +} + +static ant_value_t brotli_encoder_step( + ant_t *js, brotli_stream_state_t *st, ant_value_t ctrl_obj, + const uint8_t *input, size_t input_len, BrotliEncoderOperation op +) { + size_t avail_in = input_len; + const uint8_t *next_in = input; + + for (;;) { + uint8_t out_buf[BROTLI_CHUNK_SIZE]; + uint8_t *next_out = out_buf; + size_t avail_out = sizeof(out_buf); + + if (!BrotliEncoderCompressStream( + st->u.enc, op, &avail_in, &next_in, &avail_out, &next_out, NULL)) { + return js_mkerr_typed(js, JS_ERR_TYPE, "Compression failed"); + } + + size_t have = sizeof(out_buf) - avail_out; + if (have > 0) { + ant_value_t r = brotli_enqueue_buffer(js, ctrl_obj, out_buf, have); + if (is_err(r)) return r; + } + + if (op == BROTLI_OPERATION_FINISH) { + if (BrotliEncoderIsFinished(st->u.enc) && + !BrotliEncoderHasMoreOutput(st->u.enc)) + break; + } else if (avail_in == 0 && !BrotliEncoderHasMoreOutput(st->u.enc)) break; + } + + return js_mkundef(); +} + +static ant_value_t brotli_decoder_step( + ant_t *js, brotli_stream_state_t *st, ant_value_t ctrl_obj, + const uint8_t *input, size_t input_len +) { + size_t avail_in = input_len; + const uint8_t *next_in = input; + + for (;;) { + uint8_t out_buf[BROTLI_CHUNK_SIZE]; + uint8_t *next_out = out_buf; + size_t avail_out = sizeof(out_buf); + + BrotliDecoderResult ret = BrotliDecoderDecompressStream( + st->u.dec, &avail_in, &next_in, &avail_out, &next_out, NULL); + + size_t have = sizeof(out_buf) - avail_out; + if (have > 0) { + ant_value_t r = brotli_enqueue_buffer(js, ctrl_obj, out_buf, have); + if (is_err(r)) return r; + } + + if (ret == BROTLI_DECODER_RESULT_ERROR) + return js_mkerr_typed(js, JS_ERR_TYPE, "Decompression failed"); + if (ret == BROTLI_DECODER_RESULT_SUCCESS) break; + if (ret == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT || + BrotliDecoderHasMoreOutput(st->u.dec)) continue; + if (ret == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) break; + } + + return js_mkundef(); +} + +brotli_stream_state_t *brotli_stream_state_new(bool decompress) { + brotli_stream_state_t *st = calloc(1, sizeof(*st)); + if (!st) return NULL; + + st->decompress = decompress; + if (decompress) { + st->u.dec = BrotliDecoderCreateInstance(NULL, NULL, NULL); + if (!st->u.dec) { + free(st); + return NULL; + }} else { + st->u.enc = BrotliEncoderCreateInstance(NULL, NULL, NULL); + if (!st->u.enc) { + free(st); + return NULL; + }} + + return st; +} + +void brotli_stream_state_destroy(brotli_stream_state_t *st) { + if (!st) return; + if (st->decompress) BrotliDecoderDestroyInstance(st->u.dec); + else BrotliEncoderDestroyInstance(st->u.enc); + free(st); +} + +ant_value_t brotli_stream_transform( + ant_t *js, brotli_stream_state_t *st, + ant_value_t ctrl_obj, const uint8_t *input, size_t input_len +) { + if (st->decompress) + return brotli_decoder_step(js, st, ctrl_obj, input, input_len); + return brotli_encoder_step(js, st, ctrl_obj, input, input_len, BROTLI_OPERATION_PROCESS); +} + +ant_value_t brotli_stream_flush( + ant_t *js, brotli_stream_state_t *st, + ant_value_t ctrl_obj +) { + if (st->decompress) { + ant_value_t result = brotli_decoder_step(js, st, ctrl_obj, NULL, 0); + if (is_err(result)) return result; + if (!BrotliDecoderIsFinished(st->u.dec)) + return js_mkerr_typed(js, JS_ERR_TYPE, "Decompression failed"); + return js_mkundef(); + } + + return brotli_encoder_step(js, st, ctrl_obj, NULL, 0, BROTLI_OPERATION_FINISH); +} diff --git a/src/streams/compression.c b/src/streams/compression.c index b8c05fa..5f0f4e2 100644 --- a/src/streams/compression.c +++ b/src/streams/compression.c @@ -10,6 +10,7 @@ #include "modules/symbol.h" #include "modules/buffer.h" +#include "streams/brotli.h" #include "streams/compression.h" #include "streams/transform.h" @@ -27,6 +28,7 @@ static int zfmt_window_bits(zformat_t fmt, bool decompress) { case ZFMT_GZIP: return decompress ? (15 + 32) : (15 + 16); case ZFMT_DEFLATE: return 15; case ZFMT_DEFLATE_RAW: return -15; + case ZFMT_BROTLI: return 15; } return 15; } @@ -39,6 +41,7 @@ static int parse_format(ant_t *js, ant_value_t arg, zformat_t *out) { if (len == 4 && !memcmp(s, "gzip", 4)) { *out = ZFMT_GZIP; return 0; } if (len == 7 && !memcmp(s, "deflate", 7)) { *out = ZFMT_DEFLATE; return 0; } if (len == 11 && !memcmp(s, "deflate-raw", 11)) { *out = ZFMT_DEFLATE_RAW; return 0; } + if (len == 6 && !memcmp(s, "brotli", 6)) { *out = ZFMT_BROTLI; return 0; } return -1; } @@ -46,6 +49,11 @@ static ant_value_t get_ts(ant_value_t obj) { return js_get_slot(obj, SLOT_ENTRIES); } +static zformat_t get_wrapper_format(ant_value_t wrapper) { + ant_value_t fmt = js_get_slot(wrapper, SLOT_CTOR); + return (zformat_t)(int)js_getnum(fmt); +} + bool cs_is_stream(ant_value_t obj) { return is_object_type(obj) && vtype(js_get_slot(obj, SLOT_DATA)) == T_NUM @@ -98,6 +106,19 @@ static void zstate_inflate_finalize(ant_t *js, ant_object_t *obj) { }} } +static void brotli_state_finalize(ant_t *js, ant_object_t *obj) { + if (!obj->extra_slots) return; + ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots; + + for (uint8_t i = 0; i < obj->extra_count; i++) { + if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) { + brotli_stream_state_t *st = + (brotli_stream_state_t *)(uintptr_t)(size_t)js_getnum(entries[i].value); + brotli_stream_state_destroy(st); + return; + }} +} + static bool get_chunk_bytes(ant_t *js, ant_value_t chunk, const uint8_t **out, size_t *len) { ant_value_t slot = js_get_slot(chunk, SLOT_BUFFER); TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(slot); @@ -162,6 +183,12 @@ static ant_value_t cs_transform(ant_t *js, ant_value_t *args, int nargs) { if (!input || input_len == 0) return js_mkundef(); + if (get_wrapper_format(wrapper) == ZFMT_BROTLI) { + brotli_stream_state_t *brotli_st = + (brotli_stream_state_t *)(uintptr_t)(size_t)js_getnum(state_val); + return brotli_stream_transform(js, brotli_st, ctrl_obj, input, input_len); + } + st->strm.next_in = (Bytef *)input; st->strm.avail_in = (uInt)input_len; @@ -185,9 +212,14 @@ static ant_value_t cs_transform(ant_t *js, ant_value_t *args, int nargs) { static ant_value_t cs_flush(ant_t *js, ant_value_t *args, int nargs) { ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA); ant_value_t state_val = js_get_slot(wrapper, SLOT_DATA); - zstate_t *st = (zstate_t *)(uintptr_t)(size_t)js_getnum(state_val); ant_value_t ctrl_obj = (nargs > 0) ? args[0] : js_mkundef(); + if (get_wrapper_format(wrapper) == ZFMT_BROTLI) { + brotli_stream_state_t *brotli_st = + (brotli_stream_state_t *)(uintptr_t)(size_t)js_getnum(state_val); + return brotli_stream_flush(js, brotli_st, ctrl_obj); + } zstate_t *st = (zstate_t *)(uintptr_t)(size_t)js_getnum(state_val); + st->strm.next_in = NULL; st->strm.avail_in = 0; @@ -231,22 +263,28 @@ static ant_value_t js_cs_ctor(ant_t *js, ant_value_t *args, int nargs) { return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'CompressionStream': Unsupported compression format"); zstate_t *st = calloc(1, sizeof(zstate_t)); - if (!st) return js_mkerr(js, "out of memory"); - st->format = fmt; - - int ret = deflateInit2(&st->strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, - zfmt_window_bits(fmt, false), 8, Z_DEFAULT_STRATEGY); - if (ret != Z_OK) { free(st); return js_mkerr(js, "Failed to initialize compression"); } - st->initialized = true; + brotli_stream_state_t *brotli = NULL; + if (fmt == ZFMT_BROTLI) { + brotli = brotli_stream_state_new(false); + if (!brotli) return js_mkerr(js, "Failed to initialize compression"); + } else { + if (!st) return js_mkerr(js, "out of memory"); + st->format = fmt; + int ret = deflateInit2(&st->strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, + zfmt_window_bits(fmt, false), 8, Z_DEFAULT_STRATEGY); + if (ret != Z_OK) { free(st); return js_mkerr(js, "Failed to initialize compression"); } + st->initialized = true; + } ant_value_t obj = js_mkobj(js); ant_value_t proto = js_instance_proto_from_new_target(js, g_cs_proto); if (is_object_type(proto)) js_set_proto_init(obj, proto); - js_set_slot(obj, SLOT_DATA, ANT_PTR(st)); - js_set_finalizer(obj, zstate_finalize); + js_set_slot(obj, SLOT_DATA, fmt == ZFMT_BROTLI ? ANT_PTR(brotli) : ANT_PTR(st)); + js_set_finalizer(obj, fmt == ZFMT_BROTLI ? brotli_state_finalize : zstate_finalize); ant_value_t wrapper = js_mkobj(js); - js_set_slot(wrapper, SLOT_DATA, ANT_PTR(st)); + js_set_slot(wrapper, SLOT_DATA, fmt == ZFMT_BROTLI ? ANT_PTR(brotli) : ANT_PTR(st)); + js_set_slot(wrapper, SLOT_CTOR, js_mknum((double)fmt)); ant_value_t transformer = js_mkobj(js); ant_value_t transform_fn = js_heavy_mkfun(js, cs_transform, wrapper); @@ -263,7 +301,11 @@ static ant_value_t js_cs_ctor(ant_t *js, ant_value_t *args, int nargs) { js->new_target = saved_new_target; js->this_val = saved_this; - if (is_err(ts_obj)) { deflateEnd(&st->strm); free(st); return ts_obj; } + if (is_err(ts_obj)) { + if (fmt == ZFMT_BROTLI) brotli_stream_state_destroy(brotli); + else { deflateEnd(&st->strm); free(st); } + return ts_obj; + } js_set_slot(obj, SLOT_ENTRIES, ts_obj); js_set_slot(wrapper, SLOT_ENTRIES, ts_obj); @@ -284,6 +326,12 @@ static ant_value_t ds_transform(ant_t *js, ant_value_t *args, int nargs) { return js_mkerr_typed(js, JS_ERR_TYPE, "The provided value is not of type '(ArrayBuffer or ArrayBufferView)'"); if (!input || input_len == 0) return js_mkundef(); + if (get_wrapper_format(wrapper) == ZFMT_BROTLI) { + brotli_stream_state_t *brotli_st = + (brotli_stream_state_t *)(uintptr_t)(size_t)js_getnum(state_val); + return brotli_stream_transform(js, brotli_st, ctrl_obj, input, input_len); + } + st->strm.next_in = (Bytef *)input; st->strm.avail_in = (uInt)input_len; @@ -306,6 +354,14 @@ static ant_value_t ds_transform(ant_t *js, ant_value_t *args, int nargs) { } static ant_value_t ds_flush(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA); + ant_value_t state_val = js_get_slot(wrapper, SLOT_DATA); + ant_value_t ctrl_obj = (nargs > 0) ? args[0] : js_mkundef(); + if (get_wrapper_format(wrapper) == ZFMT_BROTLI) { + brotli_stream_state_t *st = + (brotli_stream_state_t *)(uintptr_t)(size_t)js_getnum(state_val); + return brotli_stream_flush(js, st, ctrl_obj); + } return js_mkundef(); } @@ -333,21 +389,27 @@ static ant_value_t js_ds_ctor(ant_t *js, ant_value_t *args, int nargs) { return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'DecompressionStream': Unsupported compression format"); zstate_t *st = calloc(1, sizeof(zstate_t)); - if (!st) return js_mkerr(js, "out of memory"); - st->format = fmt; - - int ret = inflateInit2(&st->strm, zfmt_window_bits(fmt, true)); - if (ret != Z_OK) { free(st); return js_mkerr(js, "Failed to initialize decompression"); } - st->initialized = true; + brotli_stream_state_t *brotli = NULL; + if (fmt == ZFMT_BROTLI) { + brotli = brotli_stream_state_new(true); + if (!brotli) return js_mkerr(js, "Failed to initialize decompression"); + } else { + if (!st) return js_mkerr(js, "out of memory"); + st->format = fmt; + int ret = inflateInit2(&st->strm, zfmt_window_bits(fmt, true)); + if (ret != Z_OK) { free(st); return js_mkerr(js, "Failed to initialize decompression"); } + st->initialized = true; + } ant_value_t obj = js_mkobj(js); ant_value_t proto = js_instance_proto_from_new_target(js, g_ds_proto); if (is_object_type(proto)) js_set_proto_init(obj, proto); - js_set_slot(obj, SLOT_DATA, ANT_PTR(st)); - js_set_finalizer(obj, zstate_inflate_finalize); + js_set_slot(obj, SLOT_DATA, fmt == ZFMT_BROTLI ? ANT_PTR(brotli) : ANT_PTR(st)); + js_set_finalizer(obj, fmt == ZFMT_BROTLI ? brotli_state_finalize : zstate_inflate_finalize); ant_value_t wrapper = js_mkobj(js); - js_set_slot(wrapper, SLOT_DATA, ANT_PTR(st)); + js_set_slot(wrapper, SLOT_DATA, fmt == ZFMT_BROTLI ? ANT_PTR(brotli) : ANT_PTR(st)); + js_set_slot(wrapper, SLOT_CTOR, js_mknum((double)fmt)); ant_value_t transformer = js_mkobj(js); ant_value_t transform_fn = js_heavy_mkfun(js, ds_transform, wrapper); @@ -365,7 +427,11 @@ static ant_value_t js_ds_ctor(ant_t *js, ant_value_t *args, int nargs) { js->new_target = saved_new_target; js->this_val = saved_this; - if (is_err(ts_obj)) { inflateEnd(&st->strm); free(st); return ts_obj; } + if (is_err(ts_obj)) { + if (fmt == ZFMT_BROTLI) brotli_stream_state_destroy(brotli); + else { inflateEnd(&st->strm); free(st); } + return ts_obj; + } js_set_slot(obj, SLOT_ENTRIES, ts_obj); js_set_slot(wrapper, SLOT_ENTRIES, ts_obj);