]> git.kaiwu.me - njs.git/commitdiff
QuickJS: added zlib module.
authorDmitry Volyntsev <xeioex@nginx.com>
Thu, 2 May 2024 00:31:01 +0000 (17:31 -0700)
committerDmitry Volyntsev <xeioex@nginx.com>
Thu, 2 May 2024 00:31:01 +0000 (17:31 -0700)
17 files changed:
auto/init
auto/make
auto/qjs_module [new file with mode: 0644]
auto/qjs_modules [new file with mode: 0644]
auto/quickjs
auto/sources
configure
external/njs_shell.c
external/qjs_zlib_module.c [new file with mode: 0644]
src/qjs.c [new file with mode: 0644]
src/qjs.h [new file with mode: 0644]
src/qjs_buffer.c [new file with mode: 0644]
src/test/njs_unit_test.c
test/buffer.t.js [new file with mode: 0644]
test/harness/runTsuite.js
test/setup
test/zlib.t.mjs [new file with mode: 0644]

index 3c92859150e96dd04872e572bf94c6ea35b61ea7..019c62cbe0e04393064b3f8853825807fd145d6a 100644 (file)
--- a/auto/init
+++ b/auto/init
@@ -16,6 +16,7 @@ NJS_CFLAGS=${NJS_CFLAGS=}
 NJS_BUILD_DIR=${NJS_BUILD_DIR:-build}
 
 NJS_LIB_MODULES=
+QJS_LIB_MODULES=
 
 NJS_LIBRT=
 
index 879cabec3a7ae5ffba54c76df3b7e1acae877eaf..4a77335b89aa0421444d2a00a2b3c6bee515dbe6 100644 (file)
--- a/auto/make
+++ b/auto/make
@@ -15,11 +15,6 @@ njs_modules_c=$NJS_BUILD_DIR/njs_modules.c
 
 NJS_LIB_SRCS="$NJS_LIB_SRCS $njs_modules_c"
 
-njs_incs=`echo $NJS_LIB_INCS \
-        | sed -e "s# *\([^ ]*\)#$njs_regex_cont-I\1#g"`
-njs_objs=`echo $NJS_LIB_SRCS \
-        | sed -e "s# *\([^ ]*\.\)c#$NJS_BUILD_DIR/\1o$njs_regex_cont#g"`
-
 cat << END                                    > $njs_modules_c
 
 #include <njs_main.h>
@@ -45,6 +40,43 @@ cat << END                                    >> $njs_modules_c
 
 END
 
+if [ $NJS_HAVE_QUICKJS = YES ]; then
+
+qjs_modules_c=$NJS_BUILD_DIR/qjs_modules.c
+
+NJS_LIB_SRCS="$NJS_LIB_SRCS $qjs_modules_c"
+
+cat << END                                    > $qjs_modules_c
+
+#include <qjs.h>
+
+END
+
+for mod in $QJS_LIB_MODULES
+do
+    echo "extern qjs_module_t  $mod;"         >> $qjs_modules_c
+done
+
+echo                                          >> $qjs_modules_c
+echo 'qjs_module_t *qjs_modules[] = {'        >> $qjs_modules_c
+
+for mod in $QJS_LIB_MODULES
+do
+    echo "    &$mod,"                         >> $qjs_modules_c
+done
+
+cat << END                                    >> $qjs_modules_c
+    NULL
+};
+
+END
+fi
+
+njs_incs=`echo $NJS_LIB_INCS \
+        | sed -e "s# *\([^ ]*\)#$njs_regex_cont-I\1#g"`
+njs_objs=`echo $NJS_LIB_SRCS \
+        | sed -e "s# *\([^ ]*\.\)c#$NJS_BUILD_DIR/\1o$njs_regex_cont#g"`
+
 cat << END > $NJS_MAKEFILE
 
 # This file is auto-generated by configure
diff --git a/auto/qjs_module b/auto/qjs_module
new file mode 100644 (file)
index 0000000..3827048
--- /dev/null
@@ -0,0 +1,6 @@
+# Copyright (C) Dmitry Volyntsev
+# Copyright (C) F5, Inc
+
+QJS_LIB_MODULES="$QJS_LIB_MODULES $njs_module_name"
+NJS_LIB_SRCS="$NJS_LIB_SRCS $njs_module_srcs"
+NJS_LIB_INCS="$NJS_LIB_INCS $njs_module_incs"
diff --git a/auto/qjs_modules b/auto/qjs_modules
new file mode 100644 (file)
index 0000000..9dde2ef
--- /dev/null
@@ -0,0 +1,20 @@
+# Copyright (C) Dmitry Volyntsev
+# Copyright (C) F5, Inc
+
+if [ $NJS_HAVE_QUICKJS = YES ]; then
+
+    njs_module_name=qjs_buffer_module
+    njs_module_incs=
+    njs_module_srcs=src/qjs_buffer.c
+
+    . auto/qjs_module
+
+    if [ $NJS_ZLIB = YES -a $NJS_HAVE_ZLIB = YES ]; then
+        njs_module_name=qjs_zlib_module
+        njs_module_incs=
+        njs_module_srcs=external/qjs_zlib_module.c
+
+        . auto/qjs_module
+    fi
+
+fi
index f910aff1e422a2d0fec926c7c8fdaa52681c912d..4491fc5c5ea5fa93733413aa4e0ba99a55768027 100644 (file)
@@ -25,6 +25,7 @@ if [ $NJS_TRY_QUICKJS = YES ]; then
                           JSRuntime *rt;
 
                           rt = JS_NewRuntime();
+                          (void) JS_GetClassID;
                           JS_FreeRuntime(rt);
                           return 0;
                      }"
@@ -54,6 +55,26 @@ if [ $NJS_TRY_QUICKJS = YES ]; then
     fi
 
     if [ $njs_found = yes ]; then
+
+        njs_feature="QuickJS JS_NewTypedArray()"
+        njs_feature_test="#if defined(__GNUC__) && (__GNUC__ >= 8)
+                          #pragma GCC diagnostic push
+                          #pragma GCC diagnostic ignored \"-Wcast-function-type\"
+                          #endif
+
+                          #include <quickjs.h>
+
+                          int main() {
+                              (void) JS_NewTypedArray;
+                              return 0;
+                         }"
+
+        . auto/feature
+
+        if [ $njs_found = yes ]; then
+            njs_define=NJS_HAVE_QUICKJS_NEW_TYPED_ARRAY . auto/define
+        fi
+
         NJS_HAVE_QUICKJS=YES
         NJS_QUICKJS_LIB="$njs_feature_libs"
         NJS_LIB_INCS="$NJS_LIB_INCS $njs_feature_incs"
index d5637a52117aaca875f76014b9d6f20137f850f8..ae20829339e6e3454732c22bc797ebc77520951e 100644 (file)
@@ -72,6 +72,10 @@ if [ "$NJS_HAVE_LIBBFD" = "YES" -a "$NJS_HAVE_DL_ITERATE_PHDR" = "YES" ]; then
        NJS_LIB_SRCS="$NJS_LIB_SRCS src/njs_addr2line.c"
 fi
 
+if [ "$NJS_HAVE_QUICKJS" = "YES" ]; then
+       NJS_LIB_SRCS="$NJS_LIB_SRCS src/qjs.c"
+fi
+
 NJS_TS_SRCS=$(find ts/ -name "*.d.ts" -o -name "*.json")
 
 NJS_TEST_TS_SRCS=$(find test/ts/ -name "*.ts" -o -name "*.json")
index 1a80d86de2ce8d25dbd900615ba213dcc39e17b8..57730b0577a71a8a936976454c76dc6062776e1c 100755 (executable)
--- a/configure
+++ b/configure
@@ -21,7 +21,7 @@ NJS_AUTOCONF_ERR=$NJS_BUILD_DIR/autoconf.err
 NJS_AUTO_CONFIG_H=$NJS_BUILD_DIR/njs_auto_config.h
 NJS_MAKEFILE=$NJS_BUILD_DIR/Makefile
 
-NJS_LIB_INCS="src $NJS_BUILD_DIR"
+NJS_LIB_INCS="src external $NJS_BUILD_DIR"
 
 test -d $NJS_BUILD_DIR || mkdir $NJS_BUILD_DIR
 
@@ -59,6 +59,7 @@ NJS_LIB_AUX_LIBS=
 
 . auto/sources
 . auto/modules
+. auto/qjs_modules
 . auto/make
 . auto/expect
 . auto/summary
index 2b59f0a047c02d297c622d63de64886da3053b76..cc4bf8e4892cd73bb20fddc97ce92345726b5dd2 100644 (file)
 #include <njs_rbtree.h>
 
 #if (NJS_HAVE_QUICKJS)
-#if defined(__GNUC__) && (__GNUC__ >= 8)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wcast-function-type"
-#endif
-
-#include <quickjs.h>
-
-#if defined(__GNUC__) && (__GNUC__ >= 8)
-#pragma GCC diagnostic pop
-#endif
-#define NJS_QUICKJS_VERSION  "Unknown version"
-#include <pthread.h>
+#include <qjs.h>
 #endif
 
 #if (!defined NJS_FUZZER_TARGET && defined NJS_HAVE_READLINE)
@@ -2822,7 +2811,7 @@ njs_engine_qjs_init(njs_engine_t *engine, njs_opts_t *opts)
         return NJS_ERROR;
     }
 
