/*
* Copyright (C) 2024 Thibaut VARÈNE <hacks@slashdirt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/**
* # Zlib bindings
*
* The `zlib` module provides single-call and stream-oriented functions for interacting with zlib data.
*
* @module zlib
*/
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <zlib.h>
#include "ucode/module.h"
#include "ucode/platform.h"
// https://zlib.net/zlib_how.html
/*
* CHUNK is simply the buffer size for feeding data to and pulling data from
* the zlib routines. Larger buffer sizes would be more efficient, especially
* for inflate(). If the memory is available, buffers sizes on the order of
* 128K or 256K bytes should be used.
*/
#define CHUNK 16384
static uc_resource_type_t *zstrmd_type, *zstrmi_type;
static int last_error = 0;
#define err_return(err) do { last_error = err; return NULL; } while(0)
typedef struct {
z_stream strm;
uc_stringbuf_t *outbuf;
int flush;
} zstrm_t;
/* zlib init error message */
static const char * ziniterr(int ret)
{
const char * msg;
switch (ret) {
case Z_ERRNO:
msg = strerror(errno);
break;
case Z_STREAM_ERROR: // can only happen for deflateInit2() by construction
msg = "invalid compression level";
break;
case Z_MEM_ERROR:
msg = "out of memory";
break;
case Z_VERSION_ERROR:
msg = "zlib version mismatch!";
break;
default:
msg = "unknown error";
break;
}
return msg;
}
static int
def_chunks(zstrm_t * const zstrm)
{
int ret;
/* run deflate() on input until output buffer not full */
do {
printbuf_memset(zstrm->outbuf, printbuf_length(zstrm->outbuf) + CHUNK - 1, 0, 1);
zstrm->outbuf->bpos -= CHUNK;
zstrm->strm.avail_out = CHUNK;
zstrm->strm.next_out = (unsigned char *)(zstrm->outbuf->buf + zstrm->outbuf->bpos);
ret = deflate(&zstrm->strm, zstrm->flush);
assert(ret != Z_STREAM_ERROR);
zstrm->outbuf->bpos += CHUNK - zstrm->strm.avail_out;
} while (zstrm->strm.avail_out == 0);
assert(zstrm->strm.avail_in == 0); // all input will be used
return ret;
}
static bool
uc_zlib_def_object(uc_vm_t *const vm, uc_value_t * const obj, zstrm_t * const zstrm)
{
int ret;
bool eof = false;
uc_value_t *rfn, *rbuf;
rfn = ucv_property_get(obj, "read");
if (!ucv_is_callable(rfn)) {
uc_vm_raise_exception(vm, EXCEPTION_TYPE,
"Input object does not implement read() method");
return false;
}
do {
rbuf = NULL;
uc_vm_stack_push(vm, ucv_get(obj));
uc_vm_stack_push(vm, ucv_get(rfn));
uc_vm_stack_push(vm, ucv_int64_new(CHUNK));
if (uc_vm_call(vm, true, 1) != EXCEPTION_NONE)
goto fail;
rbuf = uc_vm_stack_pop(vm); // read output chunk
/* we only accept strings */
if (rbuf != NULL && ucv_type(rbuf) != UC_STRING) {
uc_vm_raise_exception(vm, EXCEPTION_TYPE,
"Input object read() method returned non-string value");
goto fail;
}
/* check EOF */
eof = (rbuf == NULL || ucv_string_length(rbuf) == 0);
zstrm->strm.next_in = (unsigned char *)ucv_string_get(rbuf);
zstrm->strm.avail_in = ucv_string_length(rbuf);
zstrm->flush = eof ? Z_FINISH : Z_NO_FLUSH;
ret = def_chunks(zstrm);
(void)ret; // XXX make annoying compiler that ignores assert() happy
ucv_put(rbuf); // release rbuf
} while (!eof); // finish compression if all of source has been read in
assert(ret == Z_STREAM_END); // stream will be complete
return true;
fail:
ucv_put(rbuf);
return false;
}
static bool
uc_zlib_def_string(uc_vm_t * const vm, uc_value_t * const str, zstrm_t * const zstrm)
{
zstrm->strm.next_in = (unsigned char *)ucv_string_get(str);
zstrm->strm.avail_in = ucv_string_length(str);
last_error = def_chunks(zstrm);
return true;
}
/**
* Compresses data in Zlib or gzip format.
*
* If the input argument is a plain string, it is directly compressed.
*
* If an array, object or resource value is given, this function will attempt to
* invoke a `read()` method on it to read chunks of input text to incrementally
* compress. Reading will stop if the object's `read()` method returns
* either `null` or an empty string.
*
* Throws an exception on errors.
*
* Returns the compressed data.
*
* @function module:zlib#deflate
*
* @param {string} str_or_resource
* The string or resource object to be compressed.
*
* @param {?boolean} [gzip=false]
* Add a gzip header if true (creates a gzip-compliant output, otherwise defaults to Zlib)
*
* @param {?number} [level=Z_DEFAULT_COMPRESSION]
* The compression level (0-9).
*
* @returns {?string}
*
* @example
* // deflate content using default compression
* const deflated = deflate(content);
*
* // deflate content using fastest compression
* const deflated = deflate(content, Z_BEST_SPEED);
*/
static uc_value_t *
uc_zlib_deflate(uc_vm_t * const vm, const size_t nargs)
{
uc_value_t *rv = NULL;
uc_value_t *src = uc_fn_arg(0);
uc_value_t *gzip = uc_fn_arg(1);
uc_value_t *level = uc_fn_arg(2);
int ret, lvl = Z_DEFAULT_COMPRESSION;
bool success, gz = false;
zstrm_t zstrm = {
.strm = {
.zalloc = Z_NULL,
.zfree = Z_NULL,
.opaque = Z_NULL,
},
.outbuf = NULL,
.flush = Z_FINISH,
};
if (gzip) {
if (ucv_type(gzip) != UC_BOOLEAN) {
uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Passed gzip flag is not a boolean");
goto out;
}
gz = (int)ucv_boolean_get(gzip);
}
if (level) {
if (ucv_type(level) != UC_INTEGER) {
uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Passed level is not a number");
goto out;
}
lvl = (int)ucv_int64_get(level);
}
ret = deflateInit2(&zstrm.strm, lvl,
Z_DEFLATED, // only allowed method
gz ? 15+16 : 15, // 15 Zlib default, +16 for gzip
8, // default value
Z_DEFAULT_STRATEGY); // default value
if (ret != Z_OK) {
uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", ziniterr(ret));
goto out;
}
zstrm.outbuf = ucv_stringbuf_new();
switch (ucv_type(src)) {
case UC_STRING:
success = uc_zlib_def_string(vm, src, &zstrm);
break;
case UC_RESOURCE:
case UC_OBJECT:
case UC_ARRAY:
success = uc_zlib_def_object(vm, src, &zstrm);
break;
default:
uc_vm_raise_exception(vm, EXCEPTION_TYPE,
"Passed value is neither a string nor an object");
goto out;
}
if (!success) {
if (vm->exception.type == EXCEPTION_NONE) // do not clobber previous exception
uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", zstrm.strm.msg);
printbuf_free(zstrm.outbuf);
goto out;
}
rv = ucv_stringbuf_finish(zstrm.outbuf);
out:
(void)deflateEnd(&zstrm.strm);
return rv;
}
static int
inf_chunks(zstrm_t * const zstrm)
{
int ret;
/* run inflate() on input until output buffer not full */
do {
printbuf_memset(zstrm->outbuf, printbuf_length(zstrm->outbuf) + CHUNK - 1, 0, 1);
zstrm->outbuf->bpos -= CHUNK;
zstrm->strm.avail_out = CHUNK;
zstrm->strm.next_out = (unsigned char *)(zstrm->outbuf->buf + zstrm->outbuf->bpos);
ret = inflate(&zstrm->strm, zstrm->flush);
assert(ret != Z_STREAM_ERROR);
switch (ret) {
case Z_NEED_DICT:
case Z_DATA_ERROR:
case Z_MEM_ERROR:
return ret;
}
zstrm->outbuf->bpos += CHUNK - zstrm->strm.avail_out;
} while (zstrm->strm.avail_out == 0);
return ret;
}
static bool
uc_zlib_inf_object(uc_vm_t *const vm, uc_value_t * const obj, zstrm_t * const zstrm)
{
int ret = Z_STREAM_ERROR; // error out if EOF on first loop
bool eof = false;
uc_value_t *rfn, *rbuf;
rfn = ucv_property_get(obj, "read");
if (!ucv_is_callable(rfn)) {
uc_vm_raise_exception(vm, EXCEPTION_TYPE,
"Input object does not implement read() method");
return false;
}
do {
rbuf = NULL;
uc_vm_stack_push(vm, ucv_get(obj));
uc_vm_stack_push(vm, ucv_get(rfn));
uc_vm_stack_push(vm, ucv_int64_new(CHUNK));
if (uc_vm_call(vm, true, 1) != EXCEPTION_NONE)
goto fail;
rbuf = uc_vm_stack_pop(vm); // read output chunk
/* we only accept strings */
if (rbuf != NULL && ucv_type(rbuf) != UC_STRING) {
uc_vm_raise_exception(vm, EXCEPTION_TYPE,
"Input object read() method returned non-string value");
goto fail;
}
/* check EOF */
eof = (rbuf == NULL || ucv_string_length(rbuf) == 0);
if (eof)
break;
zstrm->strm.next_in = (unsigned char *)ucv_string_get(rbuf);
zstrm->strm.avail_in = ucv_string_length(rbuf);
ret = inf_chunks(zstrm);
switch (ret) {
case Z_NEED_DICT:
case Z_DATA_ERROR:
case Z_MEM_ERROR:
goto fail;
}
ucv_put(rbuf); // release rbuf
} while (ret != Z_STREAM_END); // done when inflate() says it's done
if (ret != Z_STREAM_END) // data error
return false;
return true;
fail:
ucv_put(rbuf);
return false;
}
static bool
uc_zlib_inf_string(uc_vm_t * const vm, uc_value_t * const str, zstrm_t * const zstrm)
{
int ret;
zstrm->strm.next_in = (unsigned char *)ucv_string_get(str);
zstrm->strm.avail_in = ucv_string_length(str);
ret = inf_chunks(zstrm);
assert(zstrm->strm.avail_in == 0);
last_error = ret;
return Z_STREAM_END == ret;
}
/**
* Decompresses data in Zlib or gzip format.
*
* If the input argument is a plain string, it is directly decompressed.
*
* If an array, object or resource value is given, this function will attempt to
* invoke a `read()` method on it to read chunks of input text to incrementally
* decompress. Reading will stop if the object's `read()` method returns
* either `null` or an empty string.
*
* Throws an exception on errors.
*
* Returns the decompressed data.
*
* @function module:zlib#inflate
*
* @param {string} str_or_resource
* The string or resource object to be parsed as JSON.
*
* @returns {?string}
*/
static uc_value_t *
uc_zlib_inflate(uc_vm_t * const vm, const size_t nargs)
{
uc_value_t *rv = NULL;
uc_value_t *src = uc_fn_arg(0);
bool success;
int ret;
zstrm_t zstrm = {
.strm = {
.zalloc = Z_NULL,
.zfree = Z_NULL,
.opaque = Z_NULL,
.avail_in = 0, // must be initialized before call to inflateInit
.next_in = Z_NULL, // must be initialized before call to inflateInit
},
.outbuf = NULL,
};
/* tell inflateInit2 to perform either zlib or gzip decompression: 15+32 */
ret = inflateInit2(&zstrm.strm, 15+32);
if (ret != Z_OK) {
uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", ziniterr(ret));
goto out;
}
zstrm.outbuf = ucv_stringbuf_new();
switch (ucv_type(src)) {
case UC_STRING:
zstrm.flush = Z_FINISH;
success = uc_zlib_inf_string(vm, src, &zstrm);
break;
case UC_RESOURCE:
case UC_OBJECT:
case UC_ARRAY:
zstrm.flush = Z_NO_FLUSH;
success = uc_zlib_inf_object(vm, src, &zstrm);
break;
default:
uc_vm_raise_exception(vm, EXCEPTION_TYPE,
"Passed value is neither a string nor an object");
goto out;
}
if (!success) {
if (vm->exception.type == EXCEPTION_NONE) // do not clobber previous exception
uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", zstrm.strm.msg);
printbuf_free(zstrm.outbuf);
goto out;
}
rv = ucv_stringbuf_finish(zstrm.outbuf);
out:
(void)inflateEnd(&zstrm.strm);
return rv;
}
/**
* Represents a handle for interacting with a deflate stream initiated by defnew().
*
* @class module:zlib.zstrmd
* @hideconstructor
*
* @see {@link module:zlib#defnew()}
*
* @example
*
* const zstrmd = defnew(…);
*
* for (let data = ...; data; data = ...) {
* zstrmd.write(data, Z_PARTIAL_FLUSH); // write uncompressed data to stream
* if (foo)
* let defl = zstrmd.read(); // read back compressed stream content
* }
*
* // terminate the stream if needed (for e.g. file output)
* zstrmd.write('', Z_FINISH);
* defl = ztrmd.read();
*
* zstrmd.error();
*/
/**
* Initializes a deflate stream.
*
* Returns a stream handle on success.
*
* Returns `null` if an error occurred.
*
* @function module:zlib#defnew
*
* @param {?boolean} [gzip=false]
* Add a gzip header if true (creates a gzip-compliant output, otherwise defaults to Zlib)
*
* @param {?number} [level=Z_DEFAULT_COMPRESSION]
* The compression level (0-9).
*
* @returns {?module:zlib.zstrmd}
*
* @example
* // initialize a Zlib deflate stream using default compression
* const zstrmd = defnew();
*
* // initialize a gzip deflate stream using fastest compression
* const zstrmd = defnew(true, Z_BEST_SPEED);
*/
static uc_value_t *
uc_zlib_defnew(uc_vm_t *vm, size_t nargs)
{
uc_value_t *gzip = uc_fn_arg(0);
uc_value_t *level = uc_fn_arg(1);
int ret, lvl = Z_DEFAULT_COMPRESSION;
bool gz = false;
zstrm_t *zstrm;
zstrm = calloc(1, sizeof(*zstrm));
if (!zstrm)
err_return(ENOMEM);
zstrm->strm.zalloc = Z_NULL;
zstrm->strm.zfree = Z_NULL;
zstrm->strm.opaque = Z_NULL;
if (gzip) {
if (ucv_type(gzip) != UC_BOOLEAN) {
last_error = EINVAL;
goto fail;
}
gz = (int)ucv_boolean_get(gzip);
}
if (level) {
if (ucv_type(level) != UC_INTEGER) {
last_error = EINVAL;
goto fail;
}
lvl = (int)ucv_int64_get(level);
}
ret = deflateInit2(&zstrm->strm, lvl,
Z_DEFLATED, // only allowed method
gz ? 15+16 : 15, // 15 Zlib default, +16 for gzip
8, // default value
Z_DEFAULT_STRATEGY); // default value
if (ret != Z_OK) {
last_error = ret;
goto fail;
}
return uc_resource_new(zstrmd_type, zstrm);
fail:
free(zstrm);
return NULL;
}
/**
* Writes a chunk of data to the deflate stream.
*
* Input data must be a string, it is internally compressed by the zlib deflate() routine,
* the end is buffered according to the requested `flush` mode until read via
* @see {@link module:zlib.zstrmd#read}.
* If `flush` is `Z_FINISH` (the default) then no more data can be written to the stream.
* Valid `flush`values are `Z_NO_FLUSH, Z_SYNC_FLUSH, Z_PARTIAL_FLUSH, Z_FULL_FLUSH, Z_FINISH`
*
* Returns `true` on success.
*
* Returns `null` if an error occurred.
*
* @function module:zlib.zstrmd#write
*
* @param {string} src
* The string of data to deflate.
*
* @param {?number} [flush=Z_FINISH]
* The zlib flush mode.
*
* @returns {?boolean}
*/
static uc_value_t *
uc_zlib_defwrite(uc_vm_t *vm, size_t nargs)
{
uc_value_t *src = uc_fn_arg(0);
uc_value_t *flush = uc_fn_arg(1);
zstrm_t **z = uc_fn_this("zlib.strmd");
zstrm_t *zstrm;
if (!z || !*z)
err_return(EBADF);
zstrm = *z;
if (Z_FINISH == zstrm->flush)
err_return(EPIPE); // can't reuse a finished stream
if (flush) {
if (ucv_type(flush) != UC_INTEGER)
err_return(EINVAL);
zstrm->flush = (int)ucv_int64_get(flush);
switch (zstrm->flush) {
case Z_NO_FLUSH:
case Z_SYNC_FLUSH:
case Z_PARTIAL_FLUSH:
case Z_FULL_FLUSH:
case Z_FINISH:
break;
default:
err_return(EINVAL);
}
}
else
zstrm->flush = Z_FINISH;
/* we only accept strings */
if (!src || ucv_type(src) != UC_STRING)
err_return(EINVAL);
if (!zstrm->outbuf)
zstrm->outbuf = ucv_stringbuf_new();
return ucv_boolean_new(uc_zlib_def_string(vm, src, zstrm));
}
/**
* Reads a chunk of compressed data from the deflate stream.
*
* Returns the current content of the deflate buffer, fed through
* @see {@link module:zlib.zstrmd#write}.
*
* Returns compressed chunk on success.
*
* Returns `null` if an error occurred.
*
* @function module:zlib.zstrmd#read
*
* @returns {?string}
*/
static uc_value_t *
uc_zlib_defread(uc_vm_t *vm, size_t nargs)
{
zstrm_t **z = uc_fn_this("zlib.strmd");
zstrm_t *zstrm;
uc_value_t *rv;
if (!z || !*z)
err_return(EBADF);
zstrm = *z;
if (!zstrm->outbuf)
err_return(ENODATA);
if (Z_FINISH == zstrm->flush)
(void)deflateEnd(&zstrm->strm);
rv = ucv_stringbuf_finish(zstrm->outbuf);
zstrm->outbuf = NULL; // outbuf is now unuseable
return rv;
}
/**
* Represents a handle for interacting with an inflate stream initiated by infnew().
*
* @class module:zlib.zstrmi
* @hideconstructor
*
* @see {@link module:zlib#infnew()}
*
* @example
*
* const zstrmi = infnew();
*
* for (let data = ...; data; data = ...) {
* zstrmi.write(data, Z_SYNC_FLUSH); // write compressed data to stream
* if (foo)
* let defl = zstrmi.read(); // read back decompressed stream content
* }
*
* // terminate the stream if needed (for e.g. file output)
* zstrmi.write('', Z_FINISH);
* defl = ztrmi.read();
*
* zstrmi.error();
*/
/**
* Initializes an inflate stream. Can process either Zlib or gzip data.
*
* Returns a stream handle on success.
*
* Returns `null` if an error occurred.
*
* @function module:zlib#infnew
*
* @returns {?module:zlib.zstrmi}
*
* @example
* // initialize an inflate stream
* const zstrmi = infnew();
*/
static uc_value_t *
uc_zlib_infnew(uc_vm_t *vm, size_t nargs)
{
int ret;
zstrm_t *zstrm;
zstrm = calloc(1, sizeof(*zstrm));
if (!zstrm)
err_return(ENOMEM);
zstrm->strm.zalloc = Z_NULL;
zstrm->strm.zfree = Z_NULL;
zstrm->strm.opaque = Z_NULL;
zstrm->strm.avail_in = 0;
zstrm->strm.next_in = Z_NULL;
/* tell inflateInit2 to perform either zlib or gzip decompression: 15+32 */
ret = inflateInit2(&zstrm->strm, 15+32);
if (ret != Z_OK) {
last_error = ret;
goto fail;
}
return uc_resource_new(zstrmi_type, zstrm);
fail:
free(zstrm);
return NULL;
}
/**
* Writes a chunk of data to the inflate stream.
*
* Input data must be a string, it is internally decompressed by the zlib deflate() routine,
* the end is buffered according to the requested `flush` mode until read via
* @see {@link module:zlib.zstrmd#read}.
* If `flush` is `Z_FINISH` (the default) then no more data can be written to the stream.
* Valid `flush` values are `Z_NO_FLUSH, Z_SYNC_FLUSH, Z_FINISH`
*
* Returns `true` on success.
*
* Returns `null` if an error occurred.
*
* @function module:zlib.zstrmi#write
*
* @param {string} src
* The string of data to deflate.
*
* @param {?number} [flush=Z_FINISH]
* The zlib flush mode.
*
* @returns {?boolean}
*/
static uc_value_t *
uc_zlib_infwrite(uc_vm_t *vm, size_t nargs)
{
uc_value_t *src = uc_fn_arg(0);
uc_value_t *flush = uc_fn_arg(1);
zstrm_t **z = uc_fn_this("zlib.strmi");
zstrm_t *zstrm;
if (!z || !*z)
err_return(EBADF);
zstrm = *z;
if (Z_FINISH == zstrm->flush)
err_return(EPIPE); // can't reuse a finished stream
if (flush) {
if (ucv_type(flush) != UC_INTEGER)
err_return(EINVAL);
zstrm->flush = (int)ucv_int64_get(flush);
switch (zstrm->flush) {
case Z_NO_FLUSH:
case Z_SYNC_FLUSH:
case Z_FINISH:
break;
default:
err_return(EINVAL);
}
}
else
zstrm->flush = Z_FINISH;
/* we only accept strings */
if (!src || ucv_type(src) != UC_STRING)
err_return(EINVAL);
if (!zstrm->outbuf)
zstrm->outbuf = ucv_stringbuf_new();
return ucv_boolean_new(uc_zlib_inf_string(vm, src, zstrm));
}
/**
* Reads a chunk of decompressed data from the inflate stream.
*
* Returns the current content of the inflate buffer, fed through
* @see {@link module:zlib.zstrmi#write}.
*
* Returns compressed chunk on success.
*
* Returns `null` if an error occurred.
*
* @function module:zlib.zstrmd#read
*
* @returns {?string}
*/
static uc_value_t *
uc_zlib_infread(uc_vm_t *vm, size_t nargs)
{
zstrm_t **z = uc_fn_this("zlib.strmi");
zstrm_t *zstrm;
uc_value_t *rv;
if (!z || !*z)
err_return(EBADF);
zstrm = *z;
if (!zstrm->outbuf)
err_return(ENODATA);
if (Z_FINISH == zstrm->flush)
(void)inflateEnd(&zstrm->strm);
rv = ucv_stringbuf_finish(zstrm->outbuf);
zstrm->outbuf = NULL; // outbuf is now unuseable
return rv;
}
/**
* Query error information.
*
* Returns a string containing a description of the last occurred error or
* `null` if there is no error information.
*
* @function module:zlib.zstrmd#error
*
*
* @returns {?string}
*/
static uc_value_t *
uc_zlib_error(uc_vm_t *vm, size_t nargs)
{
uc_value_t *errmsg;
if (!last_error)
return NULL;
// negative last_error only happens for zlib init returns
errmsg = ucv_string_new(last_error < 0 ? ziniterr(last_error) : strerror(last_error));
last_error = 0;
return errmsg;
}
static const uc_function_list_t strmd_fns[] = {
{ "write", uc_zlib_defwrite },
{ "read", uc_zlib_defread },
{ "error", uc_zlib_error },
};
static const uc_function_list_t strmi_fns[] = {
{ "write", uc_zlib_infwrite },
{ "read", uc_zlib_infread },
{ "error", uc_zlib_error },
};
static const uc_function_list_t global_fns[] = {
{ "deflate", uc_zlib_deflate },
{ "inflate", uc_zlib_inflate },
{ "defnew", uc_zlib_defnew },
{ "infnew", uc_zlib_infnew },
};
static void destroy_zstrmd(void *z)
{
zstrm_t *zstrm = z;
if (zstrm) {
(void)deflateEnd(&zstrm->strm);
printbuf_free(zstrm->outbuf);
free(zstrm);
}
}
static void destroy_zstrmi(void *z)
{
zstrm_t *zstrm = z;
if (zstrm) {
(void)inflateEnd(&zstrm->strm);
printbuf_free(zstrm->outbuf);
free(zstrm);
}
}
void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
{
uc_function_list_register(scope, global_fns);
zstrmd_type = uc_type_declare(vm, "zlib.strmd", strmd_fns, destroy_zstrmd);
zstrmi_type = uc_type_declare(vm, "zlib.strmi", strmi_fns, destroy_zstrmi);
#define ADD_CONST(x) ucv_object_add(scope, #x, ucv_int64_new(x))
/**
* @typedef
* @name Compression levels
* @description Constants representing predefined compression levels.
* @property {number} Z_NO_COMPRESSION.
* @property {number} Z_BEST_SPEED.
* @property {number} Z_BEST_COMPRESSION.
* @property {number} Z_DEFAULT_COMPRESSION - default compromise between speed and compression (currently equivalent to level 6).
*/
ADD_CONST(Z_NO_COMPRESSION);
ADD_CONST(Z_BEST_SPEED);
ADD_CONST(Z_BEST_COMPRESSION);
ADD_CONST(Z_DEFAULT_COMPRESSION);
/**
* @typedef
* @name flush options
* @description Constants representing flush options.
* @property {number} Z_NO_FLUSH.
* @property {number} Z_PARTIAL_FLUSH.
* @property {number} Z_SYNC_FLUSH.
* @property {number} Z_FULL_FLUSH.
* @property {number} Z_FINISH.
*/
ADD_CONST(Z_NO_FLUSH);
ADD_CONST(Z_PARTIAL_FLUSH);
ADD_CONST(Z_SYNC_FLUSH);
ADD_CONST(Z_FULL_FLUSH);
ADD_CONST(Z_FINISH);
}