MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <stdlib.h>
2#include <string.h>
3
4#include <brotli/encode.h>
5#include <brotli/decode.h>
6
7#include "ant.h"
8#include "errors.h"
9#include "internal.h"
10#include "modules/buffer.h"
11#include "streams/brotli.h"
12#include "streams/transform.h"
13
14#define BROTLI_CHUNK_SIZE (32 * 1024)
15
16struct brotli_stream_state {
17 bool decompress;
18 union {
19 BrotliEncoderState *enc;
20 BrotliDecoderState *dec;
21 } u;
22};
23
24static int brotli_emit_chunk(
25 brotli_stream_chunk_cb cb, void *ctx,
26 const uint8_t *data, size_t len
27) {
28 if (!cb || len == 0) return 0;
29 return cb(ctx, data, len);
30}
31
32static ant_value_t brotli_enqueue_buffer(
33 ant_t *js, ant_value_t ctrl_obj, const uint8_t *data, size_t len
34) {
35 ArrayBufferData *ab = create_array_buffer_data(len);
36 if (!ab) return js_mkerr(js, "out of memory");
37 memcpy(ab->data, data, len);
38 ant_value_t arr = create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, len, "Uint8Array");
39 if (!ts_is_controller(ctrl_obj))
40 return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TransformStreamDefaultController");
41 return ts_ctrl_enqueue(js, ctrl_obj, arr);
42}
43
44static int brotli_encoder_step(
45 brotli_stream_state_t *st,
46 const uint8_t *input,
47 size_t input_len,
48 BrotliEncoderOperation op,
49 brotli_stream_chunk_cb cb,
50 void *ctx
51) {
52 size_t avail_in = input_len;
53 const uint8_t *next_in = input;
54
55 for (;;) {
56 uint8_t out_buf[BROTLI_CHUNK_SIZE];
57 uint8_t *next_out = out_buf;
58 size_t avail_out = sizeof(out_buf);
59
60 if (!BrotliEncoderCompressStream(
61 st->u.enc, op, &avail_in, &next_in, &avail_out, &next_out, NULL)) {
62 return -1;
63 }
64
65 size_t have = sizeof(out_buf) - avail_out;
66 if (brotli_emit_chunk(cb, ctx, out_buf, have) != 0) return -1;
67
68 if (op == BROTLI_OPERATION_FINISH) {
69 if (BrotliEncoderIsFinished(st->u.enc) &&
70 !BrotliEncoderHasMoreOutput(st->u.enc))
71 break;
72 } else if (avail_in == 0 && !BrotliEncoderHasMoreOutput(st->u.enc)) break;
73 }
74
75 return 0;
76}
77
78static int brotli_decoder_step(
79 brotli_stream_state_t *st,
80 const uint8_t *input,
81 size_t input_len,
82 brotli_stream_chunk_cb cb,
83 void *ctx
84) {
85 size_t avail_in = input_len;
86 const uint8_t *next_in = input;
87
88 for (;;) {
89 uint8_t out_buf[BROTLI_CHUNK_SIZE];
90 uint8_t *next_out = out_buf;
91 size_t avail_out = sizeof(out_buf);
92
93 BrotliDecoderResult ret = BrotliDecoderDecompressStream(
94 st->u.dec, &avail_in, &next_in, &avail_out, &next_out, NULL);
95
96 size_t have = sizeof(out_buf) - avail_out;
97 if (brotli_emit_chunk(cb, ctx, out_buf, have) != 0) return -1;
98
99 if (ret == BROTLI_DECODER_RESULT_ERROR) return -1;
100 if (ret == BROTLI_DECODER_RESULT_SUCCESS) break;
101 if (ret == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT ||
102 BrotliDecoderHasMoreOutput(st->u.dec)) continue;
103 if (ret == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) break;
104 }
105
106 return 0;
107}
108
109brotli_stream_state_t *brotli_stream_state_new(bool decompress) {
110 brotli_stream_state_t *st = calloc(1, sizeof(*st));
111 if (!st) return NULL;
112
113 st->decompress = decompress;
114 if (decompress) {
115 st->u.dec = BrotliDecoderCreateInstance(NULL, NULL, NULL);
116 if (!st->u.dec) {
117 free(st);
118 return NULL;
119 }} else {
120 st->u.enc = BrotliEncoderCreateInstance(NULL, NULL, NULL);
121 if (!st->u.enc) {
122 free(st);
123 return NULL;
124 }}
125
126 return st;
127}
128
129void brotli_stream_state_destroy(brotli_stream_state_t *st) {
130 if (!st) return;
131 if (st->decompress) BrotliDecoderDestroyInstance(st->u.dec);
132 else BrotliEncoderDestroyInstance(st->u.enc);
133 free(st);
134}
135
136int brotli_stream_process(
137 brotli_stream_state_t *st,
138 const uint8_t *input,
139 size_t input_len,
140 brotli_stream_chunk_cb cb,
141 void *ctx
142) {
143 if (!st) return -1;
144 if (st->decompress) return brotli_decoder_step(st, input, input_len, cb, ctx);
145 return brotli_encoder_step(st, input, input_len, BROTLI_OPERATION_PROCESS, cb, ctx);
146}
147
148int brotli_stream_finish(
149 brotli_stream_state_t *st,
150 brotli_stream_chunk_cb cb,
151 void *ctx
152) {
153 if (!st) return -1;
154
155 if (st->decompress) {
156 if (brotli_decoder_step(st, NULL, 0, cb, ctx) != 0) return -1;
157 return BrotliDecoderIsFinished(st->u.dec) ? 0 : -1;
158 }
159
160 return brotli_encoder_step(st, NULL, 0, BROTLI_OPERATION_FINISH, cb, ctx);
161}
162
163bool brotli_stream_is_finished(brotli_stream_state_t *st) {
164 if (!st) return false;
165 if (st->decompress) return BrotliDecoderIsFinished(st->u.dec);
166 return BrotliEncoderIsFinished(st->u.enc);
167}
168
169typedef struct {
170 ant_t *js;
171 ant_value_t ctrl_obj;
172 ant_value_t error;
173} brotli_js_emit_ctx_t;
174
175static int brotli_enqueue_chunk_cb(void *ctx, const uint8_t *chunk, size_t len) {
176 brotli_js_emit_ctx_t *emit = (brotli_js_emit_ctx_t *)ctx;
177 ant_value_t result = brotli_enqueue_buffer(emit->js, emit->ctrl_obj, chunk, len);
178 if (is_err(result)) {
179 emit->error = result;
180 return -1;
181 }
182 return 0;
183}
184
185ant_value_t brotli_stream_transform(
186 ant_t *js, brotli_stream_state_t *st,
187 ant_value_t ctrl_obj, const uint8_t *input, size_t input_len
188) {
189 brotli_js_emit_ctx_t emit = { .js = js, .ctrl_obj = ctrl_obj, .error = js_mkundef() };
190 if (brotli_stream_process(st, input, input_len, brotli_enqueue_chunk_cb, &emit) != 0) {
191 if (is_err(emit.error)) return emit.error;
192 return js_mkerr_typed(js, JS_ERR_TYPE, st && st->decompress ? "Decompression failed" : "Compression failed");
193 }
194 return js_mkundef();
195}
196
197ant_value_t brotli_stream_flush(
198 ant_t *js, brotli_stream_state_t *st,
199 ant_value_t ctrl_obj
200) {
201 brotli_js_emit_ctx_t emit = { .js = js, .ctrl_obj = ctrl_obj, .error = js_mkundef() };
202 if (brotli_stream_finish(st, brotli_enqueue_chunk_cb, &emit) != 0) {
203 if (is_err(emit.error)) return emit.error;
204 return js_mkerr_typed(js, JS_ERR_TYPE, st && st->decompress ? "Decompression failed" : "Compression failed");
205 }
206 return js_mkundef();
207}