-    engine->u.qjs.ctx = JS_NewContext(engine->u.qjs.rt);
+    engine->u.qjs.ctx = qjs_new_context(engine->u.qjs.rt);
     if (engine->u.qjs.ctx == NULL) {
         njs_stderror("JS_NewContext() failed\n");
         return NJS_ERROR;
diff --git a/external/qjs_zlib_module.c b/external/qjs_zlib_module.c
new file mode 100644 (file)
index 0000000..2d3b2d9
--- /dev/null
@@ -0,0 +1,512 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) F5, Inc.
+ */
+
+#include <qjs.h>
+#include <zlib.h>
+
+#define NJS_ZLIB_CHUNK_SIZE  1024
+
+static JSValue qjs_zlib_ext_deflate(JSContext *ctx, JSValueConst this_val,
+    int argc, JSValueConst *argv, int raw);
+static JSValue qjs_zlib_ext_inflate(JSContext *ctx, JSValueConst this_val,
+    int argc, JSValueConst *argv, int raw);
+
+static JSModuleDef *qjs_zlib_init(JSContext *ctx, const char *name);
+static void *qjs_zlib_alloc(void *opaque, u_int items, u_int size);
+static void qjs_zlib_free(void *opaque, void *address);
+
+
+static const JSCFunctionListEntry qjs_zlib_constants[] = {
+    JS_PROP_INT32_DEF("Z_NO_COMPRESSION",
+                      Z_NO_COMPRESSION,
+                      JS_PROP_ENUMERABLE),
+    JS_PROP_INT32_DEF("Z_BEST_SPEED",
+                      Z_BEST_SPEED,
+                      JS_PROP_ENUMERABLE),
+    JS_PROP_INT32_DEF("Z_BEST_COMPRESSION",
+                      Z_BEST_COMPRESSION,
+                      JS_PROP_ENUMERABLE),
+    JS_PROP_INT32_DEF("Z_FILTERED",
+                      Z_FILTERED,
+                      JS_PROP_ENUMERABLE),
+    JS_PROP_INT32_DEF("Z_HUFFMAN_ONLY",
+                      Z_HUFFMAN_ONLY,
+                      JS_PROP_ENUMERABLE),
+    JS_PROP_INT32_DEF("Z_RLE",
+                      Z_RLE,
+                      JS_PROP_ENUMERABLE),
+    JS_PROP_INT32_DEF("Z_FIXED",
+                      Z_FIXED,
+                      JS_PROP_ENUMERABLE),
+    JS_PROP_INT32_DEF("Z_DEFAULT_STRATEGY",
+                      Z_DEFAULT_STRATEGY,
+                      JS_PROP_ENUMERABLE),
+};
+
+static const JSCFunctionListEntry qjs_zlib_export[] = {
+    JS_CFUNC_MAGIC_DEF("deflateRawSync", 2, qjs_zlib_ext_deflate, 1),
+    JS_CFUNC_MAGIC_DEF("deflateSync", 2, qjs_zlib_ext_deflate, 0),
+    JS_CFUNC_MAGIC_DEF("inflateRawSync", 2, qjs_zlib_ext_inflate, 1),
+    JS_CFUNC_MAGIC_DEF("inflateSync", 2, qjs_zlib_ext_inflate, 0),
+    JS_OBJECT_DEF("constants",
+                  qjs_zlib_constants,
+                  njs_nitems(qjs_zlib_constants),
+                  JS_PROP_CONFIGURABLE),
+};
+
+
+qjs_module_t  qjs_zlib_module = {
+    .name = "zlib",
+    .init = qjs_zlib_init,
+};
+
+
+static JSValue
+qjs_zlib_ext_deflate(JSContext *ctx, JSValueConst this_val, int argc,
+    JSValueConst *argv, int raw)
+{
+    int          rc, chunk_size, level, mem_level, strategy, window_bits;
+    JSValue      ret, options;
+    z_stream     stream;
+    njs_chb_t    chain;
+    qjs_bytes_t  bytes, dictionary;
+
+    chunk_size = NJS_ZLIB_CHUNK_SIZE;
+    mem_level = 8;
+    level = Z_DEFAULT_COMPRESSION;
+    strategy = Z_DEFAULT_STRATEGY;
+    window_bits = raw ? -MAX_WBITS : MAX_WBITS;
+
+    NJS_CHB_CTX_INIT(&chain, ctx);
+    dictionary.start = NULL;
+    dictionary.length = 0;
+    stream.opaque = NULL;
+
+    options = argv[1];
+
+    if (JS_IsObject(options)) {
+        ret = JS_GetPropertyStr(ctx, options, "chunkSize");
+        if (JS_IsException(ret)) {
+            return JS_EXCEPTION;
+        }
+
+        if (!JS_IsUndefined(ret)) {
+            rc = JS_ToInt32(ctx, &chunk_size, ret);
+            JS_FreeValue(ctx, ret);
+            if (rc != 0) {
+                return JS_EXCEPTION;
+            }
+
+            if (chunk_size < 64) {
+                JS_ThrowRangeError(ctx, "chunkSize must be >= 64");
+                return JS_EXCEPTION;
+            }
+        }
+
+        ret = JS_GetPropertyStr(ctx, options, "level");
+        if (JS_IsException(ret)) {
+            return JS_EXCEPTION;
+        }
+
+        if (!JS_IsUndefined(ret)) {
+            rc = JS_ToInt32(ctx, &level, ret);
+            JS_FreeValue(ctx, ret);
+            if (rc != 0) {
+                return JS_EXCEPTION;
+            }
+
+            if (level < Z_DEFAULT_COMPRESSION || level > Z_BEST_COMPRESSION) {
+                JS_ThrowRangeError(ctx, "level must be in the range %d..%d",
+                                   Z_DEFAULT_COMPRESSION, Z_BEST_COMPRESSION);
+                return JS_EXCEPTION;
+            }
+        }
+
+        ret = JS_GetPropertyStr(ctx, options, "windowBits");
+        if (JS_IsException(ret)) {
+            return JS_EXCEPTION;
+        }
+
+        if (!JS_IsUndefined(ret)) {
+            rc = JS_ToInt32(ctx, &window_bits, ret);
+            JS_FreeValue(ctx, ret);
+            if (rc != 0) {
+                return JS_EXCEPTION;
+            }
+
+            if (raw) {
+                if (window_bits < -15 || window_bits > -9) {
+                    JS_ThrowRangeError(ctx, "windowBits must be in the range "
+                                       "-15..-9");
+                    return JS_EXCEPTION;
+                }
+
+            } else {
+                if (window_bits < 9 || window_bits > 15) {
+                    JS_ThrowRangeError(ctx, "windowBits must be in the range "
+                                       "9..15");
+                    return JS_EXCEPTION;
+                }
+            }
+        }
+
+        ret = JS_GetPropertyStr(ctx, options, "memLevel");
+        if (JS_IsException(ret)) {
+            return JS_EXCEPTION;
+        }
+
+        if (!JS_IsUndefined(ret)) {
+            rc = JS_ToInt32(ctx, &mem_level, ret);
+            JS_FreeValue(ctx, ret);
+            if (rc != 0) {
+                return JS_EXCEPTION;
+            }
+
+            if (mem_level < 1 || mem_level > 9) {
+                JS_ThrowRangeError(ctx, "memLevel must be in the range 1..9");
+                return JS_EXCEPTION;
+            }
+        }
+
+        ret = JS_GetPropertyStr(ctx, options, "strategy");
+        if (JS_IsException(ret)) {
+            return JS_EXCEPTION;
+        }
+
+        if (!JS_IsUndefined(ret)) {
+            rc = JS_ToInt32(ctx, &strategy, ret);
+            JS_FreeValue(ctx, ret);
+            if (rc != 0) {
+                return JS_EXCEPTION;
+            }
+
+            switch (strategy) {
+            case Z_FILTERED:
+            case Z_HUFFMAN_ONLY:
+            case Z_RLE:
+            case Z_FIXED:
+            case Z_DEFAULT_STRATEGY:
+                break;
+
+            default:
+                JS_ThrowRangeError(ctx, "unknown strategy: %d", strategy);
+                return JS_EXCEPTION;
+            }
+        }
+
+        ret = JS_GetPropertyStr(ctx, options, "dictionary");
+        if (JS_IsException(ret)) {
+            return JS_EXCEPTION;
+        }
+
+        if (!JS_IsUndefined(ret)) {
+            rc = qjs_to_bytes(ctx, &dictionary, ret);
+            JS_FreeValue(ctx, ret);
+            if (rc != 0) {
+                return JS_EXCEPTION;
+            }
+        }
+    }
+
+    rc = qjs_to_bytes(ctx, &bytes, argv[0]);
+    if (rc != 0) {
+        return JS_EXCEPTION;
+    }
+
+    stream.next_in = bytes.start;
+    stream.avail_in = bytes.length;
+
+    stream.zalloc = qjs_zlib_alloc;
+    stream.zfree = qjs_zlib_free;
+    stream.opaque = ctx;
+
+    rc = deflateInit2(&stream, level, Z_DEFLATED, window_bits, mem_level,
+                      strategy);
+    if (njs_slow_path(rc != Z_OK)) {
+        JS_ThrowInternalError(ctx, "deflateInit2() failed");
+        goto fail;
+    }
+
+    if (dictionary.start != NULL) {
+        rc = deflateSetDictionary(&stream, dictionary.start, dictionary.length);
+        if (rc != Z_OK) {
+            JS_ThrowInternalError(ctx, "deflateSetDictionary() failed");
+            goto fail;
+        }
+    }
+
+    do {
+        stream.next_out = njs_chb_reserve(&chain, chunk_size);
+        if (njs_slow_path(stream.next_out == NULL)) {
+            JS_ThrowOutOfMemory(ctx);
+            goto fail;
+        }
+
+        stream.avail_out = chunk_size;
+
+        rc = deflate(&stream, Z_FINISH);
+        if (njs_slow_path(rc < 0)) {
+            JS_ThrowInternalError(ctx, "failed to deflate the data: %s",
+                                  stream.msg);
+            goto fail;
+        }
+
+        njs_chb_written(&chain, chunk_size - stream.avail_out);
+
+    } while (stream.avail_out == 0);
+
+    deflateEnd(&stream);
+
+    qjs_bytes_free(ctx, &bytes);
+
+    if (dictionary.start != NULL) {
+        qjs_bytes_free(ctx, &dictionary);
+    }
+
+    ret = qjs_buffer_chb_alloc(ctx, &chain);
+
+    njs_chb_destroy(&chain);
+
+    return ret;
+
+fail:
+
+    qjs_bytes_free(ctx, &bytes);
+
+    if (dictionary.start != NULL) {
+        qjs_bytes_free(ctx, &dictionary);
+    }
+
+    if (stream.opaque != NULL) {
+        deflateEnd(&stream);
+    }
+
+    if (chain.pool != NULL) {
+        njs_chb_destroy(&chain);
+    }
+
+    return JS_EXCEPTION;
+}
+
+
+static JSValue
+qjs_zlib_ext_inflate(JSContext *ctx, JSValueConst this_val, int argc,
+    JSValueConst *argv, int raw)
+{
+    int          rc, chunk_size, window_bits;
+    JSValue      ret, options;
+    z_stream     stream;
+    njs_chb_t    chain;
+    qjs_bytes_t  bytes, dictionary;
+
+    chunk_size = NJS_ZLIB_CHUNK_SIZE;
+    window_bits = raw ? -MAX_WBITS : MAX_WBITS;
+
+    NJS_CHB_CTX_INIT(&chain, ctx);
+    dictionary.start = NULL;
+    dictionary.length = 0;
+    stream.opaque = NULL;
+
+    options = argv[1];
+
+    if (JS_IsObject(options)) {
+        ret = JS_GetPropertyStr(ctx, options, "chunkSize");
+        if (JS_IsException(ret)) {
+            return JS_EXCEPTION;
+        }
+
+        if (!JS_IsUndefined(ret)) {
+            rc = JS_ToInt32(ctx, &chunk_size, ret);
+            JS_FreeValue(ctx, ret);
+            if (rc != 0) {
+                return JS_EXCEPTION;
+            }
+
+            if (chunk_size < 64) {
+                JS_ThrowRangeError(ctx, "chunkSize must be >= 64");
+                return JS_EXCEPTION;
+            }
+        }
+
+        ret = JS_GetPropertyStr(ctx, options, "windowBits");
+        if (JS_IsException(ret)) {
+            return JS_EXCEPTION;
+        }
+
+        if (!JS_IsUndefined(ret)) {
+            rc = JS_ToInt32(ctx, &window_bits, ret);
+            JS_FreeValue(ctx, ret);
+            if (rc != 0) {
+                return JS_EXCEPTION;
+            }
+
+            if (raw) {
+                if (window_bits < -15 || window_bits > -8) {
+                    JS_ThrowRangeError(ctx, "windowBits must be in the range "
+                                       "-15..-8");
+                    return JS_EXCEPTION;
+                }
+
+            } else {
+                if (window_bits < 8 || window_bits > 15) {
+                    JS_ThrowRangeError(ctx, "windowBits must be in the range "
+                                       "8..15");
+                    return JS_EXCEPTION;
+                }
+            }
+        }
+
+        ret = JS_GetPropertyStr(ctx, options, "dictionary");
+        if (JS_IsException(ret)) {
+            return JS_EXCEPTION;
+        }
+
+        if (!JS_IsUndefined(ret)) {
+            rc = qjs_to_bytes(ctx, &dictionary, ret);
+            JS_FreeValue(ctx, ret);
+            if (rc != 0) {
+                return JS_EXCEPTION;
+            }
+        }
+    }
+
+    rc = qjs_to_bytes(ctx, &bytes, argv[0]);
+    if (rc != 0) {
+        return JS_EXCEPTION;
+    }
+
+    stream.next_in = bytes.start;
+    stream.avail_in = bytes.length;
+
+    stream.zalloc = qjs_zlib_alloc;
+    stream.zfree = qjs_zlib_free;
+    stream.opaque = ctx;
+
+    rc = inflateInit2(&stream, window_bits);
+    if (njs_slow_path(rc != Z_OK)) {
+        JS_ThrowInternalError(ctx, "inflateInit2() failed");
+        goto fail;
+    }
+
+    if (dictionary.start != NULL) {
+        rc = inflateSetDictionary(&stream, dictionary.start, dictionary.length);
+        if (rc != Z_OK) {
+            JS_ThrowInternalError(ctx, "inflateSetDictionary() failed");
+            goto fail;
+        }
+    }
+
+    while (rc != Z_STREAM_END) {
+        stream.next_out = njs_chb_reserve(&chain, chunk_size);
+        if (njs_slow_path(stream.next_out == NULL)) {
+            JS_ThrowOutOfMemory(ctx);
+            goto fail;
+        }
+
+        stream.avail_out = chunk_size;
+
+        rc = inflate(&stream, Z_NO_FLUSH);
+        if (njs_slow_path(rc < 0)) {
+            JS_ThrowInternalError(ctx, "failed to inflate the data: %s",
+                                  stream.msg);
+            goto fail;
+        }
+
+        njs_chb_written(&chain, chunk_size - stream.avail_out);
+    }
+
+    rc = inflateEnd(&stream);
+    if (njs_slow_path(rc != Z_OK)) {
+        JS_ThrowInternalError(ctx, "inflateEnd() failed");
+        goto fail;
+    }
+
+    qjs_bytes_free(ctx, &bytes);
+
+    if (dictionary.start != NULL) {
+        qjs_bytes_free(ctx, &dictionary);
+    }
+
+    ret = qjs_buffer_chb_alloc(ctx, &chain);
+
+    njs_chb_destroy(&chain);
+
+    return ret;
+
+fail:
+
+    qjs_bytes_free(ctx, &bytes);
+
+    if (dictionary.start != NULL) {
+        qjs_bytes_free(ctx, &dictionary);
+    }
+
+    if (stream.opaque != NULL) {
+        inflateEnd(&stream);
+    }
+
+    if (chain.pool != NULL) {
+        njs_chb_destroy(&chain);
+    }
+
+    return JS_EXCEPTION;
+}
+
+
+static int
+qjs_zlib_module_init(JSContext *ctx, JSModuleDef *m)
+{
+    int      rc;
+    JSValue  proto;
+
+    proto = JS_NewObject(ctx);
+    JS_SetPropertyFunctionList(ctx, proto, qjs_zlib_export,
+                               njs_nitems(qjs_zlib_export));
+
+    rc = JS_SetModuleExport(ctx, m, "default", proto);
+    if (rc != 0) {
+        return -1;
+    }
+
+    return JS_SetModuleExportList(ctx, m, qjs_zlib_export,
+                                  njs_nitems(qjs_zlib_export));
+}
+
+
+static JSModuleDef *
+qjs_zlib_init(JSContext *ctx, const char *name)
+{
+    int          rc;
+    JSModuleDef  *m;
+
+    m = JS_NewCModule(ctx, name, qjs_zlib_module_init);
+    if (m == NULL) {
+        return NULL;
+    }
+
+    JS_AddModuleExport(ctx, m, "default");
+    rc = JS_AddModuleExportList(ctx, m, qjs_zlib_export,
+                                njs_nitems(qjs_zlib_export));
+    if (rc != 0) {
+        return NULL;
+    }
+
+    return m;
+}
+
+
+static void *
+qjs_zlib_alloc(void *opaque, u_int items, u_int size)
+{
+    return js_malloc(opaque, items * size);
+}
+
+
+static void
+qjs_zlib_free(void *opaque, void *address)
+{
+    js_free(opaque, address);
+}
diff --git a/src/qjs.c b/src/qjs.c
new file mode 100644 (file)
index 0000000..83b43ad
--- /dev/null
+++ b/src/qjs.c
@@ -0,0 +1,109 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) F5, Inc.
+ */
+
+#include <qjs.h>
+
+
+JSContext *
+qjs_new_context(JSRuntime *rt)
+{
+    JSContext     *ctx;
+    qjs_module_t  **module;
+
+    ctx = JS_NewContext(rt);
+    if (ctx == NULL) {
+        return NULL;
+    }
+
+    for (module = qjs_modules; *module != NULL; module++) {
+        if ((*module)->init(ctx, (*module)->name) == NULL) {
+            return NULL;
+        }
+    }
+
+    return ctx;
+}
+
+
+int
+qjs_to_bytes(JSContext *ctx, qjs_bytes_t *bytes, JSValueConst value)
+{
+    size_t   byte_offset, byte_length;
+    JSValue  val;
+
+    val = JS_GetTypedArrayBuffer(ctx, value, &byte_offset, &byte_length, NULL);
+    if (!JS_IsException(val)) {
+        bytes->start = JS_GetArrayBuffer(ctx, &bytes->length, val);
+
+        JS_FreeValue(ctx, val);
+
+        if (bytes->start != NULL) {
+            bytes->tag = JS_TAG_OBJECT;
+            bytes->start += byte_offset;
+            bytes->length = byte_length;
+            return 0;
+        }
+    }
+
+    bytes->start = JS_GetArrayBuffer(ctx, &bytes->length, value);
+    if (bytes->start != NULL) {
+        bytes->tag = JS_TAG_OBJECT;
+        return 0;
+    }
+
+    bytes->tag = JS_TAG_STRING;
+
+    if (!JS_IsString(value)) {
+        val = JS_ToString(ctx, value);
+
+        bytes->start = (u_char *) JS_ToCStringLen(ctx, &bytes->length, val);
+
+        JS_FreeValue(ctx, val);
+
+        if (bytes->start == NULL) {
+            return -1;
+        }
+    }
+
+    bytes->start = (u_char *) JS_ToCStringLen(ctx, &bytes->length, value);
+
+    return (bytes->start != NULL) ? 0 : -1;
+}
+
+
+void
+qjs_bytes_free(JSContext *ctx, qjs_bytes_t *bytes)
+{
+    if (bytes->tag == JS_TAG_STRING) {
+        JS_FreeCString(ctx, (char *) bytes->start);
+    }
+}
+
+
+JSValue
+qjs_typed_array_data(JSContext *ctx, JSValueConst value, njs_str_t *data)
+{
+    size_t  byte_offset, byte_length;
+
+    value = JS_GetTypedArrayBuffer(ctx, value, &byte_offset, &byte_length,
+                                   NULL);
+    if (JS_IsException(value)) {
+        return value;
+    }
+
+    data->start = JS_GetArrayBuffer(ctx, &data->length, value);
+
+    JS_FreeValue(ctx, value);
+
+    if (data->start == NULL) {
+        return JS_EXCEPTION;
+    }
+
+    data->start += byte_offset;
+    data->length = byte_length;
+
+    return JS_UNDEFINED;
+}
diff --git a/src/qjs.h b/src/qjs.h
new file mode 100644 (file)
index 0000000..2307d4d
--- /dev/null
+++ b/src/qjs.h
@@ -0,0 +1,79 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) F5, Inc.
+ */
+
+#ifndef _QJS_H_INCLUDED_
+#define _QJS_H_INCLUDED_
+
+#include <njs_auto_config.h>
+
+#include <njs_types.h>
+#include <njs_clang.h>
+#include <string.h>
+#include <njs_str.h>
+#include <njs_unicode.h>
+#include <njs_utf8.h>
+#include <njs_chb.h>
+
+#if defined(__GNUC__) && (__GNUC__ >= 8)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-function-type"
+#endif
+
+#include <quickjs.h>
+
+#if defined(__GNUC__) && (__GNUC__ >= 8)
+#pragma GCC diagnostic pop
+#endif
+#define NJS_QUICKJS_VERSION  "Unknown version"
+#include <pthread.h>
+
+
+typedef JSModuleDef *(*qjs_addon_init_pt)(JSContext *ctx, const char *name);
+
+typedef struct {
+    const char                     *name;
+    qjs_addon_init_pt               init;
+} qjs_module_t;
+
+
+JSContext *qjs_new_context(JSRuntime *rt);
+
+
+JSValue qjs_buffer_alloc(JSContext *ctx, size_t size);
+JSValue qjs_buffer_chb_alloc(JSContext *ctx, njs_chb_t *chain);
+
+typedef int (*qjs_buffer_encode_t)(JSContext *ctx, const njs_str_t *src,
+    njs_str_t *dst);
+typedef size_t (*qjs_buffer_encode_length_t)(JSContext *ctx,
+    const njs_str_t *src);
+
+typedef struct {
+    njs_str_t                   name;
+    qjs_buffer_encode_t         encode;
+    qjs_buffer_encode_length_t  encode_length;
+    qjs_buffer_encode_t         decode;
+    qjs_buffer_encode_length_t  decode_length;
+} qjs_buffer_encoding_t;
+
+const qjs_buffer_encoding_t *qjs_buffer_encoding(JSContext *ctx,
+    JSValueConst value, JS_BOOL thrw);
+
+
+typedef struct {
+    int                         tag;
+    size_t                      length;
+    u_char                      *start;
+} qjs_bytes_t;
+
+int qjs_to_bytes(JSContext *ctx, qjs_bytes_t *data, JSValueConst value);
+void qjs_bytes_free(JSContext *ctx, qjs_bytes_t *data);
+JSValue qjs_typed_array_data(JSContext *ctx, JSValueConst value,
+    njs_str_t *data);
+
+
+extern qjs_module_t              *qjs_modules[];
+
+#endif /* _QJS_H_INCLUDED_ */
diff --git a/src/qjs_buffer.c b/src/qjs_buffer.c
new file mode 100644 (file)
index 0000000..166eb97
--- /dev/null
@@ -0,0 +1,1174 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) F5, Inc.
+ */
+
+#include <qjs.h>
+
+static JSValue qjs_buffer(JSContext *ctx, JSValueConst this_val, int argc,
+    JSValueConst *argv);
+static JSValue qjs_buffer_from(JSContext *ctx, JSValueConst this_val, int argc,
+    JSValueConst *argv);
+static JSValue qjs_buffer_is_buffer(JSContext *ctx, JSValueConst this_val,
+    int argc, JSValueConst *argv);
+static JSValue qjs_buffer_prototype_to_json(JSContext *ctx,
+    JSValueConst this_val, int argc, JSValueConst *argv);
+static JSValue qjs_buffer_prototype_to_string(JSContext *ctx,
+    JSValueConst this_val, int argc, JSValueConst *argv);
+static JSValue qjs_buffer_from_string(JSContext *ctx, JSValueConst str,
+    JSValueConst encoding);
+static JSValue qjs_buffer_from_typed_array(JSContext *ctx, JSValueConst obj,
+    size_t offset, size_t size, size_t bytes, int float32);
+static JSValue qjs_buffer_from_array_buffer(JSContext *ctx, u_char *buf,
+    size_t size, JSValueConst offset, JSValueConst length);
+static JSValue qjs_buffer_from_object(JSContext *ctx, JSValueConst obj);
+static int qjs_base64_encode(JSContext *ctx, const njs_str_t *src,
+    njs_str_t *dst);
+static size_t qjs_base64_encode_length(JSContext *ctx, const njs_str_t *src);
+static int qjs_base64_decode(JSContext *ctx, const njs_str_t *src,
+    njs_str_t *dst);
+static size_t qjs_base64_decode_length(JSContext *ctx, const njs_str_t *src);
+static int qjs_base64url_encode(JSContext *ctx, const njs_str_t *src,
+    njs_str_t *dst);
+static int qjs_base64url_decode(JSContext *ctx, const njs_str_t *src,
+    njs_str_t *dst);
+static size_t qjs_base64url_decode_length(JSContext *ctx, const njs_str_t *src);
+static int qjs_hex_encode(JSContext *ctx, const njs_str_t *src, njs_str_t *dst);
+static size_t qjs_hex_encode_length(JSContext *ctx, const njs_str_t *src);
+static int qjs_hex_decode(JSContext *ctx, const njs_str_t *src, njs_str_t *dst);
+static size_t qjs_hex_decode_length(JSContext *ctx, const njs_str_t *src);
+static JSValue qjs_new_uint8_array(JSContext *ctx, size_t size);
+static JSModuleDef *qjs_buffer_init(JSContext *ctx, const char *name);
+
+
+static qjs_buffer_encoding_t  qjs_buffer_encodings[] =
+{
+    {
+        njs_str("utf-8"),
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+    },
+
+    {
+        njs_str("utf8"),
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+    },
+
+    {
+        njs_str("base64"),
+        qjs_base64_encode,
+        qjs_base64_encode_length,
+        qjs_base64_decode,
+        qjs_base64_decode_length,
+    },
+
+    {
+        njs_str("base64url"),
+        qjs_base64url_encode,
+        qjs_base64_encode_length,
+        qjs_base64url_decode,
+        qjs_base64url_decode_length,
+    },
+
+    {
+        njs_str("hex"),
+        qjs_hex_encode,
+        qjs_hex_encode_length,
+        qjs_hex_decode,
+        qjs_hex_decode_length,
+    },
+
+    { njs_null_str, 0, 0, 0, 0 }
+};
+
+
+static const JSCFunctionListEntry qjs_buffer_constants[] = {
+    JS_PROP_INT32_DEF("MAX_LENGTH", INT32_MAX, JS_PROP_ENUMERABLE),
+    JS_PROP_INT32_DEF("MAX_STRING_LENGTH", 0x3fffffff, JS_PROP_ENUMERABLE),
+};
+
+
+static const JSCFunctionListEntry qjs_buffer_export[] = {
+    JS_OBJECT_DEF("constants",
+                  qjs_buffer_constants,
+                  njs_nitems(qjs_buffer_constants),
+                  JS_PROP_CONFIGURABLE),
+};
+
+
+static const JSCFunctionListEntry qjs_buffer_props[] = {
+    JS_CFUNC_DEF("from", 3, qjs_buffer_from),
+    JS_CFUNC_DEF("isBuffer", 1, qjs_buffer_is_buffer),
+};
+
+
+static const JSCFunctionListEntry qjs_buffer_proto[] = {
+    JS_CFUNC_DEF("toJSON", 0, qjs_buffer_prototype_to_json),
+    JS_CFUNC_DEF("toString", 1, qjs_buffer_prototype_to_string),
+};
+
+
+static JSClassDef qjs_buffer_class = {
+    "Buffer",
+    .finalizer = NULL,
+};
+
+
+static JSClassID qjs_buffer_class_id;
+
+#ifndef NJS_HAVE_QUICKJS_NEW_TYPED_ARRAY
+static JSClassDef qjs_uint8_array_ctor_class = {
+    "Uint8ArrayConstructor",
+    .finalizer = NULL,
+};
+
+static JSClassID qjs_uint8_array_ctor_id;
+#endif
+
+
+qjs_module_t  qjs_buffer_module = {
+    .name = "buffer",
+    .init = qjs_buffer_init,
+};
+
+
+static u_char   qjs_basis64[] = {
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 62, 77, 77, 77, 63,
+    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 77, 77, 77, 77, 77, 77,
+    77,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 77, 77, 77, 77, 77,
+    77, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 77, 77, 77, 77, 77,
+
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77
+};
+
+
+static u_char   qjs_basis64url[] = {
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 62, 77, 77,
+    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 77, 77, 77, 77, 77, 77,
+    77,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 77, 77, 77, 77, 63,
+    77, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 77, 77, 77, 77, 77,
+
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+    77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77
+};
+
+
+static u_char   qjs_basis64_enc[] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static u_char   qjs_basis64url_enc[] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
+
+#define qjs_base64_encoded_length(len)       (((len + 2) / 3) * 4)
+#define qjs_base64_decoded_length(len, pad)  (((len / 4) * 3) - pad)
+
+
+static JSValue
+qjs_buffer(JSContext *ctx, JSValueConst this_val, int argc,
+    JSValueConst *argv)
+{
+    JS_ThrowTypeError(ctx, "Buffer() is deprecated. Use the Buffer.alloc() "
+                      "or Buffer.from() methods instead.");
+
+    return JS_EXCEPTION;
+}
+
+
+static JSValue
+qjs_buffer_from(JSContext *ctx, JSValueConst this_val, int argc,
+    JSValueConst *argv)
+{
+    int         float32;
+    size_t      off, size, bytes;
+    u_char      *buf;
+    JSValue     ret, ctor, name, obj, valueOf;
+    const char  *str;
+
+    if (JS_IsString(argv[0])) {
+        return qjs_buffer_from_string(ctx, argv[0], argv[1]);
+
+    } else if (ret = JS_GetTypedArrayBuffer(ctx, argv[0], &off, &size, &bytes),
+               !JS_IsException(ret))
+    {
+        float32 = 0;
+
+        if (bytes == 4) {
+            /*
+             * API workaround for JS_GetTypedArrayBuffer()
+             * that does not distinguish between Float32Array and Uint32Array.
+             */
+            ctor = JS_GetPropertyStr(ctx, argv[0], "constructor");
+            if (JS_IsException(ctor)) {
+                JS_FreeValue(ctx, ret);
+                return ctor;
+            }
+
+            name = JS_GetPropertyStr(ctx, ctor, "name");
+            if (JS_IsException(name)) {
+                JS_FreeValue(ctx, ret);
+                return name;
+            }
+
+            JS_FreeValue(ctx, ctor);
+            str = JS_ToCString(ctx, name);
+
+            if (strncmp(str, "Float32Array", 12) == 0) {
+                float32 = 1;
+            }
+
+            JS_FreeCString(ctx, str);
+            JS_FreeValue(ctx, name);
+        }
+
+        return qjs_buffer_from_typed_array(ctx, ret, off, size, bytes, float32);
+
+    } else if ((buf = JS_GetArrayBuffer(ctx, &size, argv[0])) != NULL) {
+        return qjs_buffer_from_array_buffer(ctx, buf, size, argv[1], argv[2]);
+
+    } else if (JS_IsObject(argv[0])) {
+        obj = argv[0];
+        valueOf = JS_GetPropertyStr(ctx, obj, "valueOf");
+        if (JS_IsException(valueOf)) {
+            return valueOf;
+        }
+
+        if (JS_IsFunction(ctx, valueOf)) {
+            ret = JS_Call(ctx, valueOf, obj, 0, NULL);
+            JS_FreeValue(ctx, valueOf);
+            if (JS_IsException(ret)) {
+                return ret;
+            }
+
+            if (JS_IsString(ret)) {
+                obj = ret;
+                ret = qjs_buffer_from_string(ctx, obj, argv[1]);
+                JS_FreeValue(ctx, obj);
+                return ret;
+            }
+
+            if (JS_IsObject(ret)
+                && JS_VALUE_GET_PTR(ret) != JS_VALUE_GET_PTR(obj))
+            {
+                obj = ret;
+                ret = qjs_buffer_from_object(ctx, obj);
+                JS_FreeValue(ctx, obj);
+                return ret;
+            }
+
+            JS_FreeValue(ctx, ret);
+        }
+
+        return qjs_buffer_from_object(ctx, obj);
+    }
+
+    JS_ThrowTypeError(ctx, "first argument is not a string "
+                      "or Buffer-like object");
+    return JS_EXCEPTION;
+}
+
+
+static JSValue
+qjs_buffer_is_buffer(JSContext *ctx, JSValueConst this_val,
+    int argc, JSValueConst *argv)
+{
+    JSValue proto, buffer_proto, ret;
+
+    proto = JS_GetPrototype(ctx, argv[0]);
+    buffer_proto = JS_GetClassProto(ctx, qjs_buffer_class_id);
+
+    ret = JS_NewBool(ctx, JS_VALUE_GET_TAG(argv[0]) == JS_TAG_OBJECT &&
+                     JS_VALUE_GET_OBJ(buffer_proto) == JS_VALUE_GET_OBJ(proto));
+
+    JS_FreeValue(ctx, buffer_proto);
+    JS_FreeValue(ctx, proto);
+
+    return ret;
+}
+
+
+static JSValue
+qjs_buffer_prototype_to_json(JSContext *ctx, JSValueConst this_val, int argc,
+    JSValueConst *argv)
+{
+    int         rc;
+    JSValue     obj, data, ret;
+    njs_str_t   src;
+    njs_uint_t  i;
+
+    ret = qjs_typed_array_data(ctx, this_val, &src);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    obj = JS_NewObject(ctx);
+    if (JS_IsException(obj)) {
+        return obj;
+    }
+
+    data = JS_NewArray(ctx);
+    if (JS_IsException(data)) {
+        JS_FreeValue(ctx, obj);
+        return data;
+    }
+
+    rc = JS_DefinePropertyValueStr(ctx, obj, "type",
+                                   JS_NewString(ctx, "Buffer"),
+                                   JS_PROP_ENUMERABLE);
+    if (rc == -1) {
+        JS_FreeValue(ctx, obj);
+        JS_FreeValue(ctx, data);
+        return ret;
+    }
+
+    rc = JS_DefinePropertyValueStr(ctx, obj, "data", data, JS_PROP_ENUMERABLE);
+    if (rc == -1) {
+        JS_FreeValue(ctx, obj);
+        JS_FreeValue(ctx, data);
+        return ret;
+    }
+
+    for (i = 0; i < src.length; i++) {
+        rc = JS_SetPropertyUint32(ctx, data, i, JS_NewInt32(ctx, src.start[i]));
+        if (rc == -1) {
+            JS_FreeValue(ctx, obj);
+            JS_FreeValue(ctx, data);
+            return ret;
+        }
+    }
+
+    return obj;
+}
+
+
+
+static JSValue
+qjs_buffer_prototype_to_string(JSContext *ctx, JSValueConst this_val,
+    int argc, JSValueConst *argv)
+{
+    JSValue                      ret;
+    njs_str_t                    src, data;
+    const qjs_buffer_encoding_t  *encoding;
+
+    ret = qjs_typed_array_data(ctx, this_val, &src);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    if (JS_IsUndefined(argv[0]) || src.length == 0) {
+        return JS_NewStringLen(ctx, (char *) src.start, src.length);
+    }
+
+    encoding = qjs_buffer_encoding(ctx, argv[0], 1);
+    if (njs_slow_path(encoding == NULL)) {
+        return JS_EXCEPTION;
+    }
+
+    if (encoding->encode_length == NULL) {
+        return JS_NewStringLen(ctx, (char *) src.start, src.length);
+    }
+
+    data.length = encoding->encode_length(ctx, &src);
+    data.start = js_malloc(ctx, data.length);
+    if (njs_slow_path(data.start == NULL)) {
+        JS_ThrowOutOfMemory(ctx);
+        return JS_EXCEPTION;
+    }
+
+    if (encoding->encode(ctx, &src, &data) != 0) {
+        js_free(ctx, data.start);
+        JS_ThrowTypeError(ctx, "failed to encode buffer");
+        return JS_EXCEPTION;
+    }
+
+    ret = JS_NewStringLen(ctx, (char *) data.start, data.length);
+
+    js_free(ctx, data.start);
+
+    return ret;
+}
+
+
+static JSValue
+qjs_buffer_from_string(JSContext *ctx, JSValueConst str,
+    JSValueConst enc)
+{
+    size_t                       size;
+    JSValue                      buffer, ret;
+    njs_str_t                    src, dst;
+    const qjs_buffer_encoding_t  *encoding;
+
+    encoding = qjs_buffer_encoding(ctx, enc, 1);
+    if (njs_slow_path(encoding == NULL)) {
+        return JS_EXCEPTION;
+    }
+
+    src.start = (u_char *) JS_ToCStringLen(ctx, &src.length, str);
+
+    if (encoding->decode_length != NULL) {
+        size = encoding->decode_length(ctx, &src);
+
+    } else {
+        size = src.length;
+    }
+
+    buffer = qjs_buffer_alloc(ctx, size);
+    if (JS_IsException(buffer)) {
+        JS_FreeCString(ctx, (char *) src.start);
+        return buffer;
+    }
+
+    ret = qjs_typed_array_data(ctx, buffer, &dst);
+    if (JS_IsException(ret)) {
+        JS_FreeCString(ctx, (char *) src.start);
+        return ret;
+    }
+
+    if (encoding->decode != NULL) {
+        if (encoding->decode(ctx, &src, &dst) != 0) {
+            JS_FreeCString(ctx, (char *) src.start);
+            JS_ThrowTypeError(ctx, "failed to decode string");
+            return JS_EXCEPTION;
+        }
+
+    } else {
+        memcpy(dst.start, src.start, src.length);
+    }
+
+    JS_FreeCString(ctx, (char *) src.start);
+
+    return buffer;
+}
+
+
+static JSValue
+qjs_buffer_from_typed_array(JSContext *ctx, JSValueConst arr_buf,
+    size_t offset, size_t size, size_t bytes, int float32)
+{
+    float      *f32;
+    u_char     *p, *u8;
+    size_t     i;
+    double     *f64;
+    JSValue    buffer, ret;
+    uint16_t   *u16;
+    uint32_t   *u32;
+    njs_str_t  src, dst;
+
+    size = size / bytes;
+    buffer = qjs_buffer_alloc(ctx, size);
+    if (JS_IsException(buffer)) {
+        JS_FreeValue(ctx, arr_buf);
+        return buffer;
+    }
+
+    ret = qjs_typed_array_data(ctx, buffer, &dst);
+    if (JS_IsException(ret)) {
+        JS_FreeValue(ctx, arr_buf);
+        JS_FreeValue(ctx, buffer);
+        return ret;
+    }
+
+    src.start = JS_GetArrayBuffer(ctx, &src.length, arr_buf);
+    if (src.start == NULL) {
+        JS_FreeValue(ctx, arr_buf);
+        JS_FreeValue(ctx, buffer);
+        return JS_EXCEPTION;
+    }
+
+    p = dst.start;
+
+    switch (bytes) {
+    case 1:
+        u8 = src.start;
+        memcpy(p, u8 + offset, size);
+        break;
+
+    case 2:
+        u16 = (uint16_t *) src.start;
+
+        for (i = 0; i < size; i++) {
+            *p++ = u16[offset + i];
+        }
+
+        break;
+
+    case 4:
+        if (float32) {
+            f32 = (float *) src.start;
+
+            for (i = 0; i < size; i++) {
+                *p++ = f32[offset + i];
+            }
+
+            break;
+        }
+
+        u32 = (uint32_t *) src.start;
+
+        for (i = 0; i < size; i++) {
+            *p++ = u32[offset + i];
+        }
+
+        break;
+
+    case 8:
+        f64 = (double *) src.start;
+
+        for (i = 0; i < size; i++) {
+            *p++ = f64[offset + i];
+        }
+
+        break;
+    }
+
+    JS_FreeValue(ctx, arr_buf);
+
+    return buffer;
+}
+
+static JSValue
+qjs_buffer_from_array_buffer(JSContext *ctx, u_char *buf, size_t size,
+    JSValueConst offset, JSValueConst length)
+{
+    JSValue    buffer, ret;
+    int64_t    len;
+    uint64_t   off;
+    njs_str_t  dst;
+
+    if (JS_ToIndex(ctx, &off, offset)) {
+        return JS_EXCEPTION;
+    }
+
+    if ((size_t) off > size) {
+        JS_ThrowRangeError(ctx, "\"offset\" is outside of buffer bounds");
+        return JS_EXCEPTION;
+    }
+
+    if (JS_IsUndefined(length)) {
+        len = size - off;
+
+    } else {
+        if (JS_ToInt64(ctx, &len, length)) {
+            return JS_EXCEPTION;
+        }
+
+        if (len < 0) {
+            len = 0;
+        }
+
+        if ((size_t) (off + len) > size) {
+            JS_ThrowRangeError(ctx, "\"length\" is outside of buffer bounds");
+            return JS_EXCEPTION;
+        }
+    }
+
+    buffer = qjs_buffer_alloc(ctx, len);
+    if (JS_IsException(buffer)) {
+        return buffer;
+    }
+
+    ret = qjs_typed_array_data(ctx, buffer, &dst);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    memcpy(dst.start, buf + off, len);
+
+    return buffer;
+}
+
+
+static JSValue
+qjs_buffer_from_object(JSContext *ctx, JSValueConst obj)
+{
+    int         v;
+    u_char      *p;
+    int64_t     i, len;
+    JSValue     buffer, ret;
+    njs_str_t   dst;
+    const char  *str;
+
+    ret = JS_GetPropertyStr(ctx, obj, "length");
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    if (JS_IsUndefined(ret)) {
+        ret = JS_GetPropertyStr(ctx, obj, "type");
+        if (JS_IsException(ret)) {
+            return ret;
+        }
+
+        if (JS_IsString(ret)) {
+            str = JS_ToCString(ctx, ret);
+            JS_FreeValue(ctx, ret);
+            if (str != NULL) {
+                if (strcmp(str, "Buffer") != 0) {
+                    JS_FreeCString(ctx, str);
+                    goto reject;
+                }
+
+                JS_FreeCString(ctx, str);
+
+                ret = JS_GetPropertyStr(ctx, obj, "data");
+                if (JS_IsException(ret)) {
+                    return ret;
+                }
+
+                if (JS_IsObject(ret)) {
+                    obj = ret;
+                    ret = qjs_buffer_from_object(ctx, obj);
+                    JS_FreeValue(ctx, obj);
+                    return ret;
+                }
+            }
+        }
+    }
+
+    if (!JS_IsNumber(ret)) {
+        JS_FreeValue(ctx, ret);
+reject:
+        JS_ThrowTypeError(ctx, "first argument is not a string "
+                          "or Buffer-like object");
+        return JS_EXCEPTION;
+    }
+
+    len = JS_VALUE_GET_INT(ret);
+
+    buffer = qjs_buffer_alloc(ctx, len);
+    if (JS_IsException(buffer)) {
+        return buffer;
+    }
+
+    ret = qjs_typed_array_data(ctx, buffer, &dst);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    p = dst.start;
+
+    for (i = 0; i < len; i++) {
+        ret = JS_GetPropertyUint32(ctx, obj, i);
+        if (njs_slow_path(JS_IsException(ret))) {
+            return ret;
+        }
+
+        if (njs_slow_path(JS_ToInt32(ctx, &v, ret))) {
+            return JS_EXCEPTION;
+        }
+
+        JS_FreeValue(ctx, ret);
+
+        *p++ = v;
+    }
+
+    return buffer;
+}
+
+
+const qjs_buffer_encoding_t *
+qjs_buffer_encoding(JSContext *ctx, JSValueConst value, JS_BOOL thrw)
+{
+    njs_str_t              name;
+    qjs_buffer_encoding_t  *encoding;
+
+    if (!JS_IsString(value)){
+        if (!JS_IsUndefined(value)) {
+            JS_ThrowTypeError(ctx, "encoding must be a string");
+            return NULL;
+        }
+
+        return &qjs_buffer_encodings[0];
+    }
+
+    name.start = (u_char *) JS_ToCStringLen(ctx, &name.length, value);
+
+    for (encoding = &qjs_buffer_encodings[0];
+         encoding->name.length != 0;
+         encoding++)
+    {
+        if (njs_strstr_eq(&name, &encoding->name)) {
+            JS_FreeCString(ctx, (char *) name.start);
+            return encoding;
+        }
+    }
+
+    JS_FreeCString(ctx, (char *) name.start);
+
+    if (thrw) {
+        JS_ThrowTypeError(ctx, "\"%*s\" encoding is not supported",
+                          (int) name.length, name.start);
+    }
+
+    return NULL;
+}
+
+
+static void
+qjs_base64_encode_core(njs_str_t *dst, const njs_str_t *src,
+    const u_char *basis, njs_bool_t padding)
+{
+   u_char  *d, *s, c0, c1, c2;
+   size_t  len;
+
+    len = src->length;
+    s = src->start;
+    d = dst->start;
+
+    while (len > 2) {
+        c0 = s[0];
+        c1 = s[1];
+        c2 = s[2];
+
+        *d++ = basis[c0 >> 2];
+        *d++ = basis[((c0 & 0x03) << 4) | (c1 >> 4)];
+        *d++ = basis[((c1 & 0x0f) << 2) | (c2 >> 6)];
+        *d++ = basis[c2 & 0x3f];
+
+        s += 3;
+        len -= 3;
+    }
+
+    if (len > 0) {
+        c0 = s[0];
+        *d++ = basis[c0 >> 2];
+
+        if (len == 1) {
+            *d++ = basis[(c0 & 0x03) << 4];
+            if (padding) {
+                *d++ = '=';
+                *d++ = '=';
+            }
+
+        } else {
+            c1 = s[1];
+
+            *d++ = basis[((c0 & 0x03) << 4) | (c1 >> 4)];
+            *d++ = basis[(c1 & 0x0f) << 2];
+
+            if (padding) {
+                *d++ = '=';
+            }
+        }
+
+    }
+
+    dst->length = d - dst->start;
+}
+
+
+static int
+qjs_base64_encode(JSContext *ctx, const njs_str_t *src, njs_str_t *dst)
+{
+    qjs_base64_encode_core(dst, src, qjs_basis64_enc, 1);
+
+    return 0;
+}
+
+
+static size_t
+qjs_base64_encode_length(JSContext *ctx, const njs_str_t *src)
+{
+    return qjs_base64_encoded_length(src->length);
+}
+
+
+static void
+qjs_base64_decode_core(njs_str_t *dst, const njs_str_t *src,
+    const u_char *basis)
+{
+    size_t  len;
+    u_char  *d, *s;
+
+    s = src->start;
+    d = dst->start;
+
+    len = dst->length;
+
+    while (len >= 3) {
+        *d++ = (u_char) (basis[s[0]] << 2 | basis[s[1]] >> 4);
+        *d++ = (u_char) (basis[s[1]] << 4 | basis[s[2]] >> 2);
+        *d++ = (u_char) (basis[s[2]] << 6 | basis[s[3]]);
+
+        s += 4;
+        len -= 3;
+    }
+
+    if (len >= 1) {
+        *d++ = (u_char) (basis[s[0]] << 2 | basis[s[1]] >> 4);
+    }
+
+    if (len >= 2) {
+        *d++ = (u_char) (basis[s[1]] << 4 | basis[s[2]] >> 2);
+    }
+}
+
+
+static size_t
+qjs_base64_decode_length_core(const njs_str_t *src, const u_char *basis)
+{
+    uint    pad;
+    size_t  len;
+
+    for (len = 0; len < src->length; len++) {
+        if (basis[src->start[len]] == 77) {
+            break;
+        }
+    }
+
+    pad = 0;
+
+    if (len % 4 != 0) {
+        pad = 4 - (len % 4);
+        len += pad;
+    }
+
+    return qjs_base64_decoded_length(len, pad);
+}
+
+
+static int
+qjs_base64_decode(JSContext *ctx, const njs_str_t *src, njs_str_t *dst)
+{
+    qjs_base64_decode_core(dst, src, qjs_basis64);
+
+    return 0;
+}
+
+
+static size_t
+qjs_base64_decode_length(JSContext *ctx, const njs_str_t *src)
+{
+    return qjs_base64_decode_length_core(src, qjs_basis64);
+}
+
+
+static int
+qjs_base64url_encode(JSContext *ctx, const njs_str_t *src, njs_str_t *dst)
+{
+    qjs_base64_encode_core(dst, src, qjs_basis64url_enc, 1);
+
+    return 0;
+}
+
+
+static int
+qjs_base64url_decode(JSContext *ctx, const njs_str_t *src, njs_str_t *dst)
+{
+    qjs_base64_decode_core(dst, src, qjs_basis64url);
+
+    return 0;
+}
+
+
+static size_t
+qjs_base64url_decode_length(JSContext *ctx, const njs_str_t *src)
+{
+    return qjs_base64_decode_length_core(src, qjs_basis64url);
+}
+
+
+njs_inline njs_int_t
+qjs_char_to_hex(u_char c)
+{
+    c |= 0x20;
+
+    /* Values less than '0' become >= 208. */
+    c = c - '0';
+
+    if (c > 9) {
+        /* Values less than 'a' become >= 159. */
+        c = c - ('a' - '0');
+
+        if (njs_slow_path(c > 5)) {
+            return -1;
+        }
+
+        c += 10;
+    }
+
+    return c;
+}
+
+
+static int
+qjs_hex_decode(JSContext *ctx, const njs_str_t *src, njs_str_t *dst)
+{
+    u_char        *p;
+    size_t        len;
+    njs_int_t     c;
+    njs_uint_t    i, n;
+    const u_char  *start;
+
+    n = 0;
+    p = dst->start;
+
+    start = src->start;
+    len = src->length;
+
+    for (i = 0; i < len; i++) {
+        c = qjs_char_to_hex(start[i]);
+        if (njs_slow_path(c < 0)) {
+            break;
+        }
+
+        n = n * 16 + c;
+
+        if ((i & 1) != 0) {
+            *p++ = (u_char) n;
+            n = 0;
+        }
+    }
+
+    dst->length -= (dst->start + dst->length) - p;
+
+    return 0;
+}
+
+
+static size_t
+qjs_hex_decode_length(JSContext *ctx, const njs_str_t *src)
+{
+    const u_char  *p, *end;
+
+    p = src->start;
+    end = p + src->length;
+
+    for (; p < end; p++) {
+        if (njs_slow_path(qjs_char_to_hex(*p) < 0)) {
+            break;
+        }
+    }
+
+    return (p - src->start) / 2;
+}
+
+
+static int
+qjs_hex_encode(JSContext *ctx, const njs_str_t *src, njs_str_t *dst)
+{
+    u_char        *p, c;
+    size_t        i, len;
+    const u_char  *start;
+
+    static const u_char  hex[16] = "0123456789abcdef";
+
+    len = src->length;
+    start = src->start;
+
+    p = dst->start;
+
+    for (i = 0; i < len; i++) {
+        c = start[i];
+        *p++ = hex[c >> 4];
+        *p++ = hex[c & 0x0f];
+    }
+
+    return 0;
+}
+
+
+static size_t
+qjs_hex_encode_length(JSContext *ctx, const njs_str_t *src)
+{
+    return src->length * 2;
+}
+
+
+JSValue
+qjs_buffer_alloc(JSContext *ctx, size_t size)
+{
+    JSValue  ret, proto;
+
+    ret = qjs_new_uint8_array(ctx, size);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    proto = JS_GetClassProto(ctx, qjs_buffer_class_id);
+    JS_SetPrototype(ctx, ret, proto);
+    JS_FreeValue(ctx, proto);
+
+    return ret;
+}
+
+
+JSValue
+qjs_buffer_chb_alloc(JSContext *ctx, njs_chb_t *chain)
+{
+    ssize_t      size;
+    JSValue      ret;
+    qjs_bytes_t  bytes;
+
+    size = njs_chb_size(chain);
+    if (njs_slow_path(size < 0)) {
+        JS_ThrowOutOfMemory(ctx);
+        return JS_EXCEPTION;
+    }
+
+    ret = qjs_buffer_alloc(ctx, size);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    (void) qjs_to_bytes(ctx, &bytes, ret);
+
+    njs_chb_join_to(chain, bytes.start);
+    qjs_bytes_free(ctx, &bytes);
+
+    return ret;
+}
+
+
+static JSValue
+qjs_new_uint8_array(JSContext *ctx, size_t size)
+{
+    JSValue  ret, value;
+
+    value = JS_NewInt64(ctx, size);
+
+#ifdef NJS_HAVE_QUICKJS_NEW_TYPED_ARRAY
+    ret = JS_NewTypedArray(ctx, 1, &value, JS_TYPED_ARRAY_UINT8);
+#else
+    JSValue ctor;
+
+    ctor = JS_GetClassProto(ctx, qjs_uint8_array_ctor_id);
+    ret = JS_CallConstructor(ctx, ctor, 1, &value);
+    JS_FreeValue(ctx, ctor);
+#endif
+
+    JS_FreeValue(ctx, value);
+
+    return ret;
+}
+
+
+static int
+qjs_buffer_builtin_init(JSContext *ctx)
+{
+    int        rc;
+    JSValue    global_obj, buffer, proto, ctor, ta, ta_proto;
+    JSClassID  u8_ta_class_id;
+
+    JS_NewClassID(&qjs_buffer_class_id);
+    JS_NewClass(JS_GetRuntime(ctx), qjs_buffer_class_id, &qjs_buffer_class);
+
+    proto = JS_NewObject(ctx);
+    JS_SetPropertyFunctionList(ctx, proto, qjs_buffer_proto,
+                               njs_nitems(qjs_buffer_proto));
+
+    global_obj = JS_GetGlobalObject(ctx);
+
+    ctor = JS_GetPropertyStr(ctx, global_obj, "Uint8Array");
+
+#ifndef NJS_HAVE_QUICKJS_NEW_TYPED_ARRAY
+    /*
+     * Workaround for absence of JS_NewTypedArray() in QuickJS.
+     * We use JS_SetClassProto()/JS_GetClassProto() as a key-value store
+     * for fast value query by class ID without querying the global object.
+     */
+    JS_NewClassID(&qjs_uint8_array_ctor_id);
+    JS_NewClass(JS_GetRuntime(ctx), qjs_uint8_array_ctor_id,
+                &qjs_uint8_array_ctor_class);
+    JS_SetClassProto(ctx, qjs_uint8_array_ctor_id, JS_DupValue(ctx, ctor));
+#endif
+
+    ta = JS_CallConstructor(ctx, ctor, 0, NULL);
+    u8_ta_class_id = JS_GetClassID(ta);
+    JS_FreeValue(ctx, ta);
+    JS_FreeValue(ctx, ctor);
+
+    ta_proto = JS_GetClassProto(ctx, u8_ta_class_id);
+    JS_SetPrototype(ctx, proto, ta_proto);
+    JS_FreeValue(ctx, ta_proto);
+
+    JS_SetClassProto(ctx, qjs_buffer_class_id, proto);
+
+    buffer = JS_NewCFunction2(ctx, qjs_buffer, "Buffer", 0,
+                              JS_CFUNC_generic, 0);
+    if (JS_IsException(buffer)) {
+        return -1;
+    }
+
+    JS_SetPropertyFunctionList(ctx, buffer, qjs_buffer_props,
+                               njs_nitems(qjs_buffer_props));
+
+    rc = JS_SetPropertyStr(ctx, global_obj, "Buffer", buffer);
+    if (rc == -1) {
+        return -1;
+    }
+
+    JS_FreeValue(ctx, global_obj);
+
+    return 0;
+}
+
+
+static int
+qjs_buffer_module_init(JSContext *ctx, JSModuleDef *m)
+{
+    int      rc;
+    JSValue  proto;
+
+    proto = JS_NewObject(ctx);
+    JS_SetPropertyFunctionList(ctx, proto, qjs_buffer_export,
+                               njs_nitems(qjs_buffer_export));
+
+    rc = JS_SetModuleExport(ctx, m, "default", proto);
+    if (rc != 0) {
+        return -1;
+    }
+
+    return JS_SetModuleExportList(ctx, m, qjs_buffer_export,
+                                  njs_nitems(qjs_buffer_export));
+}
+
+
+static JSModuleDef *
+qjs_buffer_init(JSContext *ctx, const char *name)
+{
+    int          rc;
+    JSModuleDef  *m;
+
+    qjs_buffer_builtin_init(ctx);
+
+    m = JS_NewCModule(ctx, name, qjs_buffer_module_init);
+    if (m == NULL) {
+        return NULL;
+    }
+
+    JS_AddModuleExport(ctx, m, "default");
+    rc = JS_AddModuleExportList(ctx, m, qjs_buffer_export,
+                                njs_nitems(qjs_buffer_export));
+    if (rc != 0) {
+        return NULL;
+    }
+
+    return m;
+}
index e34a5a28de46462abc50f79b97909aac8b7b861c..2fa7e01ff6d14f2f4fdb8388ae854f25264723ca 100644 (file)
@@ -21069,217 +21069,12 @@ static njs_unit_test_t  njs_buffer_module_test[] =
              "].every(args => Buffer.byteLength(args[0], args[1])  == args[2])"),
       njs_str("true") },
 
