MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <stdlib.h>
2#include <string.h>
3#include <zlib.h>
4
5#include "ant.h"
6#include "ptr.h"
7#include "errors.h"
8#include "runtime.h"
9#include "internal.h"
10#include "descriptors.h"
11
12#include "modules/symbol.h"
13#include "modules/buffer.h"
14#include "streams/brotli.h"
15#include "streams/compression.h"
16#include "streams/transform.h"
17
18ant_value_t g_cs_proto;
19ant_value_t g_ds_proto;
20
21enum {
22 CS_Z_NATIVE_TAG = 0x43535a53u, // CSZS
23 DS_Z_NATIVE_TAG = 0x44535a53u, // DSZS
24
25 CS_BROTLI_NATIVE_TAG = 0x43534252u, // CSBR
26 DS_BROTLI_NATIVE_TAG = 0x44534252u // DSBR
27};
28
29typedef struct {
30 z_stream strm;
31 zformat_t format;
32 bool initialized;
33} zstate_t;
34
35static int zfmt_window_bits(zformat_t fmt, bool decompress) {
36 switch (fmt) {
37 case ZFMT_GZIP: return decompress ? (15 + 32) : (15 + 16);
38 case ZFMT_DEFLATE: return 15;
39 case ZFMT_DEFLATE_RAW: return -15;
40 case ZFMT_BROTLI: return 15;
41 }
42 return 15;
43}
44
45static int parse_format(ant_t *js, ant_value_t arg, zformat_t *out) {
46 if (vtype(arg) != T_STR) return -1;
47 size_t len;
48 const char *s = js_getstr(js, arg, &len);
49 if (!s) return -1;
50 if (len == 4 && !memcmp(s, "gzip", 4)) { *out = ZFMT_GZIP; return 0; }
51 if (len == 7 && !memcmp(s, "deflate", 7)) { *out = ZFMT_DEFLATE; return 0; }
52 if (len == 11 && !memcmp(s, "deflate-raw", 11)) { *out = ZFMT_DEFLATE_RAW; return 0; }
53 if (len == 6 && !memcmp(s, "brotli", 6)) { *out = ZFMT_BROTLI; return 0; }
54 return -1;
55}
56
57static ant_value_t get_ts(ant_value_t obj) {
58 return js_get_slot(obj, SLOT_ENTRIES);
59}
60
61bool cs_is_stream(ant_value_t obj) {
62 return is_object_type(obj)
63 && (js_check_native_tag(obj, CS_Z_NATIVE_TAG) || js_check_native_tag(obj, CS_BROTLI_NATIVE_TAG))
64 && ts_is_stream(get_ts(obj));
65}
66
67bool ds_is_stream(ant_value_t obj) {
68 return is_object_type(obj)
69 && (js_check_native_tag(obj, DS_Z_NATIVE_TAG) || js_check_native_tag(obj, DS_BROTLI_NATIVE_TAG))
70 && ts_is_stream(get_ts(obj));
71}
72
73ant_value_t cs_stream_readable(ant_value_t obj) {
74 return ts_stream_readable(get_ts(obj));
75}
76
77ant_value_t cs_stream_writable(ant_value_t obj) {
78 return ts_stream_writable(get_ts(obj));
79}
80
81ant_value_t ds_stream_readable(ant_value_t obj) {
82 return ts_stream_readable(get_ts(obj));
83}
84
85ant_value_t ds_stream_writable(ant_value_t obj) {
86 return ts_stream_writable(get_ts(obj));
87}
88
89static void zstate_finalize(ant_t *js, ant_object_t *obj) {
90 ant_value_t value = js_obj_from_ptr(obj);
91 zstate_t *st = (zstate_t *)js_get_native(value, CS_Z_NATIVE_TAG);
92 if (!st) return;
93 if (st->initialized) deflateEnd(&st->strm);
94 free(st);
95 js_clear_native(value, CS_Z_NATIVE_TAG);
96}
97
98static void zstate_inflate_finalize(ant_t *js, ant_object_t *obj) {
99 ant_value_t value = js_obj_from_ptr(obj);
100 zstate_t *st = (zstate_t *)js_get_native(value, DS_Z_NATIVE_TAG);
101 if (!st) return;
102 if (st->initialized) inflateEnd(&st->strm);
103 free(st);
104 js_clear_native(value, DS_Z_NATIVE_TAG);
105}
106
107static void brotli_state_finalize(ant_t *js, ant_object_t *obj) {
108 ant_value_t value = js_obj_from_ptr(obj);
109 uint32_t tag = CS_BROTLI_NATIVE_TAG;
110 brotli_stream_state_t *st = (brotli_stream_state_t *)js_get_native(value, tag);
111 if (!st) {
112 tag = DS_BROTLI_NATIVE_TAG;
113 st = (brotli_stream_state_t *)js_get_native(value, tag);
114 }
115 if (!st) return;
116 brotli_stream_state_destroy(st);
117 js_clear_native(value, tag);
118}
119
120static ant_value_t enqueue_buffer(ant_t *js, ant_value_t ctrl_obj, const uint8_t *data, size_t len) {
121 ArrayBufferData *ab = create_array_buffer_data(len);
122 if (!ab) return js_mkerr(js, "out of memory");
123 memcpy(ab->data, data, len);
124 ant_value_t arr = create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, len, "Uint8Array");
125 if (!ts_is_controller(ctrl_obj))
126 return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TransformStreamDefaultController");
127 return ts_ctrl_enqueue(js, ctrl_obj, arr);
128}
129
130#define ZCHUNK_SIZE 16384
131
132static ant_value_t cs_transform(ant_t *js, ant_value_t *args, int nargs) {
133 ant_value_t ctrl_obj = (nargs > 1) ? args[1] : js_mkundef();
134 ant_value_t chunk = (nargs > 0) ? args[0] : js_mkundef();
135
136 const uint8_t *input = NULL;
137 size_t input_len = 0;
138
139 if (!is_object_type(chunk) || !buffer_source_get_bytes(js, chunk, &input, &input_len))
140 return js_mkerr_typed(js, JS_ERR_TYPE, "The provided value is not of type '(ArrayBuffer or ArrayBufferView)'");
141
142 if (!input || input_len == 0) return js_mkundef();
143
144 brotli_stream_state_t *brotli_st = (brotli_stream_state_t *)js_get_native(js->current_func, CS_BROTLI_NATIVE_TAG);
145 if (brotli_st)
146 return brotli_stream_transform(js, brotli_st, ctrl_obj, input, input_len);
147
148 zstate_t *st = (zstate_t *)js_get_native(js->current_func, CS_Z_NATIVE_TAG);
149 if (!st)
150 return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid CompressionStream");
151
152 st->strm.next_in = (Bytef *)input;
153 st->strm.avail_in = (uInt)input_len;
154
155 uint8_t out_buf[ZCHUNK_SIZE];
156 do {
157 st->strm.next_out = out_buf;
158 st->strm.avail_out = ZCHUNK_SIZE;
159 int ret = deflate(&st->strm, Z_NO_FLUSH);
160 if (ret == Z_STREAM_ERROR)
161 return js_mkerr_typed(js, JS_ERR_TYPE, "Compression failed");
162 size_t have = ZCHUNK_SIZE - st->strm.avail_out;
163 if (have > 0) {
164 ant_value_t r = enqueue_buffer(js, ctrl_obj, out_buf, have);
165 if (is_err(r)) return r;
166 }
167 } while (st->strm.avail_out == 0);
168
169 return js_mkundef();
170}
171
172static ant_value_t cs_flush(ant_t *js, ant_value_t *args, int nargs) {
173 ant_value_t ctrl_obj = (nargs > 0) ? args[0] : js_mkundef();
174
175 brotli_stream_state_t *brotli_st = (brotli_stream_state_t *)js_get_native(js->current_func, CS_BROTLI_NATIVE_TAG);
176 if (brotli_st)
177 return brotli_stream_flush(js, brotli_st, ctrl_obj);
178
179 zstate_t *st = (zstate_t *)js_get_native(js->current_func, CS_Z_NATIVE_TAG);
180 if (!st) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid CompressionStream");
181
182 st->strm.next_in = NULL;
183 st->strm.avail_in = 0;
184
185 uint8_t out_buf[ZCHUNK_SIZE];
186 int ret;
187 do {
188 st->strm.next_out = out_buf;
189 st->strm.avail_out = ZCHUNK_SIZE;
190 ret = deflate(&st->strm, Z_FINISH);
191 size_t have = ZCHUNK_SIZE - st->strm.avail_out;
192 if (have > 0) {
193 ant_value_t r = enqueue_buffer(js, ctrl_obj, out_buf, have);
194 if (is_err(r)) return r;
195 }
196 } while (ret != Z_STREAM_END);
197
198 return js_mkundef();
199}
200
201static ant_value_t js_cs_get_readable(ant_t *js, ant_value_t *args, int nargs) {
202 ant_value_t ts_obj = get_ts(js->this_val);
203 if (!ts_is_stream(ts_obj)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid CompressionStream");
204 return ts_stream_readable(ts_obj);
205}
206
207static ant_value_t js_cs_get_writable(ant_t *js, ant_value_t *args, int nargs) {
208 ant_value_t ts_obj = get_ts(js->this_val);
209 if (!ts_is_stream(ts_obj)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid CompressionStream");
210 return ts_stream_writable(ts_obj);
211}
212
213static ant_value_t js_cs_ctor(ant_t *js, ant_value_t *args, int nargs) {
214 if (vtype(js->new_target) == T_UNDEF)
215 return js_mkerr_typed(js, JS_ERR_TYPE, "CompressionStream constructor requires 'new'");
216
217 if (nargs < 1)
218 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'CompressionStream': 1 argument required");
219
220 zformat_t fmt;
221 if (parse_format(js, args[0], &fmt) < 0)
222 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'CompressionStream': Unsupported compression format");
223
224 zstate_t *st = calloc(1, sizeof(zstate_t));
225 brotli_stream_state_t *brotli = NULL;
226 if (fmt == ZFMT_BROTLI) {
227 brotli = brotli_stream_state_new(false);
228 if (!brotli) return js_mkerr(js, "Failed to initialize compression");
229 } else {
230 if (!st) return js_mkerr(js, "out of memory");
231 st->format = fmt;
232 int ret = deflateInit2(&st->strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
233 zfmt_window_bits(fmt, false), 8, Z_DEFAULT_STRATEGY);
234 if (ret != Z_OK) { free(st); return js_mkerr(js, "Failed to initialize compression"); }
235 st->initialized = true;
236 }
237
238 ant_value_t obj = js_mkobj(js);
239 ant_value_t proto = js_instance_proto_from_new_target(js, g_cs_proto);
240 if (is_object_type(proto)) js_set_proto_init(obj, proto);
241
242 js_set_native(obj, fmt == ZFMT_BROTLI ? (void *)brotli : (void *)st, fmt == ZFMT_BROTLI ? CS_BROTLI_NATIVE_TAG : CS_Z_NATIVE_TAG);
243 js_set_finalizer(obj, fmt == ZFMT_BROTLI ? brotli_state_finalize : zstate_finalize);
244
245 ant_value_t transformer = js_mkobj(js);
246
247 ant_value_t transform_fn = js_heavy_mkfun_native(js, cs_transform, fmt == ZFMT_BROTLI
248 ? (void *)brotli : (void *)st, fmt == ZFMT_BROTLI
249 ? CS_BROTLI_NATIVE_TAG : CS_Z_NATIVE_TAG
250 );
251
252 ant_value_t flush_fn = js_heavy_mkfun_native(js, cs_flush, fmt == ZFMT_BROTLI
253 ? (void *)brotli : (void *)st, fmt == ZFMT_BROTLI
254 ? CS_BROTLI_NATIVE_TAG : CS_Z_NATIVE_TAG
255 );
256
257 js_set(js, transformer, "transform", transform_fn);
258 js_set(js, transformer, "flush", flush_fn);
259
260 ant_value_t ctor_args[1] = { transformer };
261 ant_value_t saved_new_target = js->new_target;
262 ant_value_t saved_this = js->this_val;
263 js->new_target = js_mknum(1);
264
265 ant_value_t ts_obj = js_ts_ctor(js, ctor_args, 1);
266 js->new_target = saved_new_target;
267 js->this_val = saved_this;
268
269 if (is_err(ts_obj)) {
270 if (fmt == ZFMT_BROTLI) brotli_stream_state_destroy(brotli);
271 else { deflateEnd(&st->strm); free(st); }
272 return ts_obj;
273 }
274 js_set_slot(obj, SLOT_ENTRIES, ts_obj);
275 js_set_slot_wb(js, transform_fn, SLOT_ENTRIES, obj);
276 js_set_slot_wb(js, flush_fn, SLOT_ENTRIES, obj);
277
278 return obj;
279}
280
281static ant_value_t ds_transform(ant_t *js, ant_value_t *args, int nargs) {
282 ant_value_t ctrl_obj = (nargs > 1) ? args[1] : js_mkundef();
283
284 ant_value_t chunk = (nargs > 0) ? args[0] : js_mkundef();
285 const uint8_t *input = NULL;
286 size_t input_len = 0;
287
288 if (!is_object_type(chunk) || !buffer_source_get_bytes(js, chunk, &input, &input_len))
289 return js_mkerr_typed(js, JS_ERR_TYPE, "The provided value is not of type '(ArrayBuffer or ArrayBufferView)'");
290
291 if (!input || input_len == 0) return js_mkundef();
292 brotli_stream_state_t *brotli_st = (brotli_stream_state_t *)js_get_native(js->current_func, DS_BROTLI_NATIVE_TAG);
293 if (brotli_st)
294 return brotli_stream_transform(js, brotli_st, ctrl_obj, input, input_len);
295
296 zstate_t *st = (zstate_t *)js_get_native(js->current_func, DS_Z_NATIVE_TAG);
297 if (!st)
298 return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid DecompressionStream");
299
300 st->strm.next_in = (Bytef *)input;
301 st->strm.avail_in = (uInt)input_len;
302
303 uint8_t out_buf[ZCHUNK_SIZE];
304 do {
305 st->strm.next_out = out_buf;
306 st->strm.avail_out = ZCHUNK_SIZE;
307 int ret = inflate(&st->strm, Z_NO_FLUSH);
308 if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
309 return js_mkerr_typed(js, JS_ERR_TYPE, "Decompression failed");
310 size_t have = ZCHUNK_SIZE - st->strm.avail_out;
311 if (have > 0) {
312 ant_value_t r = enqueue_buffer(js, ctrl_obj, out_buf, have);
313 if (is_err(r)) return r;
314 }
315 if (ret == Z_STREAM_END) break;
316 } while (st->strm.avail_out == 0);
317
318 return js_mkundef();
319}
320
321static ant_value_t ds_flush(ant_t *js, ant_value_t *args, int nargs) {
322 ant_value_t ctrl_obj = (nargs > 0) ? args[0] : js_mkundef();
323 brotli_stream_state_t *st = (brotli_stream_state_t *)js_get_native(js->current_func, DS_BROTLI_NATIVE_TAG);
324 if (st)
325 return brotli_stream_flush(js, st, ctrl_obj);
326 return js_mkundef();
327}
328
329static ant_value_t js_ds_get_readable(ant_t *js, ant_value_t *args, int nargs) {
330 ant_value_t ts_obj = get_ts(js->this_val);
331 if (!ts_is_stream(ts_obj)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid DecompressionStream");
332 return ts_stream_readable(ts_obj);
333}
334
335static ant_value_t js_ds_get_writable(ant_t *js, ant_value_t *args, int nargs) {
336 ant_value_t ts_obj = get_ts(js->this_val);
337 if (!ts_is_stream(ts_obj)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid DecompressionStream");
338 return ts_stream_writable(ts_obj);
339}
340
341static ant_value_t js_ds_ctor(ant_t *js, ant_value_t *args, int nargs) {
342 if (vtype(js->new_target) == T_UNDEF)
343 return js_mkerr_typed(js, JS_ERR_TYPE, "DecompressionStream constructor requires 'new'");
344
345 if (nargs < 1)
346 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'DecompressionStream': 1 argument required");
347
348 zformat_t fmt;
349 if (parse_format(js, args[0], &fmt) < 0)
350 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'DecompressionStream': Unsupported compression format");
351
352 zstate_t *st = calloc(1, sizeof(zstate_t));
353 brotli_stream_state_t *brotli = NULL;
354 if (fmt == ZFMT_BROTLI) {
355 brotli = brotli_stream_state_new(true);
356 if (!brotli) return js_mkerr(js, "Failed to initialize decompression");
357 } else {
358 if (!st) return js_mkerr(js, "out of memory");
359 st->format = fmt;
360 int ret = inflateInit2(&st->strm, zfmt_window_bits(fmt, true));
361 if (ret != Z_OK) { free(st); return js_mkerr(js, "Failed to initialize decompression"); }
362 st->initialized = true;
363 }
364
365 ant_value_t obj = js_mkobj(js);
366 ant_value_t proto = js_instance_proto_from_new_target(js, g_ds_proto);
367 if (is_object_type(proto)) js_set_proto_init(obj, proto);
368
369 js_set_native(obj, fmt == ZFMT_BROTLI ? (void *)brotli : (void *)st, fmt == ZFMT_BROTLI ? DS_BROTLI_NATIVE_TAG : DS_Z_NATIVE_TAG);
370 js_set_finalizer(obj, fmt == ZFMT_BROTLI ? brotli_state_finalize : zstate_inflate_finalize);
371
372 ant_value_t transformer = js_mkobj(js);
373
374 ant_value_t transform_fn = js_heavy_mkfun_native(js, ds_transform, fmt == ZFMT_BROTLI
375 ? (void *)brotli : (void *)st, fmt == ZFMT_BROTLI
376 ? DS_BROTLI_NATIVE_TAG : DS_Z_NATIVE_TAG
377 );
378
379 ant_value_t flush_fn = js_heavy_mkfun_native(js, ds_flush, fmt == ZFMT_BROTLI
380 ? (void *)brotli : (void *)st, fmt == ZFMT_BROTLI
381 ? DS_BROTLI_NATIVE_TAG : DS_Z_NATIVE_TAG
382 );
383
384 js_set(js, transformer, "transform", transform_fn);
385 js_set(js, transformer, "flush", flush_fn);
386
387 ant_value_t ctor_args[1] = { transformer };
388 ant_value_t saved_new_target = js->new_target;
389 ant_value_t saved_this = js->this_val;
390 js->new_target = js_mknum(1);
391
392 ant_value_t ts_obj = js_ts_ctor(js, ctor_args, 1);
393
394 js->new_target = saved_new_target;
395 js->this_val = saved_this;
396 if (is_err(ts_obj)) {
397 if (fmt == ZFMT_BROTLI) brotli_stream_state_destroy(brotli);
398 else { inflateEnd(&st->strm); free(st); }
399 return ts_obj;
400 }
401
402 js_set_slot(obj, SLOT_ENTRIES, ts_obj);
403 js_set_slot_wb(js, transform_fn, SLOT_ENTRIES, obj);
404 js_set_slot_wb(js, flush_fn, SLOT_ENTRIES, obj);
405 return obj;
406}
407
408void init_compression_stream_module(void) {
409 ant_t *js = rt->js;
410 ant_value_t g = js_glob(js);
411
412 g_cs_proto = js_mkobj(js);
413 js_set_getter_desc(js, g_cs_proto, "readable", 8, js_mkfun(js_cs_get_readable), JS_DESC_C);
414 js_set_getter_desc(js, g_cs_proto, "writable", 8, js_mkfun(js_cs_get_writable), JS_DESC_C);
415 js_set_sym(js, g_cs_proto, get_toStringTag_sym(), js_mkstr(js, "CompressionStream", 17));
416
417 ant_value_t cs_ctor = js_make_ctor(js, js_cs_ctor, g_cs_proto, "CompressionStream", 17);
418 js_set(js, g, "CompressionStream", cs_ctor);
419 js_set_descriptor(js, g, "CompressionStream", 17, JS_DESC_W | JS_DESC_C);
420
421 g_ds_proto = js_mkobj(js);
422 js_set_getter_desc(js, g_ds_proto, "readable", 8, js_mkfun(js_ds_get_readable), JS_DESC_C);
423 js_set_getter_desc(js, g_ds_proto, "writable", 8, js_mkfun(js_ds_get_writable), JS_DESC_C);
424 js_set_sym(js, g_ds_proto, get_toStringTag_sym(), js_mkstr(js, "DecompressionStream", 19));
425
426 ant_value_t ds_ctor = js_make_ctor(js, js_ds_ctor, g_ds_proto, "DecompressionStream", 19);
427 js_set(js, g, "DecompressionStream", ds_ctor);
428 js_set_descriptor(js, g, "DecompressionStream", 19, JS_DESC_W | JS_DESC_C);
429}
430
431void gc_mark_compression_streams(ant_t *js, void (*mark)(ant_t *, ant_value_t)) {
432 mark(js, g_cs_proto);
433 mark(js, g_ds_proto);
434}