Files
scriptc/packages/runtime/src/scr_zlib_stream.c
T

680 lines
25 KiB
C

/* node:zlib's stream classes — Deflate, Inflate, Gzip, Gunzip, DeflateRaw,
* InflateRaw and Unzip (createDeflate() et al) — as runtime Transform
* streams over the scr_zlib.c engine. Linked only when the program uses
* both zlib and the stream surface (the scr_util_style.c pairing rule), so
* zlib's one-shot users keep their link line.
*
* The write path follows lib/zlib.js: every chunk runs as one or more
* engine steps on the shared worker pool (Node's threadpool write), each
* step filling the chunkSize output buffer from the current offset; a
* non-empty step pushes one 'data' chunk, a full buffer starts the next
* step (after a _read when push() answered false), and the transform
* callback runs once the input is consumed. The last chunk written after
* end() runs with finishFlush, the 'prefinish' flush writes an empty chunk
* with it, and flush() writes a zero-length sentinel chunk carrying its
* flush kind. Engine errors destroy the stream with Node's Error (message,
* errno and code). */
#include "scr_runtime.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* zlib.h values (the engine unit owns the zlib include). */
enum { SCR_ZF_NO_FLUSH, SCR_ZF_PARTIAL, SCR_ZF_SYNC, SCR_ZF_FULL, SCR_ZF_FINISH, SCR_ZF_BLOCK };
enum { SCR_ZM_DEFLATE = 1, SCR_ZM_INFLATE, SCR_ZM_GZIP, SCR_ZM_GUNZIP, SCR_ZM_DEFLATERAW,
SCR_ZM_INFLATERAW, SCR_ZM_UNZIP };
typedef struct ScrZlibStream {
size_t rc;
ScrZlibEngine *eng; /* freed at close (deferred while a step runs) */
bool closed; /* Node's `_handle === null` */
bool pending_close;
bool write_in_progress;
ScrStream *s; /* borrowed: the stream owns this state through its hooks */
int mode;
uint32_t chunk_size;
int default_flush, finish_flush, full_flush;
double level, strategy; /* Node's _level/_strategy (params() compares them) */
bool reject_garbage;
double bytes_written;
/* _outBuffer/_outOffset: steps write at out_offset, pushes copy slices */
unsigned char *out_buf;
uint32_t out_offset;
/* the chunk in flight (handle.buffer/inOff/availInBefore/availOutBefore) */
unsigned char *in_buf;
uint32_t in_off, avail_in_before, avail_out_before;
int flush_flag;
bool flushing; /* the 'prefinish' flush: completion is flush_done */
bool wait_read; /* push() answered false with output pending */
bool done_pending; /* Transform's kCallback: completion waits for _read */
double rlen_before; /* readableLength when the chunk's transform began */
ScrBytes *flush_bufs[6]; /* zero-length sentinels, one per flush kind */
/* The AsyncLocalStorage context of the stream's creation (+1, NULL
* outside a store): Node runs the handle's step callbacks in it. */
ScrAlsCtx *als;
} ScrZlibStream;
static void scr_zlib_stream_oom(void) {
scr_trap("scriptc: out of memory\n");
}
static void *scr_zs_retain_v(void *p) {
((ScrZlibStream *)p)->rc++;
return p;
}
static void scr_zs_close_engine(ScrZlibStream *zs) {
if (zs->write_in_progress) {
zs->pending_close = true;
return;
}
zs->pending_close = false;
scr_zlib_engine_free(zs->eng);
zs->eng = NULL;
}
static void scr_zs_release_v(void *p) {
ScrZlibStream *zs = p;
if (--zs->rc > 0) return;
scr_zlib_engine_free(zs->eng);
free(zs->out_buf);
free(zs->in_buf);
for (size_t i = 0; i < 6; i++) {
if (zs->flush_bufs[i]) scr_bytes_release(zs->flush_bufs[i]);
}
scr_als_ctx_release(zs->als);
free(zs);
}
static ScrClosure *scr_zs_hook(ScrZlibStream *zs, void *fn) {
ScrClosure *cb = scr_closure_new(fn, 1);
cb->caps[0] = scr_box_new_obj(&scr_zs_retain_v, &scr_zs_release_v, NULL);
scr_box_set_ref(cb->caps[0], scr_zs_retain_v(zs));
return cb;
}
static ScrZlibStream *scr_zs_of(ScrClosure *cb) {
ScrZlibStream *zs = scr_box_get_ref(cb->caps[0]);
scr_zs_release_v(zs); /* the stream's hooks keep it alive */
return zs;
}
/* ── completion ───────────────────────────────────────────────────────── */
static void scr_zs_finish(ScrZlibStream *zs, ScrStream *s) {
if (zs->flushing) {
zs->flushing = false;
scr_stream_flush_done(s, NULL, NULL, NULL);
} else {
/* scr_zs_complete has applied Transform._write's callback rule. */
scr_stream_write_done(s, NULL);
}
}
static void scr_zs_tick_finish(ScrClosure *cb) {
ScrStream *s = scr_box_get_ref(cb->caps[0]);
ScrZlibStream *zs = scr_box_get_ref(cb->caps[1]);
scr_zs_finish(zs, s);
scr_zs_release_v(zs);
scr_stream_release(s);
}
static void scr_zs_finish_on_tick(ScrZlibStream *zs, ScrStream *s) {
ScrClosure *cb = scr_closure_new((void *)&scr_zs_tick_finish, 2);
cb->caps[0] = scr_box_new_obj(&scr_stream_retain_v, &scr_stream_release_v, &scr_stream_trace);
scr_box_set_ref(cb->caps[0], scr_stream_retain(s));
cb->caps[1] = scr_box_new_obj(&scr_zs_retain_v, &scr_zs_release_v, NULL);
scr_box_set_ref(cb->caps[1], scr_zs_retain_v(zs));
scr_next_tick(cb);
}
/* Transform._write's callback: a write that leaves the readable buffer
* grown past its high-water mark completes at the next _read; a readable
* side that already ended completes on a tick. The flush completes at
* once. */
static void scr_zs_complete(ScrZlibStream *zs, ScrStream *s) {
if (zs->flushing) {
scr_zs_finish(zs, s);
return;
}
if (scr_stream_prop(s, "rs:ended")) {
scr_zs_finish_on_tick(zs, s);
return;
}
double rlen = scr_stream_prop(s, "readableLength");
if (scr_stream_prop(s, "writableEnded") || rlen == zs->rlen_before ||
rlen < scr_stream_prop(s, "readableHighWaterMark")) {
scr_zs_finish(zs, s);
return;
}
zs->done_pending = true;
}
/* ── the engine steps ─────────────────────────────────────────────────── */
typedef struct {
ScrZlibStream *zs;
ScrStream *s;
} ScrZlibStep;
static void scr_zs_submit(ScrZlibStream *zs, ScrStream *s);
static void scr_zs_step_work(void *payload) {
ScrZlibStep *op = payload;
ScrZlibStream *zs = op->zs;
scr_zlib_engine_work(zs->eng, zs->flush_flag, zs->in_buf + zs->in_off, zs->avail_in_before,
zs->out_buf + zs->out_offset, zs->avail_out_before);
}
static ScrError *scr_zs_engine_error(const ScrZlibError *ze) {
ScrStr *message = scr_str_new(ze->message, strlen(ze->message));
ScrError *err = scr_error_new(SCR_ERR_ERROR, message);
scr_str_release(message);
err->system_errno = ze->err;
scr_error_set_code(err, ze->code);
return err;
}
static void scr_zs_destroy_with(ScrStream *s, ScrError *err /*moves*/) {
scr_stream_release(scr_stream_destroy(s, err));
scr_error_release(err);
}
/* processCallback: account the step, push its output, then either run
* the next step (the output buffer filled) or complete the chunk. */
static void scr_zs_process(ScrZlibStream *zs, ScrStream *s) {
if (scr_stream_prop(s, "destroyed")) {
scr_zs_finish(zs, s);
return;
}
uint32_t avail_in_after, avail_out_after;
scr_zlib_engine_offsets(zs->eng, &avail_in_after, &avail_out_after);
uint32_t in_delta = zs->avail_in_before - avail_in_after;
zs->bytes_written += in_delta;
uint32_t have = zs->avail_out_before - avail_out_after;
bool full = false;
if (have > 0) {
ScrBytes *out = scr_bytes_new(SCR_BYTES_U8, (double)have);
out->is_buffer = true; /* Node pushes Buffer slices of its output buffer */
memcpy(out->data, zs->out_buf + zs->out_offset, have);
zs->out_offset += have;
full = !scr_stream_push(s, out);
scr_bytes_release(out);
if (scr_exc_pending()) return;
}
if (scr_stream_prop(s, "destroyed")) {
scr_zs_finish(zs, s);
return;
}
if (avail_out_after == 0 || zs->out_offset >= zs->chunk_size) {
zs->avail_out_before = zs->chunk_size;
zs->out_offset = 0;
}
if (avail_out_after == 0) {
zs->in_off += in_delta;
zs->avail_in_before = avail_in_after;
if (full) zs->wait_read = true;
else scr_zs_submit(zs, s);
return;
}
if (avail_in_after > 0) {
/* The decoder stopped before the input did (trailing data). */
if (zs->reject_garbage) {
static const char msg[] = "Trailing junk found after the end of the compressed stream";
ScrStr *m = scr_str_new(msg, sizeof msg - 1);
ScrError *err = scr_error_new(SCR_ERR_TYPE, m);
scr_str_release(m);
scr_error_set_code(err, "ERR_TRAILING_JUNK_AFTER_STREAM_END");
scr_stream_release(scr_stream_destroy(s, err));
if (zs->flushing) {
zs->flushing = false;
scr_stream_flush_done(s, err, NULL, NULL);
} else {
scr_stream_transform_done(s, err, NULL, NULL);
}
return;
}
scr_stream_push_null(s);
if (scr_exc_pending()) return;
}
scr_zs_complete(zs, s);
}
static void scr_zs_step_after(void *payload) {
ScrZlibStep *op = payload;
ScrZlibStream *zs = op->zs;
ScrStream *s = op->s;
ScrAlsCtx *prev = scr_als_dispatch_enter(zs->als);
zs->write_in_progress = false;
ScrZlibError ze;
if (scr_zlib_engine_error(zs->eng, &ze)) {
ScrError *err = scr_zs_engine_error(&ze);
zs->closed = true;
scr_zs_destroy_with(s, err);
} else {
scr_zs_process(zs, s);
}
if (zs->pending_close) scr_zs_close_engine(zs);
scr_als_dispatch_leave(prev);
}
static void scr_zs_step_destroy(void *payload) {
ScrZlibStep *op = payload;
scr_zs_release_v(op->zs);
scr_stream_release(op->s);
free(op);
}
static void scr_zs_submit(ScrZlibStream *zs, ScrStream *s) {
ScrZlibStep *op = malloc(sizeof *op);
if (!op) scr_zlib_stream_oom();
op->zs = scr_zs_retain_v(zs);
op->s = scr_stream_retain(s);
zs->write_in_progress = true;
scr_work_submit(op, &scr_zs_step_work, &scr_zs_step_after, &scr_zs_step_destroy);
}
/* processChunk: a closed handle completes on a tick; otherwise the chunk
* is copied (the worker reads it) and its first step starts. */
static void scr_zs_process_chunk(ScrZlibStream *zs, ScrStream *s, const ScrBytes *chunk, int flag) {
if (zs->closed) {
scr_zs_finish_on_tick(zs, s);
return;
}
size_t len = chunk->len;
free(zs->in_buf);
zs->in_buf = malloc(len ? len : 1);
if (!zs->in_buf) scr_zlib_stream_oom();
if (len) scr_bytes_read(chunk, 0, zs->in_buf, len);
zs->in_off = 0;
zs->avail_in_before = (uint32_t)len;
zs->avail_out_before = zs->chunk_size - zs->out_offset;
zs->flush_flag = flag;
zs->rlen_before = scr_stream_prop(s, "readableLength");
scr_zs_submit(zs, s);
}
/* Node's flushiness order: NO_FLUSH < BLOCK < PARTIAL < SYNC < FULL < FINISH. */
static int scr_zs_flushiness(int f) {
static const int rank[6] = { 0, 2, 3, 4, 5, 1 };
return f >= 0 && f < 6 ? rank[f] : 0;
}
static int scr_zs_max_flush(int a, int b) {
return scr_zs_flushiness(a) > scr_zs_flushiness(b) ? a : b;
}
static void scr_zs_transform_inv(ScrClosure *cb, ScrStream *s, ScrBytes *chunk) {
ScrZlibStream *zs = scr_zs_of(cb);
int flag = zs->default_flush;
for (int k = 0; k < 6; k++) {
if (zs->flush_bufs[k] != NULL && chunk == zs->flush_bufs[k]) flag = k;
}
if (scr_stream_prop(s, "writableEnded") &&
scr_stream_prop(s, "writableLength") == (double)chunk->len) {
flag = scr_zs_max_flush(flag, zs->finish_flush);
}
zs->flushing = false;
scr_zs_process_chunk(zs, s, chunk, flag);
}
/* _flush (run from the Transform's 'prefinish' listener): an empty chunk
* through _transform — the stream has ended with nothing buffered, so the
* finish flush applies. */
static void scr_zs_flush_inv(ScrClosure *cb, ScrStream *s) {
ScrZlibStream *zs = scr_zs_of(cb);
ScrBytes *empty = scr_bytes_new(SCR_BYTES_U8, 0);
zs->flushing = true;
scr_zs_process_chunk(zs, s, empty, scr_zs_max_flush(zs->default_flush, zs->finish_flush));
scr_bytes_release(empty);
}
/* _read: the step a full push() paused resumes; a completion that waited
* on the readable buffer (Transform's kCallback) runs. */
static void scr_zs_read_inv(ScrClosure *cb, ScrStream *s, double size) {
(void)size;
ScrZlibStream *zs = scr_zs_of(cb);
if (zs->wait_read) {
zs->wait_read = false;
scr_zs_submit(zs, s);
}
if (zs->done_pending) {
zs->done_pending = false;
scr_zs_finish(zs, s);
}
}
static void scr_zs_destroy_inv(ScrClosure *cb, ScrStream *s, ScrError *err) {
ScrZlibStream *zs = scr_zs_of(cb);
zs->closed = true;
scr_zs_close_engine(zs);
scr_stream_destroy_done(s, err ? scr_error_retain(err) : NULL);
}
/* ── option validation (lib/internal/validators.js) ───────────────────── */
static const ScrDyn *scr_zs_opt(const ScrDyn *opts, const char *key) {
if (opts == NULL || opts->kind != SCR_DYN_OBJ) return NULL;
return scr_dyn_obj_get((ScrDyn *)opts, key, strlen(key));
}
static bool scr_zs_is_null(const ScrDyn *v) {
return v == NULL || v->kind == SCR_DYN_UNDEF || v->kind == SCR_DYN_NULL;
}
/* getHighWaterMark (lib/internal/streams/state.js): highWaterMark wins
* over the sided key; a non-integer or negative value throws with the
* sided name. -1 = the default. */
static bool scr_zs_hwm_value(const ScrDyn *v, const char *name, double *out) {
if (v->kind != SCR_DYN_NUM || !isfinite(v->v.num) || trunc(v->v.num) != v->v.num ||
v->v.num < 0) {
scr_dyn_arg_value_fail(name, NULL, v);
return false;
}
*out = v->v.num;
return true;
}
static bool scr_zs_hwm(const ScrDyn *opts, const char *sided, const char *name, double *out) {
const ScrDyn *v = scr_zs_opt(opts, "highWaterMark");
if (scr_zs_is_null(v)) v = scr_zs_opt(opts, sided);
*out = -1;
if (scr_zs_is_null(v)) return true;
return scr_zs_hwm_value(v, name, out);
}
static bool scr_zs_false(const ScrDyn *v) {
return v != NULL && v->kind == SCR_DYN_BOOL && !v->v.b;
}
static bool scr_zs_unsupported(const char *msg) {
scr_throw_error_msg(SCR_ERR_ERROR, msg, strlen(msg));
return false;
}
/* The Transform options Node would consume with no lowering here. */
static bool scr_zs_check_unlowered(const ScrDyn *opts) {
if (opts == NULL || opts->kind != SCR_DYN_OBJ) return true;
const ScrDyn *v = scr_zs_opt(opts, "readableObjectMode");
if (v != NULL && scr_dyn_truthy(v))
return scr_zs_unsupported("objectMode streams are not supported yet (chunks are Buffers or utf8 strings)");
if (scr_zs_false(scr_zs_opt(opts, "decodeStrings")))
return scr_zs_unsupported("decodeStrings: false is not supported yet (chunks always decode to Buffers)");
v = scr_zs_opt(opts, "signal");
if (!scr_zs_is_null(v))
return scr_zs_unsupported("the stream option 'signal' is not supported yet (AbortSignal-driven destruction has no lowering)");
v = scr_zs_opt(opts, "defaultEncoding");
if (v != NULL && v->kind == SCR_DYN_STR &&
!(v->v.str->len == 4 && memcmp(v->v.str->data, "utf8", 4) == 0) &&
!(v->v.str->len == 5 && memcmp(v->v.str->data, "utf-8", 5) == 0))
return scr_zs_unsupported("the stream option 'defaultEncoding' is not supported yet (utf8, the default, is the write-side string encoding)");
static const char *const fns[] = { "construct", "read", "write", "writev", "final", "destroy",
"transform" };
for (size_t i = 0; i < sizeof fns / sizeof fns[0]; i++) {
v = scr_zs_opt(opts, fns[i]);
if (v != NULL && v->kind == SCR_DYN_FUNC) {
char msg[160];
snprintf(msg, sizeof msg,
"the zlib stream option '%s' is not supported yet (it would replace the zlib transform)",
fns[i]);
return scr_zs_unsupported(msg);
}
}
return true;
}
static const char *scr_zs_class_name(int mode) {
switch (mode) {
case SCR_ZM_DEFLATE: return "Deflate";
case SCR_ZM_INFLATE: return "Inflate";
case SCR_ZM_GZIP: return "Gzip";
case SCR_ZM_GUNZIP: return "Gunzip";
case SCR_ZM_DEFLATERAW: return "DeflateRaw";
case SCR_ZM_INFLATERAW: return "InflateRaw";
case 8: return "BrotliDecompress";
case 9: return "BrotliCompress";
default: return "Unzip";
}
}
ScrStream *scr_zlib_stream_new(double mode_d, ScrDyn *opts) {
/* A typed options object is read through its materialized view. */
if (opts != NULL && opts->kind == SCR_DYN_TYPED_REF) {
ScrDyn *view = scr_dyn_typed_ref_materialize(opts);
ScrStream *stream = scr_exc_pending() ? NULL : scr_zlib_stream_new(mode_d, view);
scr_dyn_release(view);
return stream;
}
int mode = (int)mode_d;
const ScrDyn *o = opts != NULL && scr_dyn_truthy(opts) ? opts : NULL;
ScrZlibOptions zo;
if (!scr_zlib_options_parse(mode, opts, &zo)) return NULL;
/* Transform(): the high-water marks, then the unlowered options. */
double rhwm = -1, whwm = -1;
bool hwm_ok = scr_zs_hwm(o, "readableHighWaterMark", "options.readableHighWaterMark", &rhwm);
if (hwm_ok && rhwm == 0) {
/* A zero readable mark: Transform drops highWaterMark and the writable
* side reads `writableHighWaterMark || 0`. */
const ScrDyn *w = scr_zs_opt(o, "writableHighWaterMark");
whwm = 0;
if (w != NULL && scr_dyn_truthy(w))
hwm_ok = scr_zs_hwm_value(w, "options.writableHighWaterMark", &whwm);
} else if (hwm_ok) {
hwm_ok = scr_zs_hwm(o, "writableHighWaterMark", "options.writableHighWaterMark", &whwm);
}
if (!hwm_ok || !scr_zs_check_unlowered(o)) {
scr_zlib_options_drop(&zo);
return NULL;
}
ScrZlibStream *zs = calloc(1, sizeof *zs);
if (!zs) scr_zlib_stream_oom();
zs->rc = 1;
zs->als = scr_als_capture();
zs->mode = mode;
zs->eng = scr_zlib_engine_from_options(mode, &zo);
scr_zlib_options_drop(&zo);
zs->chunk_size = (uint32_t)zo.chunk_size;
zs->out_buf = malloc(zs->chunk_size);
if (!zs->out_buf) scr_zlib_stream_oom();
zs->default_flush = (int)zo.flush;
zs->finish_flush = (int)zo.finish_flush;
/* Brotli's full flush is BROTLI_OPERATION_FLUSH (brotliDefaultOpts). */
zs->full_flush = mode == 8 || mode == 9 ? 1 : SCR_ZF_FULL;
zs->level = zo.level;
zs->strategy = zo.strategy;
zs->reject_garbage = zo.reject_garbage;
bool auto_destroy = !scr_zs_false(scr_zs_opt(o, "autoDestroy"));
bool emit_close = !scr_zs_false(scr_zs_opt(o, "emitClose"));
bool allow_half_open = !scr_zs_false(scr_zs_opt(o, "allowHalfOpen"));
bool readable_side = !scr_zs_false(scr_zs_opt(o, "readable"));
bool writable_side = !scr_zs_false(scr_zs_opt(o, "writable"));
ScrStream *s = scr_stream_new_transform(rhwm, whwm, auto_destroy, emit_close, allow_half_open,
readable_side, writable_side,
scr_zs_hook(zs, (void *)&scr_zs_transform_inv), &scr_zs_transform_inv,
scr_zs_hook(zs, (void *)&scr_zs_flush_inv), &scr_zs_flush_inv,
scr_zs_hook(zs, (void *)&scr_zs_destroy_inv), &scr_zs_destroy_inv);
scr_stream_set_read(s, scr_zs_hook(zs, (void *)&scr_zs_read_inv), &scr_zs_read_inv);
scr_stream_set_flush_on_prefinish(s);
s->cls = scr_zs_class_name(mode);
zs->s = s;
scr_stream_set_native(s, zs, &scr_zs_release_v); /* moves the creation reference */
return s;
}
/* ── the instance surface ─────────────────────────────────────────────── */
static ScrZlibStream *scr_zs_from_stream(ScrStream *s) {
ScrZlibStream *zs = scr_stream_native(s, &scr_zs_release_v);
if (zs == NULL) {
static const char msg[] = "the receiver is not a zlib stream";
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
}
return zs;
}
bool scr_zlib_stream_is(ScrStream *s, double mode) {
const ScrZlibStream *zs = scr_stream_native(s, &scr_zs_release_v);
return zs != NULL && zs->mode == (int)mode;
}
double scr_zlib_stream_bytes_written(ScrStream *s) {
ScrZlibStream *zs = scr_zs_from_stream(s);
return zs ? zs->bytes_written : 0;
}
static void scr_zs_call0(ScrClosure *cb) {
((void (*)(ScrClosure *))cb->fn)(cb);
}
static void scr_zs_end_inv(ScrClosure *cb, va_list ap) {
(void)ap;
scr_zs_call0(cb);
}
/* flush(kind?, callback?) — kind arrives undefined when absent; the
* frontend has already moved a function first argument into cb. */
void scr_zlib_stream_flush(ScrStream *s, ScrDyn *kind, ScrClosure *cb) {
ScrZlibStream *zs = scr_zs_from_stream(s);
if (!zs) {
if (cb) scr_closure_release(cb);
return;
}
double k;
if (!scr_zlib_check_range(kind, "kind", false, 0, 5, zs->full_flush, &k)) {
if (cb) scr_closure_release(cb);
return;
}
if (scr_stream_prop(s, "writableFinished")) {
if (cb) scr_next_tick(cb);
return;
}
if (scr_stream_prop(s, "writableEnded")) {
if (cb) {
ScrStr *name = scr_str_new("end", 3);
scr_emitter_release(scr_emitter_on((ScrEmitter *)s, name, cb, &scr_zs_end_inv, true, false));
scr_str_release(name);
}
return;
}
int ki = (int)k;
if (zs->flush_bufs[ki] == NULL) {
/* a Buffer, as Node's kFlushBuffers are: write() passes a Buffer
* through unchanged, so _transform recognizes the sentinel */
zs->flush_bufs[ki] = scr_bytes_new(SCR_BYTES_U8, 0);
zs->flush_bufs[ki]->is_buffer = true;
}
scr_stream_write(s, zs->flush_bufs[ki], cb);
}
/* params(level, strategy, callback): a sync flush first when either
* changes, then the engine's deflateParams; the callback runs unless the
* stream was destroyed meanwhile. */
static void scr_zs_params_after(ScrClosure *cb) {
ScrStream *s = scr_box_get_ref(cb->caps[0]);
double level = scr_box_get_f64(cb->caps[1]);
double strategy = scr_box_get_f64(cb->caps[2]);
ScrClosure *user = cb->ncaps > 3 ? scr_box_get_ref(cb->caps[3]) : NULL;
ScrZlibStream *zs = scr_zs_from_stream(s);
if (zs && zs->closed) {
static const char msg[] =
"zlib binding closed\nThis is caused by either a bug in Node.js or incorrect usage of "
"Node.js internals.\nPlease open an issue with this stack trace at "
"https://github.com/nodejs/node/issues\n";
scr_throw_error_msg_code(SCR_ERR_ERROR, msg, sizeof msg - 1, "ERR_INTERNAL_ASSERTION");
} else if (zs) {
ScrZlibError ze;
if (scr_zlib_engine_params(zs->eng, isnan(level) ? 0 : (int)level,
isnan(strategy) ? 0 : (int)strategy, &ze)) {
zs->closed = true;
scr_zs_destroy_with(s, scr_zs_engine_error(&ze));
}
if (!scr_stream_prop(s, "destroyed")) {
zs->level = level;
zs->strategy = strategy;
if (user) scr_zs_call0(user);
}
}
if (user) scr_closure_release(user);
scr_stream_release(s);
}
void scr_zlib_stream_params(ScrStream *s, ScrDyn *level_d, ScrDyn *strategy_d, ScrClosure *cb) {
ScrZlibStream *zs = scr_zs_from_stream(s);
double level, strategy;
if (!zs || !scr_zlib_check_range(level_d, "level", false, -1, 9, NAN, &level) ||
!scr_zlib_check_range(strategy_d, "strategy", false, 0, 4, NAN, &strategy)) {
if (cb) scr_closure_release(cb);
return;
}
bool same_level = level == zs->level || (isnan(level) && isnan(zs->level));
bool same_strategy = strategy == zs->strategy || (isnan(strategy) && isnan(zs->strategy));
if (same_level && same_strategy) {
if (cb == NULL) {
ScrDyn *undef = scr_dyn_undefined();
scr_dyn_arg_type_fail("callback", "of type function", undef);
return;
}
scr_next_tick(cb);
return;
}
/* caps: the stream, level, strategy, then the callback when given */
ScrClosure *after = scr_closure_new((void *)&scr_zs_params_after, cb ? 4 : 3);
after->caps[0] = scr_box_new_obj(&scr_stream_retain_v, &scr_stream_release_v, &scr_stream_trace);
scr_box_set_ref(after->caps[0], scr_stream_retain(s));
after->caps[1] = scr_box_new(SCR_BOX_F64);
scr_box_set_f64(after->caps[1], level);
after->caps[2] = scr_box_new(SCR_BOX_F64);
scr_box_set_f64(after->caps[2], strategy);
if (cb) {
after->caps[3] = scr_box_new(SCR_BOX_FUNC);
scr_box_set_ref(after->caps[3], cb);
}
ScrDyn *sync = scr_dyn_new_num(SCR_ZF_SYNC);
scr_zlib_stream_flush(s, sync, after);
scr_dyn_release(sync);
}
static void scr_zs_close_inv(ScrClosure *cb, ScrStream *s, ScrError *err) {
(void)s;
(void)err;
scr_zs_call0(cb);
}
/* close(callback?): finished(this, callback), then destroy(). */
void scr_zlib_stream_close(ScrStream *s, ScrClosure *cb) {
if (!scr_zs_from_stream(s)) {
if (cb) scr_closure_release(cb);
return;
}
if (cb) {
ScrClosure *cleanup = scr_stream_finished(s, cb, &scr_zs_close_inv);
if (cleanup) scr_closure_release(cleanup);
}
scr_stream_release(scr_stream_destroy(s, NULL));
}
void scr_zlib_stream_reset(ScrStream *s) {
ScrZlibStream *zs = scr_zs_from_stream(s);
if (!zs) return;
if (zs->closed) {
static const char msg[] =
"zlib binding closed\nThis is caused by either a bug in Node.js or incorrect usage of "
"Node.js internals.\nPlease open an issue with this stack trace at "
"https://github.com/nodejs/node/issues\n";
scr_throw_error_msg_code(SCR_ERR_ERROR, msg, sizeof msg - 1, "ERR_INTERNAL_ASSERTION");
return;
}
if (zs->write_in_progress) {
static const char msg[] = "Cannot reset zlib stream while a write is in progress";
scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
return;
}
ScrZlibError ze;
if (scr_zlib_engine_reset(zs->eng, &ze)) {
zs->closed = true;
scr_zs_destroy_with(s, scr_zs_engine_error(&ze));
}
}