-    { njs_str("Buffer.from({length:5, 0:'A'.charCodeAt(0), 2:'X', 3:NaN,4:0xfd}).toString('hex')"),
-      njs_str("41000000fd") },
-
-    { njs_str("Buffer.from([1, 2, 0.23, '5', 'A']).toString('hex')"),
-      njs_str("0102000500") },
-
-    { njs_str("Buffer.from([NaN, Infinity]).toString('hex')"),
-      njs_str("0000") },
-
-    { njs_str("Buffer.from(new Uint8Array([0xff,0xde,0xba])).toString('hex')"),
-      njs_str("ffdeba") },
-
-    { njs_str("Buffer.from((new Uint8Array([0xff,0xde,0xba])).buffer).toString('hex')"),
-      njs_str("ffdeba") },
-
-    { njs_str("["
-             " ['', ''],"
-             " ['aa0', 'aa'],"
-             " ['00aabbcc', '00aabbcc'],"
-             " [new String('00aabbcc'), '00aabbcc'],"
-             " ['deadBEEF##', 'deadbeef'],"
-             "].every(args => { "
-             "    if (Buffer.from(args[0], 'hex').toString('hex') != args[1]) {"
-             "        throw `Buffer.from(\"${args[0]}\", 'hex').toString('hex') != \"${args[1]}\"`;"
-             "    }"
-             "    return true;"
-             "})"),
-      njs_str("true") },
-
-    { njs_str("["
-             " ['', ''],"
-             " ['#', ''],"
-             " ['Q', ''],"
-             " ['QQ', 'A'],"
-             " ['QQ=', 'A'],"
-             " ['QQ==', 'A'],"
-             " ['QUI=', 'AB'],"
-             " ['QUI', 'AB'],"
-             " ['QUJD', 'ABC'],"
-             " ['QUJDRA==', 'ABCD'],"
-             "].every(args => { "
-             "    if (Buffer.from(args[0], 'base64') != args[1]) {"
-             "        throw `Buffer.from(\"${args[0]}\", 'base64') != \"${args[1]}\"`;"
-             "    }"
-             "    return true;"
-             "})"),
-      njs_str("true") },
-
-    { njs_str("["
-             " ['', ''],"
-             " ['QQ', 'A'],"
-             " ['QUI', 'AB'],"
-             " ['QUJD', 'ABC'],"
-             " ['QUJDRA', 'ABCD'],"
-             " ['QUJDRA#', 'ABCD'],"
-             "].every(args => { "
-             "    if (Buffer.from(args[0], 'base64url') != args[1]) {"
-             "        throw `Buffer.from(\"${args[0]}\", 'base64url') != \"${args[1]}\"`;"
-             "    }"
-             "    return true;"
-             "})"),
-      njs_str("true") },
-
-    { njs_str("Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72])"),
-      njs_str("buffer") },
-
-    { njs_str(njs_declare_sparse_array("arr", 6)
-              "[0x62, 0x75, 0x66, 0x66, 0x65, 0x72].map((v, i) => {arr[i] = v;});"
-              "Buffer.from(arr)"),
-      njs_str("buffer") },
-
-    { njs_str("Buffer.from({length:3, 0:0x62, 1:0x75, 2:0x66})"),
-      njs_str("buf") },
-
-    { njs_str("njs.dump(Buffer.from([-1,1,255,22323,-Infinity,Infinity,NaN]))"),
-      njs_str("Buffer [255,1,255,51,0,0,0]") },
-
-    { njs_str("var buf = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]); njs.dump(buf)"),
-      njs_str("Buffer [98,117,102,102,101,114]") },
-
-    { njs_str("var buf = Buffer.from([1,2,3]); njs.dump(Buffer.from(buf.toJSON()))"),
-      njs_str("Buffer [1,2,3]") },
-
-    { njs_str("["
-              " {type: 'B'},"
-              " {type: undefined},"
-              " {type:'Buffer'},"
-              " {type:'Buffer', data:null},"
-              " {type:'Buffer', data:{}},"
-              "].every(v=>{ try { Buffer.from(v)} catch(e) {return e.name == 'TypeError'}})"),
-      njs_str("true") },
-
-    { njs_str("var foo = new Uint16Array(2);"
-              "foo[0] = 5000; foo[1] = 4000;"
-              "var buf = Buffer.from(foo.buffer);"
-              "foo[1] = 6000;"
-              "njs.dump(buf)"),
-      njs_str("Buffer [" njs_evar("136,19,112,23", "19,136,23,112") "]") },
-
-    { njs_str("var foo = new Uint16Array(2).fill(950);"
-              "var buf = Buffer.from(foo.buffer, 1); njs.dump(buf)"),
-      njs_str("Buffer [" njs_evar("3,182,3", "182,3,182") "]") },
-
-    { njs_str("var foo = new Uint16Array(2).fill(950);"
-              "var buf = Buffer.from(foo.buffer, -1); njs.dump(buf)"),
-      njs_str("RangeError: invalid index") },
-
-    { njs_str("var foo = new Uint16Array(2).fill(950);"
-              "var buf = Buffer.from(foo.buffer, 5); njs.dump(buf)"),
-      njs_str("RangeError: \"offset\" is outside of buffer bounds") },
-
-    { njs_str("var foo = new Uint16Array(2).fill(950);"
-              "var buf = Buffer.from(foo.buffer, 2, 1); njs.dump(buf)"),
-      njs_str("Buffer [" njs_evar("182", "3") "]") },
-
-    { njs_str("var foo = new Uint16Array(2).fill(950);"
-              "var buf = Buffer.from(foo.buffer, 2, -1); njs.dump(buf)"),
-      njs_str("Buffer []") },
-
-    { njs_str("var foo = new Uint16Array(2).fill(950);"
-              "var buf = Buffer.from(foo.buffer, 2, 3); njs.dump(buf)"),
-      njs_str("RangeError: \"length\" is outside of buffer bounds") },
-
-    { njs_str("var foo = new Uint16Array(2).fill(950);"
-              "var buf = Buffer.from(foo.buffer, 2, 0); njs.dump(buf)"),
-      njs_str("Buffer []") },
-
-    { njs_str("var foo = new Uint16Array(2).fill(950);"
-              "var buf = Buffer.from(foo.buffer, 2, 2); njs.dump(buf)"),
-      njs_str("Buffer [" njs_evar("182,3", "3,182") "]") },
-
-    { njs_str("var foo = new Uint16Array(2).fill(950);"
-              "var buf = Buffer.from(foo.buffer, '2', '2'); njs.dump(buf)"),
-      njs_str("Buffer [" njs_evar("182,3", "3,182") "]") },
-
-    { njs_str("var foo = new Uint32Array(1).fill(0xF1F2F3F4);"
-              "var buf = Buffer.from(foo); njs.dump(buf)"),
-      njs_str("Buffer [244]") },
-
-    { njs_str("var foo = new Uint32Array(2).fill(0xF1F2F3F4);"
-              "var buf = Buffer.from(foo); njs.dump(buf)"),
-      njs_str("Buffer [244,244]") },
-
     { njs_str("var foo = new Uint8Array(5);"
               "foo[0] = 1; foo[1] = 2; foo[2] = 3; foo[3] = 4; foo[4] = 5;"
               "foo = foo.subarray(1, 3);"
               "var buf = Buffer.from(foo); njs.dump(buf)"),
       njs_str("Buffer [2,3]") },
 
