Skip to content

Commit

Permalink
zlib: add stream-oriented inflate operations
Browse files Browse the repository at this point in the history
Signed-off-by: Thibaut VARÈNE <[email protected]>
  • Loading branch information
f00b4r0 committed Jul 18, 2024
1 parent 6c773a3 commit 5ba94d1
Showing 1 changed file with 194 additions and 1 deletion.
195 changes: 194 additions & 1 deletion lib/zlib.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
*/
#define CHUNK 16384

static uc_resource_type_t *zstrmd_type;
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)
Expand Down Expand Up @@ -380,6 +380,7 @@ uc_zlib_inf_string(uc_vm_t * const vm, uc_value_t * const str, zstrm_t * const z

ret = inf_chunks(zstrm);
assert(zstrm->strm.avail_in == 0);
last_error = ret;

return Z_STREAM_END == ret;
}
Expand Down Expand Up @@ -671,6 +672,179 @@ uc_zlib_defread(uc_vm_t *vm, size_t nargs)
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.
*
Expand Down Expand Up @@ -702,10 +876,17 @@ static const uc_function_list_t strmd_fns[] = {
{ "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)
Expand All @@ -719,11 +900,23 @@ static void destroy_zstrmd(void *z)
}
}

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))

Expand Down

0 comments on commit 5ba94d1

Please sign in to comment.