-    { njs_str("var buf = Buffer.from(''); njs.dump(buf)"),
-      njs_str("Buffer []") },
-
-    { njs_str("var buf = Buffer.from('α'); njs.dump(buf)"),
-      njs_str("Buffer [206,177]") },
-
-    { njs_str("var arr = new Array(1,2,3); arr.valueOf = () => arr;"
-              "njs.dump(Buffer.from(arr))"),
-      njs_str("Buffer [1,2,3]") },
-
-    { njs_str("var obj = new Object(); obj.valueOf = () => obj;"
-              "Buffer.from(obj)"),
-      njs_str("TypeError: first argument object is not a string or Buffer-like object") },
-
-    { njs_str("var obj = new Object(); obj.valueOf = () => undefined;"
-              "njs.dump(Buffer.from(obj))"),
-      njs_str("TypeError: first argument undefined is not a string or Buffer-like object") },
-
-    { njs_str("var arr = new Array(1,2,3); arr.valueOf = () => null;"
-              "njs.dump(Buffer.from(arr))"),
-      njs_str("Buffer [1,2,3]") },
-
-    { njs_str("var obj = new Object(); obj.valueOf = () => new Array(1,2,3);"
-              "njs.dump(Buffer.from(obj))"),
-      njs_str("Buffer [1,2,3]") },
-
-    { njs_str("njs.dump(Buffer.from(new String('test')))"),
-      njs_str("Buffer [116,101,115,116]") },
-
-    { njs_str("Buffer.from({ get type() { throw new Error('test'); } })"),
-      njs_str("Error: test") },
-
-    { njs_str("Buffer.from({ type: 'Buffer', get data() { throw new Error('test'); } })"),
-      njs_str("Error: test") },
-
-    { njs_str("var a = [1,2,3,4]; a[1] = { valueOf() { a.length = 3; return 1; } };"
-              "njs.dump(Buffer.from(a))"),
-      njs_str("Buffer [1,1,3,0]") },
-
-    { njs_str("var a = [1,2,3,4]; a[1] = { valueOf() { a.length = 4096; a.fill(13); return 1; } };"
-              "njs.dump(Buffer.from(a))"),
-      njs_str("Buffer [1,1,13,13]") },
-
-    { njs_str("["
-             " ['6576696c', 'hex'],"
-             " ['ZXZpbA==', 'base64'],"
-             " ['ZXZpbA==#', 'base64'],"
-             " ['ZXZpbA', 'base64url'],"
-             " ['ZXZpbA##', 'base64url'],"
-             "].every(args => Buffer.from(args[0], args[1]) == 'evil')"),
-      njs_str("true") },
-
-    { njs_str("var buf = Buffer.from($262.byteString([0xF3])); buf"),
-      njs_str("�") },
-
-    { njs_str("Buffer.from('', 'utf-128')"),
-      njs_str("TypeError: \"utf-128\" encoding is not supported") },
-
-    { njs_str("[Buffer.from('α'), new Uint8Array(10), {}, 1]"
-              ".map(v=>Buffer.isBuffer(v))"),
-      njs_str("true,false,false,false") },
-
     { njs_str("['utf8', 'utf-8', 'hex', 'base64', 'base64url', 'utf-88', '1hex']"
               ".map(v=>Buffer.isEncoding(v))"),
       njs_str("true,true,true,true,true,false,false") },
@@ -21760,12 +21555,6 @@ static njs_unit_test_t  njs_buffer_module_test[] =
               "Buffer [4,3,2,1,8,7,6,5],"
               "Buffer [8,7,6,5,4,3,2,1]") },
 
-    { njs_str("njs.dump(Buffer.from('αααα').toJSON())"),
-      njs_str("{type:'Buffer',data:[206,177,206,177,206,177,206,177]}") },
-
-    { njs_str("njs.dump(Buffer.from('').toJSON())"),
-      njs_str("{type:'Buffer',data:[]}") },
-
     { njs_str("["
               " [['base64'], 'ZXZpbA=='],"
               " [['base64url'], 'ZXZpbA'],"
@@ -21785,9 +21574,6 @@ static njs_unit_test_t  njs_buffer_module_test[] =
               "})"),
       njs_str("true") },
 
-    { njs_str("Buffer.from('evil').toString('utf-128')"),
-      njs_str("TypeError: \"utf-128\" encoding is not supported") },
-
     { njs_str("var buf = Buffer.allocUnsafe(4);"
               "var len = buf.write('ZXZpbA==', 'base64'); [len, buf]"),
       njs_str("4,evil") },
@@ -22306,82 +22092,6 @@ static njs_unit_test_t  njs_xml_test[] =
 };
 
 
-static njs_unit_test_t  njs_zlib_test[] =
-{
-    { njs_str("const zlib = require('zlib');"
-              "['C3f0dgQA', 'O7fx3KZzmwE=']"
-              ".map(v => zlib.inflateRawSync(Buffer.from(v, 'base64')).toString())"),
-      njs_str("WAKA,αβγ") },
-
-    { njs_str("const zlib = require('zlib');"
-              "['eJwLd/R2BAAC+gEl', 'eJw7t/HcpnObAQ/sBIE=']"
-              ".map(v => zlib.inflateSync(Buffer.from(v, 'base64')).toString())"),
-      njs_str("WAKA,αβγ") },
-
-    { njs_str("const zlib = require('zlib');"
-              "const enc = ['WAKA', 'αβγ'].map(v => zlib.deflateRawSync(v).toString('base64'));"
-              "enc.map(v => zlib.inflateRawSync(Buffer.from(v, 'base64')).toString())"),
-      njs_str("WAKA,αβγ") },
-
-    { njs_str("const zlib = require('zlib');"
-              "const enc = ['WAKA', 'αβγ']"
-              ".map(v => zlib.deflateRawSync(v, {dictionary: Buffer.from('WAKA')}).toString('base64'));"
-              "enc.map(v => zlib.inflateRawSync(Buffer.from(v, 'base64'), {dictionary: Buffer.from('WAKA')}))"),
-      njs_str("WAKA,αβγ") },
-
-    { njs_str("const zlib = require('zlib');"
-              "['WAKA', 'αβγ']"
-              ".map(v => zlib.deflateRawSync(v, {level: zlib.constants.Z_NO_COMPRESSION}).toString('base64'))"),
-      njs_str("AQQA+/9XQUtB,AQYA+f/Osc6yzrM=") },
-
-    { njs_str("const zlib = require('zlib');"
-              "[zlib.constants.Z_FIXED,  zlib.constants.Z_RLE]"
-              ".map(v => zlib.deflateRawSync('WAKA'.repeat(10), {strategy: v}).toString('base64'))"),
-      njs_str("C3f0dgwnAgMA,BcExAQAAAMKgbNwLYP8mwmQymUwmk8lkcg==") },
-
-    { njs_str("const zlib = require('zlib');"
-              "[1, 8]"
-              ".map(v => zlib.deflateRawSync('WAKA'.repeat(35),"
-              "                              {strategy: zlib.constants.Z_RLE, memLevel: v})"
-              "          .toString('base64'))"),
-      njs_str("BMExAQAAAMKgbNwLYP8mwmQymUwmk8lkMplMJpPJZDKZTCaTyWQymUwmk+lzDHf0dgx39HYMd/R2BAA=,"
-              "BcExAQAAAMKgbNwLYP8mwmQymUwmk8lkMplMJpPJZDKZTCaTyWQymUwmk8lkMjk=") },
-
-    { njs_str("const zlib = require('zlib');"
-              "['WAKA', 'αβγ']"
-              ".map(v => zlib.deflateSync(v).toString('base64'))"),
-      njs_str("eJwLd/R2BAAC+gEl,eJw7t/HcpnObAQ/sBIE=") },
-
-    { njs_str("const zlib = require('zlib');"
-              "const buf = 'αβγ'.repeat(56);"
-              "const enc = zlib.deflateRawSync(buf, {chunkSize:64}).toString('base64');"
-              "const dec = zlib.inflateRawSync(Buffer.from(enc, 'base64')).toString();"
-              "buf == dec"),
-      njs_str("true") },
-
-    { njs_str("const zlib = require('zlib');"
-              "['WAKA'.repeat(1024), 'αβγ'.repeat(1024)]"
-              ".map(v => [v, zlib.deflateRawSync(v).toString('base64')])"
-              ".every(pair => pair[0] == zlib.inflateRawSync(Buffer.from(pair[1], 'base64')).toString())"),
-      njs_str("true") },
-
-    { njs_str("const zlib = require('zlib');"
-              "['WAKA'.repeat(1024), 'αβγ'.repeat(1024)]"
-              ".map(v => [v, zlib.deflateRawSync(v, {chunkSize:64}).toString('base64')])"
-              ".every(pair => pair[0] == zlib.inflateRawSync(Buffer.from(pair[1], 'base64'),"
-              "                                              {chunkSize:64}).toString())"),
-      njs_str("true") },
-
-    { njs_str("const zlib = require('zlib');"
-              "['WAKA', 'αβγ']"
-              ".map(v => [v, zlib.deflateRawSync(v, {dictionary: Buffer.from('WAKA')}).toString('base64')])"
-              ".every(pair => pair[0] == zlib.inflateRawSync(Buffer.from(pair[1], 'base64'),"
-              "                                              {dictionary: Buffer.from('WAKA')}).toString())"),
-      njs_str("true") },
-
-};
-
-
 static njs_unit_test_t  njs_module_test[] =
 {
     { njs_str("function f(){return 2}; var f; f()"),
@@ -25115,17 +24825,6 @@ static njs_test_suite_t  njs_suites[] =
       njs_nitems(njs_xml_test),
       njs_unit_test },
 
-    {
-#if (NJS_HAVE_ZLIB && !NJS_HAVE_MEMORY_SANITIZER)
-        njs_str("zlib"),
-#else
-        njs_str(""),
-#endif
-      { .externals = 1, .repeat = 1, .unsafe = 1 },
-      njs_zlib_test,
-      njs_nitems(njs_zlib_test),
-      njs_unit_test },
-
     { njs_str("module"),
       { .repeat = 1, .module = 1, .unsafe = 1 },
       njs_module_test,
diff --git a/test/buffer.t.js b/test/buffer.t.js
new file mode 100644 (file)
index 0000000..1d96d34
--- /dev/null
@@ -0,0 +1,236 @@
+/*---
+includes: [compatBuffer.js, runTsuite.js]
+flags: [async]
+---*/
+
+function p(args, default_opts) {
+    let params = merge({}, default_opts);
+    params = merge(params, args);
+
+    return params;
+}
+
+let from_tsuite = {
+    name: "Buffer.from() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let buf = Buffer.from.apply(null, params.args);
+
+        if (params.modify) {
+            params.modify(buf);
+        }
+
+        let r = buf.toString(params.fmt);
+
+        if (r.length !== params.expected.length) {
+            throw Error(`unexpected "${r}" length ${r.length} != ${params.expected.length}`);
+        }
+
+        if (r !== params.expected) {
+            throw Error(`unexpected output "${r}" != "${params.expected}"`);
+        }
+
+        return 'SUCCESS';
+    },
+    prepare_args: p,
+    opts: { fmt: 'utf-8' },
+
+    tests: [
+        { args: [[0x62, 0x75, 0x66, 0x66, 0x65, 0x72]], expected: 'buffer' },
+        { args: [{length:3, 0:0x62, 1:0x75, 2:0x66}], expected: 'buf' },
+        { args: [[-1, 1, 255, 22323, -Infinity, Infinity, NaN]], fmt: "hex", expected: 'ff01ff33000000' },
+        { args: [{length:5, 0:'A'.charCodeAt(0), 2:'X', 3:NaN, 4:0xfd}], fmt: "hex", expected: '41000000fd' },
+        { args: [[1, 2, 0.23, '5', 'A']], fmt: "hex", expected: '0102000500' },
+        { args: [new Uint8Array([0xff, 0xde, 0xba])], fmt: "hex", expected: 'ffdeba' },
+
+        { args: [(new Uint8Array([0xaa, 0xbb, 0xcc])).buffer], fmt: "hex", expected: 'aabbcc' },
+        { args: [(new Uint8Array([0xaa, 0xbb, 0xcc])).buffer, 1], fmt: "hex", expected: 'bbcc' },
+        { args: [(new Uint8Array([0xaa, 0xbb, 0xcc])).buffer, 1, 1], fmt: "hex", expected: 'bb' },
+        { args: [(new Uint8Array([0xaa, 0xbb, 0xcc])).buffer, '1', '1'], fmt: "hex", expected: 'bb' },
+        { args: [(new Uint8Array([0xaa, 0xbb, 0xcc])).buffer, 1, 0], fmt: "hex", expected: '' },
+        { args: [(new Uint8Array([0xaa, 0xbb, 0xcc])).buffer, 1, -1], fmt: "hex", expected: '' },
+
+        { args: [(new Uint8Array([0xaa, 0xbb, 0xcc])).buffer], fmt: "hex",
+          modify: (buf) => { buf[1] = 0; },
+          expected: 'aa00cc' },
+
+        { args: [new Uint16Array([234, 123])], fmt: "hex", expected: 'ea7b' },
+        { args: [new Uint32Array([234, 123])], fmt: "hex", expected: 'ea7b' },
+        { args: [new Float32Array([234.001, 123.11])], fmt: "hex", expected: 'ea7b' },
+        { args: [new Uint32Array([234, 123])], fmt: "hex", expected: 'ea7b' },
+        { args: [new Float64Array([234.001, 123.11])], fmt: "hex", expected: 'ea7b' },
+
+        { args: [(new Uint8Array(2)).buffer, -1],
+          exception: 'RangeError: invalid index' },
+        { args: [(new Uint8Array(2)).buffer, 3],
+          exception: 'RangeError: \"offset\" is outside of buffer bounds' },
+        { args: [(new Uint8Array(2)).buffer, 1, 2],
+          exception: 'RangeError: \"length\" is outside of buffer bounds' },
+
+        { args: [Buffer.from([0xaa, 0xbb, 0xcc]).toJSON()], fmt: "hex", expected: 'aabbcc' },
+        { args: [{type: 'Buffer', data: [0xaa, 0xbb, 0xcc]}], fmt: "hex", expected: 'aabbcc' },
+        { args: [new String('00aabbcc'), 'hex'], fmt: "hex", expected: '00aabbcc' },
+        { args: [Buffer.from([0xaa, 0xbb, 0xcc]).toJSON()], fmt: "hex", expected: 'aabbcc' },
+
+        { args: [(function() {var arr = new Array(1, 2, 3); arr.valueOf = () => arr; return arr})()],
+          fmt: "hex", expected: '010203' },
+        { args: [(function() {var obj = new Object(); obj.valueOf = () => obj; return obj})()],
+          exception: 'TypeError: first argument is not a string or Buffer-like object' },
+        { args: [(function() {var obj = new Object(); obj.valueOf = () => undefined; return obj})()],
+          exception: 'TypeError: first argument is not a string or Buffer-like object' },
+        { args: [(function() {var obj = new Object(); obj.valueOf = () => null; return obj})()],
+          exception: 'TypeError: first argument is not a string or Buffer-like object' },
+        { args: [(function() {var obj = new Object(); obj.valueOf = () => new Array(1, 2, 3); return obj})()],
+          fmt: "hex", expected: '010203' },
+        { args: [(function() {var a = [1,2,3,4]; a[1] = { valueOf() { a.length = 3; return 1; } }; return a})()],
+          fmt: "hex", expected: '01010300' },
+
+        { args: [{type: 'B'}],
+          exception: 'TypeError: first argument is not a string or Buffer-like object' },
+        { args: [{type: undefined}],
+          exception: 'TypeError: first argument is not a string or Buffer-like object' },
+        { args: [{type: 'Buffer'}],
+          exception: 'TypeError: first argument is not a string or Buffer-like object' },
+        { args: [{type: 'Buffer', data: null}],
+          exception: 'TypeError: first argument is not a string or Buffer-like object' },
+        { args: [{type: 'Buffer', data: {}}],
+          exception: 'TypeError: first argument is not a string or Buffer-like object' },
+
+        { args: ['', 'utf-128'], exception: 'TypeError: "utf-128" encoding is not supported' },
+
+        { args: [''], fmt: "hex", expected: '' },
+        { args: ['α'], fmt: "hex", expected: 'ceb1' },
+        { args: ['α', 'utf-8'], fmt: "hex", expected: 'ceb1' },
+        { args: ['α', 'utf8'], fmt: "hex", expected: 'ceb1' },
+        { args: ['', 'hex'], fmt: "hex", expected: '' },
+        { args: ['aa0', 'hex'], fmt: "hex", expected: 'aa' },
+        { args: ['00aabbcc', 'hex'], fmt: "hex", expected: '00aabbcc' },
+        { args: ['deadBEEF##', 'hex'], fmt: "hex", expected: 'deadbeef' },
+        { args: ['6576696c', 'hex'], expected: 'evil' },
+        { args: ['f3', 'hex'], expected: '�' },
+
+        { args: ['', "base64"], expected: '' },
+        { args: ['#', "base64"], expected: '' },
+        { args: ['Q', "base64"], expected: '' },
+        { args: ['QQ', "base64"], expected: 'A' },
+        { args: ['QQ=', "base64"], expected: 'A' },
+        { args: ['QQ==', "base64"], expected: 'A' },
+        { args: ['QUI=', "base64"], expected: 'AB' },
+        { args: ['QUI', "base64"], expected: 'AB' },
+        { args: ['QUJD', "base64"], expected: 'ABC' },
+        { args: ['QUJDRA==', "base64"], expected: 'ABCD' },
+
+        { args: ['', "base64url"], expected: '' },
+        { args: ['QQ', "base64url"], expected: 'A' },
+        { args: ['QUI', "base64url"], expected: 'AB' },
+        { args: ['QUJD', "base64url"], expected: 'ABC' },
+        { args: ['QUJDRA', "base64url"], expected: 'ABCD' },
+        { args: ['QUJDRA#', "base64url"], expected: 'ABCD' },
+]};
+
+let isBuffer_tsuite = {
+    name: "Buffer.isBuffer() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let r = Buffer.isBuffer(params.value);
+
+        if (r !== params.expected) {
+            throw Error(`unexpected output "${r}" != "${params.expected}"`);
+        }
+
+        return 'SUCCESS';
+    },
+    prepare_args: p,
+    opts: {},
+
+    tests: [
+        { value: Buffer.from('α'), expected: true },
+        { value: new Uint8Array(10), expected: false },
+        { value: {}, expected: false },
+        { value: 1, expected: false },
+]};
+
+
+function compare_object(a, b) {
+    if (a === b) {
+        return true;
+    }
+
+    if (typeof a !== 'object' || typeof b !== 'object') {
+        return false;
+    }
+
+    if (Object.keys(a).length !== Object.keys(b).length) {
+        return false;
+    }
+
+    for (let key in a) {
+        if (!compare_object(a[key], b[key])) {
+            return false;
+        }
+
+    }
+
+    return true;
+}
+
+
+let toJSON_tsuite = {
+    name: "Buffer.toJSON() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let r = Buffer.from(params.value).toJSON();
+
+        if (!compare_object(r, params.expected)) {
+            throw Error(`unexpected output "${JSON.stringify(r)}" != "${JSON.stringify(params.expected)}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: {},
+    tests: [
+        { value: '', expected: { type: 'Buffer', data: [] } },
+        { value: 'αβγ', expected: { type: 'Buffer', data: [0xCE, 0xB1, 0xCE, 0xB2, 0xCE, 0xB3] } },
+        { value: new Uint8Array([0xff, 0xde, 0xba]), expected: { type: 'Buffer', data: [0xFF, 0xDE, 0xBA] } },
+    ],
+};
+
+
+let toString_tsuite = {
+    name: "Buffer.toString() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let r = Buffer.from(params.value).toString(params.fmt);
+
+        if (r.length !== params.expected.length) {
+            throw Error(`unexpected "${r}" length ${r.length} != ${params.expected.length}`);
+        }
+
+        if (r !== params.expected) {
+            throw Error(`unexpected output "${r}" != "${params.expected}"`);
+        }
+
+        return 'SUCCESS';
+    },
+    prepare_args: p,
+    opts: { fmt: 'utf-8' },
+
+    tests: [
+        { value: '💩', expected: '💩' },
+        { value: String.fromCharCode(0xD83D, 0xDCA9), expected: '💩' },
+        { value: String.fromCharCode(0xD83D, 0xDCA9), expected: String.fromCharCode(0xD83D, 0xDCA9) },
+        { value: new Uint8Array([0xff, 0xde, 0xba]), fmt: "hex", expected: 'ffdeba' },
+        { value: new Uint8Array([0xff, 0xde, 0xba]), fmt: "base64", expected: '/966' },
+        { value: new Uint8Array([0xff, 0xde, 0xba]), fmt: "base64url", expected: '_966' },
+        { value: '', fmt: "utf-128", exception: 'TypeError: "utf-128" encoding is not supported' },
+]};
+
+run([
+    from_tsuite,
+    isBuffer_tsuite,
+    toJSON_tsuite,
+    toString_tsuite,
+])
+.then($DONE, $DONE);
index 3d82680727cfe0a0c8c19ec8859edc7ad879ffe8..dc2034fd99f788c49e0960cc9fd5397feacdf025 100644 (file)
@@ -42,7 +42,15 @@ function merge(to, from) {
             r[v] = merge(r[v], from[v]);
 
         } else if (typeof from[v] == 'object') {
-            r[v] = Object.assign(Array.isArray(from[v]) ? [] : {}, from[v]);
+            if (Buffer.isBuffer(from[v])) {
+                r[v] = Buffer.from(from[v]);
+
+            } else if (from[v] instanceof Uint8Array) {
+                r[v] = new Uint8Array(from[v]);
+
+            } else {
+                r[v] = Object.assign(Array.isArray(from[v]) ? [] : {}, from[v]);
+            }
 
         } else {
             r[v] = from[v];
index ae7fcae16abedddc083cb8e1072bcae9a0a433ae..c4187b53009d47f2cc542d9db1d49ef500c44161 100644 (file)
@@ -38,7 +38,7 @@ njs_failed_list=""
 NJS_TESTS=""
 for arg in $NJS_TEST_PATHS; do
     if [ -d $arg ]; then
-        NJS_TESTS="$NJS_TESTS $(find $arg -name '*\.t\.js')"
+        NJS_TESTS="$NJS_TESTS $(find $arg -name '*\.t\.js' -o -name '*\.t\.mjs')"
     else
         NJS_TESTS="$NJS_TESTS $arg"
     fi
diff --git a/test/zlib.t.mjs b/test/zlib.t.mjs
new file mode 100644 (file)
index 0000000..d972f6f
--- /dev/null
@@ -0,0 +1,109 @@
+/*---
+includes: [runTsuite.js]
+flags: [async]
+---*/
+
+import zlib from 'zlib';
+
+function p(args, default_opts) {
+    let params = merge({}, default_opts);
+    params = merge(params, args);
+
+    return params;
+}
+
+let deflateSync_tsuite = {
+    name: "deflateSync()/deflateRawSync() tests",
+    skip: () => !zlib.deflateRawSync,
+    T: async (params) => {
+        const method = params.raw ? zlib.deflateRawSync : zlib.deflateSync;
+        const r = method(params.value, params.options).toString('base64');
+
+        if (r.length !== params.expected.length) {
+            throw Error(`unexpected "${r}" length ${r.length} != ${params.expected.length}`);
+        }
+
+        if (r !== params.expected) {
+            throw Error(`unexpected output "${r}" != "${params.expected}"`);
+        }
+
+        return 'SUCCESS';
+    },
+    prepare_args: p,
+    opts: { raw: true },
+
+    tests: [
+        { value: 'WAKA', expected: 'C3f0dgQA' },
+        { value: 'αβγ', expected: 'O7fx3KZzmwE=' },
+        { value: new Uint8Array([0x57, 0x41, 0x4b, 0x41]), expected: 'C3f0dgQA' },
+        { value: Buffer.from([0x57, 0x41, 0x4b, 0x41]), expected: 'C3f0dgQA' },
+        { value: 'WAKA', options: {level: zlib.constants.Z_NO_COMPRESSION}, expected: 'AQQA+/9XQUtB' },
+        { value: 'αβγ', options: {level: zlib.constants.Z_NO_COMPRESSION}, expected: 'AQYA+f/Osc6yzrM=' },
+        { value: 'WAKA'.repeat(10), options: {strategy: zlib.constants.Z_FIXED}, expected: 'C3f0dgwnAgMA' },
+        { value: 'WAKA'.repeat(10), options: {strategy: zlib.constants.Z_RLE},
+          expected: 'BcExAQAAAMKgbNwLYP8mwmQymUwmk8lkcg==' },
+        { value: 'WAKA'.repeat(35), options: {strategy: zlib.constants.Z_RLE, memLevel: 1},
+          expected: 'BMExAQAAAMKgbNwLYP8mwmQymUwmk8lkMplMJpPJZDKZTCaTyWQymUwmk+lzDHf0dgx39HYMd/R2BAA=' },
+        { value: 'WAKA'.repeat(35), options: {strategy: zlib.constants.Z_RLE, memLevel: 8},
+          expected: 'BcExAQAAAMKgbNwLYP8mwmQymUwmk8lkMplMJpPJZDKZTCaTyWQymUwmk8lkMjk=' },
+        { value: 'WAKA', raw: false, expected: 'eJwLd/R2BAAC+gEl' },
+        { value: 'αβγ', raw: false, expected: 'eJw7t/HcpnObAQ/sBIE=' },
+
+        { value: 'WAKA', options: {level: 10}, exception: 'RangeError: level must be in the range -1..9' },
+        { value: 'WAKA', options: {strategy: 10}, exception: 'RangeError: unknown strategy: 10' },
+        { value: 'WAKA', options: {memLevel: 10}, exception: 'RangeError: memLevel must be in the range 1..9' },
+        { value: 'WAKA', options: {windowBits: 99}, exception: 'RangeError: windowBits must be in the range -15..-9' },
+]};
+
+let inflateSync_tsuite = {
+    name: "inflateSync()/inflateRawSync() tests",
+    skip: () => !zlib.inflateRawSync || !zlib.deflateRawSync,
+    T: async (params) => {
+        const method = params.raw ? zlib.inflateRawSync : zlib.inflateSync;
+        const r = method(params.value, params.options).toString();
+
+        if (r.length !== params.expected.length) {
+            throw Error(`unexpected "${r}" length ${r.length} != ${params.expected.length}`);
+        }
+
+        if (r !== params.expected) {
+            throw Error(`unexpected output "${r}" != "${params.expected}"`);
+        }
+
+        return 'SUCCESS';
+    },
+    prepare_args: p,
+    opts: { raw: true },
+
+    tests: [
+        { value: Buffer.from('C3f0dgQA', 'base64'), expected: 'WAKA' },
+        { value: Buffer.from('C3f0dgQA', 'base64'), expected: 'WAKA' },
+        { value: new Uint8Array([0x0b, 0x77, 0xf4, 0x76, 0x04, 0x00]), expected: 'WAKA' },
+        { value: Buffer.from('eJwLd/R2BAAC+gEl', 'base64'), raw: false, expected: 'WAKA' },
+        { value: Buffer.from('eJw7t/HcpnObAQ/sBIE=', 'base64'), raw: false, expected: 'αβγ' },
+        { value: zlib.deflateRawSync('WAKA'), expected: 'WAKA' },
+        { value: zlib.deflateRawSync('αβγ'), expected: 'αβγ' },
+        { value: zlib.deflateRawSync('WAKA', {dictionary: Buffer.from('WAKA')}), options: {dictionary: Buffer.from('WAKA')},
+          expected: 'WAKA' },
+        { value: zlib.deflateRawSync('αβγ', {dictionary: Buffer.from('αβγ')}), options: {dictionary: Buffer.from('αβγ')},
+          expected: 'αβγ' },
+        { value: zlib.deflateRawSync('αβγ'.repeat(56), {chunkSize: 64}), expected: 'αβγ'.repeat(56) },
+        { value: zlib.deflateRawSync('WAKA'.repeat(1024)), expected: 'WAKA'.repeat(1024) },
+        { value: zlib.deflateRawSync('αβγ'.repeat(1024)), expected: 'αβγ'.repeat(1024) },
+        { value: zlib.deflateRawSync('WAKA'.repeat(1024)), options: {chunkSize: 64}, expected: 'WAKA'.repeat(1024) },
+        { value: zlib.deflateRawSync('αβγ'.repeat(1024)), options: {chunkSize: 64}, expected: 'αβγ'.repeat(1024) },
+
+        { value: Buffer.from('C3f0dgQA', 'base64'), options: {chunkSize: 0},
+          exception: 'RangeError: chunkSize must be >= 64' },
+        { value: Buffer.from('C3f0dgQA', 'base64'), options: {windowBits: 0},
+          exception: 'RangeError: windowBits must be in the range -15..-8' },
+
+        { value: zlib.deflateRawSync('WAKA', {dictionary: Buffer.from('WAKA')}),
+          exception: 'InternalError: failed to inflate the compressed data: invalid distance too far back' },
+]};
+
+run([
+    deflateSync_tsuite,
+    inflateSync_tsuite,
+])
+.then($DONE, $DONE);