[njs] QuickJS: added buffer module.

noreply at nginx.com noreply at nginx.com
Fri Aug 16 01:36:02 UTC 2024


details:   https://github.com/nginx/njs/commit/d1c615eaa208a4e7c92b661f8f7674e92f19aedf
branches:  master
commit:    d1c615eaa208a4e7c92b661f8f7674e92f19aedf
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Wed, 7 Aug 2024 23:11:24 -0700
description:
QuickJS: added buffer module.


---
 src/qjs.h                 |    2 +
 src/qjs_buffer.c          | 1845 ++++++++++++++++++++++++++++++++++++++++-----
 src/test/njs_unit_test.c  |  817 --------------------
 test/buffer.t.js          |  815 +++++++++++++++++++-
 test/harness/runTsuite.js |    3 +
 5 files changed, 2465 insertions(+), 1017 deletions(-)

diff --git a/src/qjs.h b/src/qjs.h
index 71e23d78..00e9296a 100644
--- a/src/qjs.h
+++ b/src/qjs.h
@@ -16,6 +16,7 @@
 #include <njs_unicode.h>
 #include <njs_utf8.h>
 #include <njs_chb.h>
+#include <njs_utils.h>
 
 #if defined(__GNUC__) && (__GNUC__ >= 8)
 #pragma GCC diagnostic push
@@ -43,6 +44,7 @@ JSContext *qjs_new_context(JSRuntime *rt, _Bool eval);
 
 
 JSValue qjs_buffer_alloc(JSContext *ctx, size_t size);
+JSValue qjs_buffer_create(JSContext *ctx, u_char *start, 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,
diff --git a/src/qjs_buffer.c b/src/qjs_buffer.c
index 166eb970..83764e02 100644
--- a/src/qjs_buffer.c
+++ b/src/qjs_buffer.c
@@ -6,23 +6,75 @@
 
 #include <qjs.h>
 
+#define INT24_MAX  0x7FFFFF
+#define INT24_MIN  (-0x800000)
+#define INT40_MAX  0x7FFFFFFFFFLL
+#define INT40_MIN  (-0x8000000000LL)
+#define INT48_MAX  0x7FFFFFFFFFFFLL
+#define INT48_MIN  (-0x800000000000LL)
+#define UINT24_MAX 0xFFFFFFLL
+#define UINT40_MAX 0xFFFFFFFFFFLL
+#define UINT48_MAX 0xFFFFFFFFFFFFLL
+
+#define qjs_buffer_magic(size, sign, little)                                 \
+      ((size << 2) | (sign << 1) | little)
+
 static JSValue qjs_buffer(JSContext *ctx, JSValueConst this_val, int argc,
     JSValueConst *argv);
+static JSValue qjs_buffer_ctor(JSContext *ctx, JSValueConst this_val, int argc,
+    JSValueConst *argv);
+static JSValue qjs_bufferobj_alloc(JSContext *ctx, JSValueConst this_val,
+    int argc, JSValueConst *argv, int ignored);
+static JSValue qjs_buffer_byte_length(JSContext *ctx, JSValueConst this_val,
+    int argc, JSValueConst *argv);
+static JSValue qjs_buffer_compare(JSContext *ctx, JSValueConst this_val,
+    int argc, JSValueConst *argv);
+static JSValue qjs_buffer_concat(JSContext *ctx, JSValueConst this_val,
+    int argc, JSValueConst *argv);
+static JSValue qjs_buffer_fill(JSContext *ctx, JSValueConst buffer,
+    JSValueConst fill, JSValueConst encode, uint64_t offset, uint64_t end);
 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_is_encoding(JSContext *ctx, JSValueConst this_val,
+    int argc, JSValueConst *argv);
+static JSValue qjs_buffer_prototype_compare(JSContext *ctx,
+    JSValueConst this_val, int argc, JSValueConst *argv);
+static JSValue qjs_buffer_prototype_copy(JSContext *ctx,
+    JSValueConst this_val, int argc, JSValueConst *argv);
+static JSValue qjs_buffer_prototype_equals(JSContext *ctx,
+    JSValueConst this_val, int argc, JSValueConst *argv);
+static JSValue qjs_buffer_prototype_fill(JSContext *ctx,
+    JSValueConst this_val, int argc, JSValueConst *argv);
+static JSValue qjs_buffer_prototype_includes(JSContext *ctx,
+    JSValueConst this_val, int argc, JSValueConst *argv);
+static JSValue qjs_buffer_prototype_index_of(JSContext *ctx,
+    JSValueConst this_val, int argc, JSValueConst *argv, int last);
+static JSValue qjs_buffer_prototype_read_float(JSContext *ctx,
+    JSValueConst this_val, int argc, JSValueConst *argv, int magic);
+static JSValue qjs_buffer_prototype_read_int(JSContext *ctx,
+    JSValueConst this_val, int argc, JSValueConst *argv, int magic);
+static JSValue qjs_buffer_prototype_swap(JSContext *ctx, JSValueConst this_val,
+    int argc, JSValueConst *argv, int size);
 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_prototype_write(JSContext *ctx, JSValueConst this_val,
+    int argc, JSValueConst *argv);
+static JSValue qjs_buffer_prototype_write_int(JSContext *ctx,
+    JSValueConst this_val, int argc, JSValueConst *argv, int magic);
+static JSValue qjs_buffer_prototype_write_float(JSContext *ctx,
+    JSValueConst this_val, int argc, JSValueConst *argv, int magic);
 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 JSValue qjs_buffer_compare_array(JSContext *ctx, JSValue val1,
+    JSValue val2, JSValueConst target_start, JSValueConst target_end,
+    JSValueConst source_start, JSValueConst source_end);
 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);
@@ -38,7 +90,8 @@ 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 JSValue qjs_new_uint8_array(JSContext *ctx, int argc,
+    JSValueConst *argv);
 static JSModuleDef *qjs_buffer_init(JSContext *ctx, const char *name);
 
 
@@ -103,14 +156,103 @@ static const JSCFunctionListEntry qjs_buffer_export[] = {
 
 
 static const JSCFunctionListEntry qjs_buffer_props[] = {
+    JS_CFUNC_MAGIC_DEF("alloc", 3, qjs_bufferobj_alloc, 0),
+    JS_CFUNC_MAGIC_DEF("allocUnsafe", 3, qjs_bufferobj_alloc, 1),
+    JS_CFUNC_DEF("byteLength", 2, qjs_buffer_byte_length),
+    JS_CFUNC_DEF("compare", 6, qjs_buffer_compare),
+    JS_CFUNC_DEF("concat", 1, qjs_buffer_concat),
     JS_CFUNC_DEF("from", 3, qjs_buffer_from),
     JS_CFUNC_DEF("isBuffer", 1, qjs_buffer_is_buffer),
+    JS_CFUNC_DEF("isEncoding", 1, qjs_buffer_is_encoding),
 };
 
 
 static const JSCFunctionListEntry qjs_buffer_proto[] = {
+    JS_CFUNC_DEF("compare", 5, qjs_buffer_prototype_compare),
+    JS_CFUNC_DEF("copy", 5, qjs_buffer_prototype_copy),
+    JS_CFUNC_DEF("equals", 1, qjs_buffer_prototype_equals),
+    JS_CFUNC_DEF("fill", 4, qjs_buffer_prototype_fill),
+    JS_CFUNC_DEF("includes", 3, qjs_buffer_prototype_includes),
+    JS_CFUNC_MAGIC_DEF("indexOf", 3, qjs_buffer_prototype_index_of, 0),
+    JS_CFUNC_MAGIC_DEF("lastIndexOf", 3, qjs_buffer_prototype_index_of, 1),
+    JS_CFUNC_MAGIC_DEF("readFloatLE", 1, qjs_buffer_prototype_read_float,
+                       qjs_buffer_magic(4, 1, 1)),
+    JS_CFUNC_MAGIC_DEF("readFloatBE", 1, qjs_buffer_prototype_read_float,
+                       qjs_buffer_magic(4, 1, 0)),
+    JS_CFUNC_MAGIC_DEF("readDoubleLE", 1, qjs_buffer_prototype_read_float,
+                       qjs_buffer_magic(8, 1, 1)),
+    JS_CFUNC_MAGIC_DEF("readDoubleBE", 1, qjs_buffer_prototype_read_float,
+                       qjs_buffer_magic(8, 1, 0)),
+    JS_CFUNC_MAGIC_DEF("readInt8", 1, qjs_buffer_prototype_read_int,
+                       qjs_buffer_magic(1, 1, 1)),
+    JS_CFUNC_MAGIC_DEF("readUInt8", 1, qjs_buffer_prototype_read_int,
+                       qjs_buffer_magic(1, 0, 1)),
+    JS_CFUNC_MAGIC_DEF("readInt16LE", 1, qjs_buffer_prototype_read_int,
+                       qjs_buffer_magic(2, 1, 1)),
+    JS_CFUNC_MAGIC_DEF("readUInt16LE", 1, qjs_buffer_prototype_read_int,
+                       qjs_buffer_magic(2, 0, 1)),
+    JS_CFUNC_MAGIC_DEF("readInt16BE", 1, qjs_buffer_prototype_read_int,
+                       qjs_buffer_magic(2, 1, 0)),
+    JS_CFUNC_MAGIC_DEF("readUInt16BE", 1, qjs_buffer_prototype_read_int,
+                       qjs_buffer_magic(2, 0, 0)),
+    JS_CFUNC_MAGIC_DEF("readInt32LE", 1, qjs_buffer_prototype_read_int,
+                       qjs_buffer_magic(4, 1, 1)),
+    JS_CFUNC_MAGIC_DEF("readUInt32LE", 1, qjs_buffer_prototype_read_int,
+                       qjs_buffer_magic(4, 0, 1)),
+    JS_CFUNC_MAGIC_DEF("readInt32BE", 1, qjs_buffer_prototype_read_int,
+                       qjs_buffer_magic(4, 1, 0)),
+    JS_CFUNC_MAGIC_DEF("readUInt32BE", 1, qjs_buffer_prototype_read_int,
+                       qjs_buffer_magic(4, 0, 0)),
+    JS_CFUNC_MAGIC_DEF("readIntLE", 2, qjs_buffer_prototype_read_int,
+                       qjs_buffer_magic(0, 1, 1)),
+    JS_CFUNC_MAGIC_DEF("readUIntLE", 2, qjs_buffer_prototype_read_int,
+                       qjs_buffer_magic(0, 0, 1)),
+    JS_CFUNC_MAGIC_DEF("readIntBE", 2, qjs_buffer_prototype_read_int,
+                       qjs_buffer_magic(0, 1, 0)),
+    JS_CFUNC_MAGIC_DEF("readUIntBE", 2, qjs_buffer_prototype_read_int,
+                       qjs_buffer_magic(0, 0, 0)),
+    JS_CFUNC_MAGIC_DEF("swap16", 0, qjs_buffer_prototype_swap, 2),
+    JS_CFUNC_MAGIC_DEF("swap32", 0, qjs_buffer_prototype_swap, 4),
+    JS_CFUNC_MAGIC_DEF("swap64", 0, qjs_buffer_prototype_swap, 8),
     JS_CFUNC_DEF("toJSON", 0, qjs_buffer_prototype_to_json),
     JS_CFUNC_DEF("toString", 1, qjs_buffer_prototype_to_string),
+    JS_CFUNC_DEF("write", 4, qjs_buffer_prototype_write),
+    JS_CFUNC_MAGIC_DEF("writeInt8", 1, qjs_buffer_prototype_write_int,
+                       qjs_buffer_magic(1, 1, 1)),
+    JS_CFUNC_MAGIC_DEF("writeUInt8", 1, qjs_buffer_prototype_write_int,
+                       qjs_buffer_magic(1, 0, 1)),
+    JS_CFUNC_MAGIC_DEF("writeInt16LE", 1, qjs_buffer_prototype_write_int,
+                       qjs_buffer_magic(2, 1, 1)),
+    JS_CFUNC_MAGIC_DEF("writeUInt16LE", 1, qjs_buffer_prototype_write_int,
+                       qjs_buffer_magic(2, 0, 1)),
+    JS_CFUNC_MAGIC_DEF("writeInt16BE", 1, qjs_buffer_prototype_write_int,
+                       qjs_buffer_magic(2, 1, 0)),
+    JS_CFUNC_MAGIC_DEF("writeUInt16BE", 1, qjs_buffer_prototype_write_int,
+                       qjs_buffer_magic(2, 0, 0)),
+    JS_CFUNC_MAGIC_DEF("writeInt32LE", 1, qjs_buffer_prototype_write_int,
+                       qjs_buffer_magic(4, 1, 1)),
+    JS_CFUNC_MAGIC_DEF("writeUInt32LE", 1, qjs_buffer_prototype_write_int,
+                       qjs_buffer_magic(4, 0, 1)),
+    JS_CFUNC_MAGIC_DEF("writeInt32BE", 1, qjs_buffer_prototype_write_int,
+                       qjs_buffer_magic(4, 1, 0)),
+    JS_CFUNC_MAGIC_DEF("writeUInt32BE", 1, qjs_buffer_prototype_write_int,
+                       qjs_buffer_magic(4, 0, 0)),
+    JS_CFUNC_MAGIC_DEF("writeIntLE", 2, qjs_buffer_prototype_write_int,
+                       qjs_buffer_magic(0, 1, 1)),
+    JS_CFUNC_MAGIC_DEF("writeUIntLE", 2, qjs_buffer_prototype_write_int,
+                       qjs_buffer_magic(0, 0, 1)),
+    JS_CFUNC_MAGIC_DEF("writeIntBE", 2, qjs_buffer_prototype_write_int,
+                       qjs_buffer_magic(0, 1, 0)),
+    JS_CFUNC_MAGIC_DEF("writeUIntBE", 2, qjs_buffer_prototype_write_int,
+                       qjs_buffer_magic(0, 0, 0)),
+    JS_CFUNC_MAGIC_DEF("writeFloatLE", 2, qjs_buffer_prototype_write_float,
+                       qjs_buffer_magic(4, 1, 1)),
+    JS_CFUNC_MAGIC_DEF("writeFloatBE", 2, qjs_buffer_prototype_write_float,
+                       qjs_buffer_magic(4, 1, 0)),
+    JS_CFUNC_MAGIC_DEF("writeDoubleLE", 2, qjs_buffer_prototype_write_float,
+                       qjs_buffer_magic(8, 1, 1)),
+    JS_CFUNC_MAGIC_DEF("writeDoubleBE", 2, qjs_buffer_prototype_write_float,
+                       qjs_buffer_magic(8, 1, 0)),
 };
 
 
@@ -201,6 +343,288 @@ qjs_buffer(JSContext *ctx, JSValueConst this_val, int argc,
 }
 
 
+static JSValue
+qjs_buffer_ctor(JSContext *ctx, JSValueConst this_val, int argc,
+    JSValueConst *argv)
+{
+    JSValue  ret, proto;
+
+    ret = qjs_new_uint8_array(ctx, argc, argv);
+    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;
+}
+
+
+static JSValue
+qjs_bufferobj_alloc(JSContext *ctx, JSValueConst this_val, int argc,
+    JSValueConst *argv, int ignored)
+{
+    JSValue   buffer, ret;
+    uint32_t  size;
+
+    if (!JS_IsNumber(argv[0])) {
+        return JS_ThrowTypeError(ctx, "The \"size\" argument must be of type"
+                                 " number");
+    }
+
+    if (JS_ToUint32(ctx, &size, argv[0])) {
+        return JS_EXCEPTION;
+    }
+
+    buffer = qjs_buffer_alloc(ctx, size);
+    if (JS_IsException(buffer)) {
+        return buffer;
+    }
+
+    if (!JS_IsUndefined(argv[1])) {
+        ret = qjs_buffer_fill(ctx, buffer, argv[1], argv[2], 0, size);
+        if (JS_IsException(ret)) {
+            JS_FreeValue(ctx, buffer);
+            return ret;
+        }
+    }
+
+    return buffer;
+}
+
+
+static JSValue
+qjs_buffer_byte_length(JSContext *ctx, JSValueConst this_val, int argc,
+    JSValueConst *argv)
+{
+    size_t                       size;
+    JSValue                      ret;
+    njs_str_t                    src;
+    const qjs_buffer_encoding_t  *encoding;
+
+    if (JS_GetArrayBuffer(ctx, &size, argv[0]) != NULL) {
+        return JS_NewInt32(ctx, size);
+    }
+
+    ret = JS_GetTypedArrayBuffer(ctx, argv[0], NULL, &size, NULL);
+    if (!JS_IsException(ret)) {
+        JS_FreeValue(ctx, ret);
+        return JS_NewInt32(ctx, size);
+    }
+
+    if (!JS_IsString(argv[0])) {
+        return JS_ThrowTypeError(ctx, "first argument is not a string "
+                                 "or Buffer-like object");
+    }
+
+    encoding = qjs_buffer_encoding(ctx, argv[1], 1);
+    if (encoding == NULL) {
+        return JS_EXCEPTION;
+    }
+
+    src.start = (u_char *) JS_ToCStringLen(ctx, &src.length, argv[0]);
+
+    if (encoding->decode_length != NULL) {
+        size = encoding->decode_length(ctx, &src);
+
+    } else {
+        size = src.length;
+    }
+
+    JS_FreeCString(ctx, (char *) src.start);
+
+    return JS_NewInt32(ctx, size);
+}
+
+
+static JSValue
+qjs_buffer_compare(JSContext *ctx, JSValueConst this_val, int argc,
+    JSValueConst *argv)
+{
+    return qjs_buffer_compare_array(ctx, argv[0], argv[1], argv[2], argv[3],
+                                   argv[4], argv[5]);
+}
+
+
+static JSValue
+qjs_buffer_concat(JSContext *ctx, JSValueConst this_val, int argc,
+    JSValueConst *argv)
+{
+    u_char     *p;
+    size_t     n;
+    JSValue    list, length, val, ret, buffer;
+    uint32_t   i, len, list_len;
+    njs_str_t  buf, dst;
+
+    list = argv[0];
+
+    if (!JS_IsArray(ctx, list)) {
+        return JS_ThrowTypeError(ctx,
+                            "\"list\" argument must be an instance of Array");
+    }
+
+    length = JS_GetPropertyStr(ctx, list, "length");
+    if (JS_IsException(length)) {
+        return JS_EXCEPTION;
+    }
+
+    len = 0;
+    if (JS_ToUint32(ctx, &list_len, length)) {
+        JS_FreeValue(ctx, length);
+        return JS_EXCEPTION;
+    }
+
+    JS_FreeValue(ctx, length);
+
+    if (JS_IsUndefined(argv[1])) {
+        for (i = 0; i < list_len; i++) {
+            val = JS_GetPropertyUint32(ctx, list, i);
+            if (JS_IsException(val)) {
+                return JS_EXCEPTION;
+            }
+
+            ret = qjs_typed_array_data(ctx, val, &buf);
+            JS_FreeValue(ctx, val);
+            if (JS_IsException(ret)) {
+                return JS_ThrowTypeError(ctx, "\"list[%d]\" argument must be an"
+                                        " instance of Buffer or Uint8Array", i);
+            }
+
+            if ((SIZE_MAX - len) < buf.length) {
+                return JS_ThrowTypeError(ctx,
+                                         "Total size of buffers is too large");
+            }
+
+            len += buf.length;
+        }
+
+    } else {
+        if (JS_ToUint32(ctx, &len, argv[1])) {
+            return JS_EXCEPTION;
+        }
+    }
+
+    buffer = qjs_buffer_alloc(ctx, len);
+    if (JS_IsException(buffer)) {
+        return JS_EXCEPTION;
+    }
+
+    ret = qjs_typed_array_data(ctx, buffer, &dst);
+    if (JS_IsException(ret)) {
+        JS_FreeValue(ctx, buffer);
+        return JS_EXCEPTION;
+    }
+
+    p = dst.start;
+
+    for (i = 0; len != 0 && i < list_len; i++) {
+        val = JS_GetPropertyUint32(ctx, list, i);
+        if (JS_IsException(val)) {
+            JS_FreeValue(ctx, buffer);
+            return JS_EXCEPTION;
+        }
+
+        ret = qjs_typed_array_data(ctx, val, &buf);
+        if (JS_IsException(ret)) {
+            JS_FreeValue(ctx, buffer);
+            JS_FreeValue(ctx, val);
+            return JS_EXCEPTION;
+        }
+
+        JS_FreeValue(ctx, val);
+
+        n = njs_min((size_t) len, buf.length);
+        p = njs_cpymem(p, buf.start, n);
+
+        len -= n;
+    }
+
+    if (len != 0) {
+        njs_memzero(p, len);
+    }
+
+    return buffer;
+}
+
+
+static JSValue
+qjs_buffer_fill(JSContext *ctx, JSValueConst buffer, JSValueConst fill,
+    JSValueConst encode, uint64_t offset, uint64_t end)
+{
+    JSValue    ret, fill_buf;
+    uint32_t   n;
+    njs_str_t  dst, src;
+
+    ret = qjs_typed_array_data(ctx, buffer, &dst);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    if (end > dst.length) {
+        return JS_ThrowRangeError(ctx, "\"end\" is out of range");
+    }
+
+    if (offset >= end) {
+        return buffer;
+    }
+
+    if (JS_IsNumber(fill)) {
+        if (JS_ToUint32(ctx, &n, fill)) {
+            return JS_EXCEPTION;
+        }
+
+        memset(dst.start + offset, n & 0xff, end - offset);
+        return buffer;
+    }
+
+    fill_buf = JS_UNDEFINED;
+
+    if (JS_IsString(fill)) {
+        fill_buf = qjs_buffer_from_string(ctx, fill, encode);
+        if (JS_IsException(fill_buf)) {
+            return fill_buf;
+        }
+
+        fill = fill_buf;
+    }
+
+    ret = qjs_typed_array_data(ctx, fill, &src);
+    if (JS_IsException(ret)) {
+        JS_FreeValue(ctx, fill_buf);
+        return ret;
+    }
+
+    if (src.length == 0) {
+        memset(dst.start + offset, 0, end - offset);
+        JS_FreeValue(ctx, fill_buf);
+        return buffer;
+    }
+
+    if (src.start >= (dst.start + dst.length)
+        || dst.start >= (dst.start + dst.length))
+    {
+        while (offset < end) {
+            n = njs_min(src.length, end - offset);
+            memcpy(dst.start + offset, src.start, n);
+            offset += n;
+        }
+
+    } else {
+        while (offset < end) {
+            n = njs_min(src.length, end - offset);
+            memmove(dst.start + offset, src.start, n);
+            offset += n;
+        }
+    }
+
+    JS_FreeValue(ctx, fill_buf);
+
+    return buffer;
+}
+
+
 static JSValue
 qjs_buffer_from(JSContext *ctx, JSValueConst this_val, int argc,
     JSValueConst *argv)
@@ -230,188 +654,1223 @@ qjs_buffer_from(JSContext *ctx, JSValueConst this_val, int argc,
                 return ctor;
             }
 
-            name = JS_GetPropertyStr(ctx, ctor, "name");
-            if (JS_IsException(name)) {
-                JS_FreeValue(ctx, ret);
-                return name;
-            }
+            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_ctor(ctx, JS_UNDEFINED, argc, argv);
+
+    } 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_is_encoding(JSContext *ctx, JSValueConst this_val,
+    int argc, JSValueConst *argv)
+{
+    return JS_NewBool(ctx, qjs_buffer_encoding(ctx, argv[0], 0) != NULL);
+}
+
+
+static JSValue
+qjs_buffer_array_range(JSContext *ctx, njs_str_t *array, JSValueConst start,
+    JSValueConst end, const char *name)
+{
+    int64_t  num_start, num_end;
+
+    num_start = 0;
+
+    if (!JS_IsUndefined(start)) {
+        if (JS_ToInt64(ctx, &num_start, start)) {
+            return JS_EXCEPTION;
+        }
+    }
+
+    if (num_start < 0 || (size_t) num_start > array->length) {
+        return JS_ThrowRangeError(ctx, "\"%sStart\" is out of range: %ld",
+                                  name, num_start);
+    }
+
+    num_end = array->length;
+
+    if (!JS_IsUndefined(end)) {
+        if (JS_ToInt64(ctx, &num_end, end)) {
+            return JS_EXCEPTION;
+        }
+    }
+
+    if (num_end < 0 || (size_t) num_end > array->length) {
+        return JS_ThrowRangeError(ctx, "\"%sEnd\" is out of range: %ld",
+                                  name, num_end);
+    }
+
+    if (num_start > num_end) {
+        num_end = num_start;
+    }
+
+    array->start += num_start;
+    array->length = num_end - num_start;
+
+    return JS_UNDEFINED;
+}
+
+
+static JSValue
+qjs_buffer_compare_array(JSContext *ctx, JSValue val1, JSValue val2,
+    JSValueConst target_start, JSValueConst target_end,
+    JSValueConst source_start, JSValueConst source_end)
+{
+    int        rc;
+    size_t     size;
+    JSValue    ret;
+    njs_str_t  src, target;
+
+    ret = qjs_typed_array_data(ctx, val1, &src);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    ret = qjs_typed_array_data(ctx, val2, &target);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    ret = qjs_buffer_array_range(ctx, &src, source_start, source_end, "source");
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    ret = qjs_buffer_array_range(ctx, &target, target_start, target_end,
+                                 "target");
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    size = njs_min(src.length, target.length);
+
+    rc = memcmp(src.start, target.start, size);
+
+    if (rc != 0) {
+        return JS_NewInt32(ctx, (rc < 0) ? -1 : 1);
+    }
+
+    if (target.length > src.length) {
+        rc = -1;
+
+    } else if (target.length < src.length) {
+        rc = 1;
+    }
+
+    return JS_NewInt32(ctx, rc);
+}
+
+
+static JSValue
+qjs_buffer_prototype_compare(JSContext *ctx, JSValueConst this_val, int argc,
+    JSValueConst *argv)
+{
+    return qjs_buffer_compare_array(ctx, this_val, argv[0], argv[1], argv[2],
+                                   argv[3], argv[4]);
+}
+
+
+static JSValue
+qjs_buffer_prototype_copy(JSContext *ctx, JSValueConst this_val, int argc,
+    JSValueConst *argv)
+{
+    size_t     size;
+    JSValue    ret;
+    njs_str_t  src, target;
+
+    ret = qjs_typed_array_data(ctx, this_val, &src);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    ret = qjs_typed_array_data(ctx, argv[0], &target);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    ret = qjs_buffer_array_range(ctx, &target, argv[1], JS_UNDEFINED, "target");
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    ret = qjs_buffer_array_range(ctx, &src, argv[2], argv[3], "source");
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    size = njs_min(src.length, target.length);
+
+    if (src.start >= (target.start + size)
+        || target.start >= (src.start + size))
+    {
+        memcpy(target.start, src.start, size);
+
+    } else {
+        memmove(target.start, src.start, size);
+    }
+
+    return JS_NewInt32(ctx, size);
+}
+
+
+static JSValue
+qjs_buffer_prototype_equals(JSContext *ctx, JSValueConst this_val, int argc,
+    JSValueConst *argv)
+{
+    JSValue  ret;
+
+    ret = qjs_buffer_compare_array(ctx, this_val, argv[0], JS_UNDEFINED,
+                                   JS_UNDEFINED, JS_UNDEFINED, JS_UNDEFINED);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    return JS_NewBool(ctx, JS_VALUE_GET_INT(ret) == 0);
+}
+
+
+static JSValue
+qjs_buffer_prototype_fill(JSContext *ctx, JSValueConst this_val, int argc,
+    JSValueConst *argv)
+{
+    JSValue    ret, encode;
+    uint64_t   offset, end;
+    njs_str_t  dst;
+
+    offset = 0;
+    encode = argv[3];
+
+    ret = qjs_typed_array_data(ctx, this_val, &dst);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    end = dst.length;
+
+    if (!JS_IsUndefined(argv[1])) {
+        if (JS_IsString(argv[0]) && JS_IsString(argv[1])) {
+            encode = argv[1];
+            goto fill;
+        }
+
+        if (JS_ToIndex(ctx, &offset, argv[1])) {
+            return JS_EXCEPTION;
+        }
+    }
+
+    if (!JS_IsUndefined(argv[2])) {
+        if (JS_IsString(argv[0]) && JS_IsString(argv[2])) {
+            encode = argv[2];
+            goto fill;
+        }
+
+        if (JS_ToIndex(ctx, &end, argv[2])) {
+            return JS_EXCEPTION;
+        }
+    }
+
+fill:
+
+    ret = qjs_buffer_fill(ctx, this_val, argv[0], encode, offset, end);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    JS_DupValue(ctx, ret);
+
+    return ret;
+}
+
+
+static JSValue
+qjs_buffer_prototype_includes(JSContext *ctx, JSValueConst this_val, int argc,
+    JSValueConst *argv)
+{
+    JSValue    ret;
+
+    ret = qjs_buffer_prototype_index_of(ctx, this_val, argc, argv, 0);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    return JS_NewBool(ctx, JS_VALUE_GET_INT(ret) != -1);
+}
+
+
+static JSValue
+qjs_buffer_prototype_index_of(JSContext *ctx, JSValueConst this_val, int argc,
+    JSValueConst *argv, int last)
+{
+    JSValue                      ret, buffer, encode, value;
+    int64_t                      from, to, increment, length, i;
+    uint32_t                     byte;
+    njs_str_t                    self, str;
+    const qjs_buffer_encoding_t  *encoding;
+
+    ret = qjs_typed_array_data(ctx, this_val, &self);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    length = self.length;
+
+    if (length == 0) {
+        return JS_NewInt32(ctx, -1);
+    }
+
+    if (last) {
+        from = length - 1;
+        to = -1;
+        increment = -1;
+
+    } else {
+        from = 0;
+        to = length;
+        increment = 1;
+    }
+
+    encode = argv[2];
+
+    if (!JS_IsUndefined(argv[1])) {
+        if (JS_IsString(argv[0]) && JS_IsString(argv[1])) {
+            encode = argv[1];
+            goto encoding;
+        }
+
+        if (JS_ToInt64(ctx, &from, argv[1])) {
+            return JS_EXCEPTION;
+        }
+
+        if (last) {
+            if (from >= 0) {
+                from = njs_min(from, length - 1);
+
+            } else if (from < 0) {
+                from += length;
+            }
+
+            if (from <= to) {
+                return JS_NewInt32(ctx, -1);
+            }
+
+        } else {
+            if (from < 0) {
+                from += length;
+
+                if (from < 0) {
+                    from = 0;
+                }
+            }
+
+            if (from >= to) {
+                return JS_NewInt32(ctx, -1);
+            }
+        }
+    }
+
+    if (JS_IsNumber(argv[0])) {
+        if (JS_ToUint32(ctx, &byte, argv[0])) {
+            return JS_EXCEPTION;
+        }
+
+        for (i = from; i != to; i += increment) {
+            if (self.start[i] == (uint8_t) byte) {
+                return JS_NewInt32(ctx, i);
+            }
+        }
+
+        return JS_NewInt32(ctx, -1);
+    }
+
+encoding:
+
+    buffer = JS_UNDEFINED;
+    value = argv[0];
+
+    if (JS_IsString(value)) {
+        encoding = qjs_buffer_encoding(ctx, encode, 1);
+        if (encoding == NULL) {
+            return JS_EXCEPTION;
+        }
+
+        buffer = qjs_buffer_from_string(ctx, value, encode);
+        if (JS_IsException(buffer)) {
+            return buffer;
+        }
+
+        value = buffer;
+    }
+
+    ret = qjs_typed_array_data(ctx, value, &str);
+    if (JS_IsException(ret)) {
+        JS_FreeValue(ctx, buffer);
+        return JS_ThrowTypeError(ctx, "\"value\" argument is not a string "
+                                "or Buffer-like object");
+    }
+
+    if (str.length == 0) {
+        JS_FreeValue(ctx, buffer);
+        return JS_NewInt32(ctx, (last) ? length : 0);
+    }
+
+    if (str.length > (size_t) length) {
+        JS_FreeValue(ctx, buffer);
+        return JS_NewInt32(ctx, -1);
+    }
+
+    if (last) {
+        from -= str.length - 1;
+        from = njs_max(from, 0);
+
+    } else {
+        to -= str.length - 1;
+        to = njs_min(to, length);
+    }
+
+    for (i = from; i != to; i += increment) {
+        if (memcmp(&self.start[i], str.start, str.length) == 0) {
+            JS_FreeValue(ctx, buffer);
+            return JS_NewInt32(ctx, i);
+        }
+    }
+
+    JS_FreeValue(ctx, buffer);
+    return JS_NewInt32(ctx, -1);
+}
+
+
+static JSValue
+qjs_buffer_prototype_read_float(JSContext *ctx, JSValueConst this_val,
+    int argc, JSValueConst *argv, int magic)
+{
+    double          v;
+    JSValue         ret;
+    uint32_t        u32;
+    uint64_t        u64, index, size;
+    njs_str_t       self;
+    njs_bool_t      little, swap;
+    njs_conv_f32_t  conv_f32;
+    njs_conv_f64_t  conv_f64;
+
+    ret = qjs_typed_array_data(ctx, this_val, &self);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    if (JS_ToIndex(ctx, &index, argv[0])) {
+        return JS_EXCEPTION;
+    }
+
+    size = magic >> 2;
+
+    if (size + index > self.length) {
+        return JS_ThrowRangeError(ctx, "index %lu is outside the bound of the"
+                                  " buffer", index);
+    }
+
+    little = magic & 1;
+    swap = little;
+
+#if NJS_HAVE_LITTLE_ENDIAN
+    swap = !swap;
+#endif
+
+    switch (size) {
+    case 4:
+        u32 = *((uint32_t *) &self.start[index]);
+
+        if (swap) {
+            u32 = njs_bswap_u32(u32);
+        }
+
+        conv_f32.u = u32;
+        v = conv_f32.f;
+        break;
+
+    case 8:
+    default:
+        u64 = *((uint64_t *) &self.start[index]);
+
+        if (swap) {
+            u64 = njs_bswap_u64(u64);
+        }
+
+        conv_f64.u = u64;
+        v = conv_f64.f;
+        break;
+    }
+
+    return JS_NewFloat64(ctx, v);
+}
+
+
+static JSValue
+qjs_buffer_prototype_read_int(JSContext *ctx, JSValueConst this_val,
+    int argc, JSValueConst *argv, int magic)
+{
+    JSValue     ret;
+    uint32_t    u32;
+    uint64_t    u64, index, size;
+    njs_str_t   self;
+    njs_bool_t  little, swap, sign;
+
+    ret = qjs_typed_array_data(ctx, this_val, &self);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    if (JS_ToIndex(ctx, &index, argv[0])) {
+        return JS_EXCEPTION;
+    }
+
+    size = magic >> 2;
+
+    if (!size) {
+        if (!JS_IsNumber(argv[1])) {
+            return JS_ThrowTypeError(ctx, "\"byteLength\" is not a number");
+        }
+
+        if (JS_ToIndex(ctx, &size, argv[1])) {
+            return JS_EXCEPTION;
+        }
+
+        if (size > 6) {
+            return JS_ThrowRangeError(ctx, "\"byteLength\" must be <= 6");
+        }
+    }
+
+    if (size + index > self.length) {
+        return JS_ThrowRangeError(ctx, "index %lu is outside the bound of the"
+                                  " buffer", index);
+    }
+
+    sign = (magic >> 1) & 1;
+    little = magic & 1;
+    swap = little;
+
+#if NJS_HAVE_LITTLE_ENDIAN
+    swap = !swap;
+#endif
+
+    switch (size) {
+    case 1:
+        if (sign) {
+            return JS_NewInt32(ctx, (int8_t) self.start[index]);
+        }
+
+        return JS_NewUint32(ctx, self.start[index]);
+
+    case 2:
+        u32 = njs_get_u16(&self.start[index]);
+
+        if (swap) {
+            u32 = njs_bswap_u16(u32);
+        }
+
+        if (sign) {
+            /* Sign extension. */
+            u32 |= (u32 & (INT16_MAX + 1ULL)) * UINT32_MAX;
+
+            return JS_NewInt32(ctx, (int16_t) u32);
+        }
+
+        return JS_NewUint32(ctx, u32);
+
+    case 3:
+
+        if (little) {
+            u32 = (self.start[index + 2] << 16)
+                  | (self.start[index + 1] << 8)
+                  | self.start[index];
+
+        } else {
+            u32 = (self.start[index] << 16)
+                  | (self.start[index + 1] << 8)
+                  | self.start[index + 2];
+        }
+
+        if (sign) {
+            /* Sign extension. */
+            u32 |= (u32 & (INT24_MAX + 1ULL)) * UINT32_MAX;
+
+            return JS_NewInt32(ctx, (int32_t) u32);
+        }
+
+        return JS_NewUint32(ctx, u32);
+
+    case 4:
+        u32 = njs_get_u32(&self.start[index]);
+
+        if (swap) {
+            u32 = njs_bswap_u32(u32);
+        }
+
+        if (sign) {
+            /* Sign extension. */
+            u32 |= (u32 & (INT32_MAX + 1ULL)) * UINT32_MAX;
+
+            return JS_NewInt32(ctx, (int32_t) u32);
+        }
+
+        return JS_NewUint32(ctx, u32);
+
+    case 5:
+        if (little) {
+            u64 = ((uint64_t) self.start[index + 4] << 32)
+                  | ((uint64_t) self.start[index + 3] << 24)
+                  | (self.start[index + 2] << 16)
+                  | (self.start[index + 1] << 8)
+                  | self.start[index];
+
+        } else {
+            u64 = ((uint64_t) self.start[index] << 32)
+                  | ((uint64_t) self.start[index + 1] << 24)
+                  | (self.start[index + 2] << 16)
+                  | (self.start[index + 3] << 8)
+                  | self.start[index + 4];
+        }
+
+        if (sign) {
+            /* Sign extension. */
+            u64 |= (u64 & (INT40_MAX + 1ULL)) * UINT64_MAX;
+
+            return JS_NewFloat64(ctx, (int64_t) u64);
+        }
+
+        return JS_NewFloat64(ctx, u64);
+
+    case 6:
+    default:
+        if (little) {
+            u64 = ((uint64_t) self.start[index + 5] << 40)
+                  | ((uint64_t) self.start[index + 4] << 32)
+                  | ((uint64_t) self.start[index + 3] << 24)
+                  | (self.start[index + 2] << 16)
+                  | (self.start[index + 1] << 8)
+                  | self.start[index];
+
+        } else {
+            u64 = ((uint64_t) self.start[index] << 40)
+                  | ((uint64_t) self.start[index + 1] << 32)
+                  | ((uint64_t) self.start[index + 2] << 24)
+                  | (self.start[index + 3] << 16)
+                  | (self.start[index + 4] << 8)
+                  | self.start[index + 5];
+        }
+
+        if (sign) {
+            /* Sign extension. */
+            u64 |= (u64 & (INT48_MAX + 1ULL)) * UINT64_MAX;
+
+            return JS_NewFloat64(ctx, (int64_t) u64);
+        }
+
+        return JS_NewFloat64(ctx, u64);
+    }
+}
+
+
+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_swap(JSContext *ctx, JSValueConst this_val,
+    int argc, JSValueConst *argv, int size)
+{
+    uint8_t    *p, *end;
+    JSValue    ret;
+    njs_str_t  self;
+
+    ret = qjs_typed_array_data(ctx, this_val, &self);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    if ((self.length % size) != 0) {
+        return JS_ThrowRangeError(ctx, "Buffer size must be a multiple "
+                                  "of %d-bits", (int) (size << 3));
+    }
+
+    p = self.start;
+    end = p + self.length;
+
+    switch (size) {
+    case 2:
+        for (; p < end; p += 2) {
+            njs_set_u16(p, njs_bswap_u16(njs_get_u16(p)));
+        }
+
+        break;
+
+    case 4:
+        for (; p < end; p += 4) {
+            njs_set_u32(p, njs_bswap_u32(njs_get_u32(p)));
+        }
+
+        break;
+
+    case 8:
+    default:
+        for (; p < end; p += 8) {
+            njs_set_u64(p, njs_bswap_u64(njs_get_u64(p)));
+        }
+    }
+
+    JS_DupValue(ctx, this_val);
+
+    return this_val;
+}
+
+
+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 JS_ThrowTypeError(ctx, "method toString() called on incompatible"
+                                 " object");
+    }
+
+    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_prototype_write(JSContext *ctx, JSValueConst this_val,
+    int argc, JSValueConst *argv)
+{
+    JSValue                      ret, buffer, encode;
+    uint64_t                     offset, max_length;
+    njs_str_t                    self, src;
+    const uint8_t                *p, *end, *prev;
+    const qjs_buffer_encoding_t  *encoding;
+
+    ret = qjs_typed_array_data(ctx, this_val, &self);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    offset = 0;
+    max_length = self.length;
+    encode = argv[3];
+
+    if (!JS_IsUndefined(argv[1])) {
+        if (JS_IsString(argv[0]) && JS_IsString(argv[1])) {
+            encode = argv[1];
+            goto write;
+        }
+
+        if (JS_ToIndex(ctx, &offset, argv[1])) {
+            return JS_EXCEPTION;
+        }
+
+        max_length = self.length - offset;
+    }
+
+    if (!JS_IsUndefined(argv[2])) {
+        if (JS_IsString(argv[0]) && JS_IsString(argv[2])) {
+            encode = argv[2];
+            goto write;
+        }
+
+        if (JS_ToIndex(ctx, &max_length, argv[2])) {
+            return JS_EXCEPTION;
+        }
+    }
+
+write:
+
+    encoding = qjs_buffer_encoding(ctx, encode, 1);
+    if (encoding == NULL) {
+        return JS_EXCEPTION;
+    }
+
+    buffer = qjs_buffer_from_string(ctx, argv[0], encode);
+    if (JS_IsException(buffer)) {
+        return buffer;
+    }
+
+    (void) qjs_typed_array_data(ctx, buffer, &src);
+
+    if (offset > self.length) {
+        JS_FreeValue(ctx, buffer);
+        return JS_ThrowRangeError(ctx, "\"offset\" is out of range");
+    }
+
+    if (src.length == 0) {
+        JS_FreeValue(ctx, buffer);
+        return JS_NewInt32(ctx, 0);
+    }
+
+    if (max_length > self.length - offset) {
+        JS_FreeValue(ctx, buffer);
+        return JS_ThrowRangeError(ctx, "\"length\" is out of range");
+    }
+
+    max_length = njs_min(max_length, src.length);
+
+    if (encoding->decode == NULL) {
+        /* Avoid writing incomplete UTF-8 characters. */
+        p = prev = src.start;
+        end = p + max_length;
+
+        while (p < end) {
+            p = njs_utf8_next(p, src.start + src.length);
+            if (p <= end) {
+                prev = p;
+            }
+        }
+
+        max_length = prev - src.start;
+    }
+
+    memcpy(&self.start[offset], src.start, max_length);
+
+    JS_FreeValue(ctx, buffer);
+
+    return JS_NewInt32(ctx, max_length);
+}
+
+
+static JSValue
+qjs_buffer_prototype_write_int(JSContext *ctx, JSValueConst this_val,
+    int argc, JSValueConst *argv, int magic)
+{
+    JSValue     ret;
+    int64_t     i64;
+    uint32_t    u32;
+    uint64_t    index, size;
+    njs_str_t   self;
+    njs_bool_t  little, swap, sign;
+
+    ret = qjs_typed_array_data(ctx, this_val, &self);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    if (JS_ToIndex(ctx, &index, argv[1])) {
+        return JS_EXCEPTION;
+    }
+
+    size = magic >> 2;
+
+    if (!size) {
+        if (JS_ToIndex(ctx, &size, argv[2])) {
+            return JS_EXCEPTION;
+        }
+
+        if (size > 6) {
+            return JS_ThrowRangeError(ctx, "\"byteLength\" must be <= 6");
+        }
+    }
+
+    if (size + index > self.length) {
+        return JS_ThrowRangeError(ctx, "index %lu is outside the bound of the"
+                                  " buffer", index);
+    }
+
+    little = magic & 1;
+    sign = (magic >> 1) & 1;
+    swap = little;
+
+#if NJS_HAVE_LITTLE_ENDIAN
+    swap = !swap;
+#endif
 
-            JS_FreeValue(ctx, ctor);
-            str = JS_ToCString(ctx, name);
+    if (JS_ToInt64(ctx, &i64, argv[0])) {
+        return JS_EXCEPTION;
+    }
 
-            if (strncmp(str, "Float32Array", 12) == 0) {
-                float32 = 1;
+    switch (size) {
+    case 1:
+        if (sign) {
+            if (i64 < INT8_MIN || i64 > INT8_MAX) {
+                return JS_ThrowRangeError(ctx, "value is outside the range of"
+                                          " representable values");
             }
 
-            JS_FreeCString(ctx, str);
-            JS_FreeValue(ctx, name);
+        } else {
+            if (i64 < 0 || i64 > UINT8_MAX) {
+                return JS_ThrowRangeError(ctx, "value is outside the range of"
+                                          " representable values");
+            }
         }
 
-        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]);
+        self.start[index] = (uint8_t) i64;
+        break;
 
-    } else if (JS_IsObject(argv[0])) {
-        obj = argv[0];
-        valueOf = JS_GetPropertyStr(ctx, obj, "valueOf");
-        if (JS_IsException(valueOf)) {
-            return valueOf;
-        }
+    case 2:
+        u32 = (uint16_t) i64;
 
-        if (JS_IsFunction(ctx, valueOf)) {
-            ret = JS_Call(ctx, valueOf, obj, 0, NULL);
-            JS_FreeValue(ctx, valueOf);
-            if (JS_IsException(ret)) {
-                return ret;
+        if (sign) {
+            if (i64 < INT16_MIN || i64 > INT16_MAX) {
+                return JS_ThrowRangeError(ctx, "value is outside the range of"
+                                          " representable values");
             }
 
-            if (JS_IsString(ret)) {
-                obj = ret;
-                ret = qjs_buffer_from_string(ctx, obj, argv[1]);
-                JS_FreeValue(ctx, obj);
-                return ret;
+        } else {
+            if (i64 < 0 || i64 > UINT16_MAX) {
+                return JS_ThrowRangeError(ctx, "value is outside the range of"
+                                          " representable values");
             }
+        }
 
-            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;
+        if (swap) {
+            u32 = njs_bswap_u16(u32);
+        }
+
+        njs_set_u16(&self.start[index], u32);
+        break;
+
+    case 3:
+        if (sign) {
+            if (i64 < INT24_MIN || i64 > INT24_MAX) {
+                return JS_ThrowRangeError(ctx, "value is outside the range of"
+                                          " representable values");
             }
 
-            JS_FreeValue(ctx, ret);
+        } else {
+            if (i64 < 0 || i64 > UINT24_MAX) {
+                return JS_ThrowRangeError(ctx, "value is outside the range of"
+                                          " representable values");
+            }
         }
 
-        return qjs_buffer_from_object(ctx, obj);
-    }
+        if (little) {
+            self.start[index] = i64; i64 >>= 8;
+            self.start[index + 1] = i64; i64 >>= 8;
+            self.start[index + 2] = i64;
 
-    JS_ThrowTypeError(ctx, "first argument is not a string "
-                      "or Buffer-like object");
-    return JS_EXCEPTION;
-}
+        } else {
+            self.start[index + 2] = i64; i64 >>= 8;
+            self.start[index + 1] = i64; i64 >>= 8;
+            self.start[index] = i64;
+        }
 
+        break;
 
-static JSValue
-qjs_buffer_is_buffer(JSContext *ctx, JSValueConst this_val,
-    int argc, JSValueConst *argv)
-{
-    JSValue proto, buffer_proto, ret;
+    case 4:
+        u32 = i64;
 
-    proto = JS_GetPrototype(ctx, argv[0]);
-    buffer_proto = JS_GetClassProto(ctx, qjs_buffer_class_id);
+        if (sign) {
+            if (i64 < INT32_MIN || i64 > INT32_MAX) {
+                return JS_ThrowRangeError(ctx, "value is outside the range of"
+                                          " representable values");
+            }
 
-    ret = JS_NewBool(ctx, JS_VALUE_GET_TAG(argv[0]) == JS_TAG_OBJECT &&
-                     JS_VALUE_GET_OBJ(buffer_proto) == JS_VALUE_GET_OBJ(proto));
+        } else {
+            if (i64 < 0 || i64 > UINT32_MAX) {
+                return JS_ThrowRangeError(ctx, "value is outside the range of"
+                                          " representable values");
+            }
+        }
 
-    JS_FreeValue(ctx, buffer_proto);
-    JS_FreeValue(ctx, proto);
+        if (swap) {
+            u32 = njs_bswap_u32(u32);
+        }
 
-    return ret;
-}
+        njs_set_u32(&self.start[index], u32);
+        break;
 
+    case 5:
+        if (sign) {
+            if (i64 < INT40_MIN || i64 > INT40_MAX) {
+                return JS_ThrowRangeError(ctx, "value is outside the range of"
+                                          " representable values");
+            }
 
-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;
+        } else {
+            if (i64 < 0 || i64 > UINT40_MAX) {
+                return JS_ThrowRangeError(ctx, "value is outside the range of"
+                                          " representable values");
+            }
+        }
 
-    ret = qjs_typed_array_data(ctx, this_val, &src);
-    if (JS_IsException(ret)) {
-        return ret;
-    }
+        if (little) {
+            self.start[index] = i64; i64 >>= 8;
+            self.start[index + 1] = i64; i64 >>= 8;
+            self.start[index + 2] = i64; i64 >>= 8;
+            self.start[index + 3] = i64; i64 >>= 8;
+            self.start[index + 4] = i64;
 
-    obj = JS_NewObject(ctx);
-    if (JS_IsException(obj)) {
-        return obj;
-    }
+        } else {
+            self.start[index + 4] = i64; i64 >>= 8;
+            self.start[index + 3] = i64; i64 >>= 8;
+            self.start[index + 2] = i64; i64 >>= 8;
+            self.start[index + 1] = i64; i64 >>= 8;
+            self.start[index] = i64;
+        }
 
-    data = JS_NewArray(ctx);
-    if (JS_IsException(data)) {
-        JS_FreeValue(ctx, obj);
-        return data;
-    }
+        break;
 
-    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;
-    }
+    case 6:
+    default:
+        if (sign) {
+            if (i64 < INT48_MIN || i64 > INT48_MAX) {
+                return JS_ThrowRangeError(ctx, "value is outside the range of"
+                                          " representable values");
+            }
 
-    rc = JS_DefinePropertyValueStr(ctx, obj, "data", data, JS_PROP_ENUMERABLE);
-    if (rc == -1) {
-        JS_FreeValue(ctx, obj);
-        JS_FreeValue(ctx, data);
-        return ret;
-    }
+        } else {
+            if (i64 < 0 || i64 > UINT48_MAX) {
+                return JS_ThrowRangeError(ctx, "value is outside the range of"
+                                          " representable values");
+            }
+        }
 
-    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;
+        if (little) {
+            self.start[index] = i64; i64 >>= 8;
+            self.start[index + 1] = i64; i64 >>= 8;
+            self.start[index + 2] = i64; i64 >>= 8;
+            self.start[index + 3] = i64; i64 >>= 8;
+            self.start[index + 4] = i64; i64 >>= 8;
+            self.start[index + 5] = i64;
+
+        } else {
+            self.start[index + 5] = i64; i64 >>= 8;
+            self.start[index + 4] = i64; i64 >>= 8;
+            self.start[index + 3] = i64; i64 >>= 8;
+            self.start[index + 2] = i64; i64 >>= 8;
+            self.start[index + 1] = i64; i64 >>= 8;
+            self.start[index] = i64;
         }
+
+        break;
     }
 
-    return obj;
+    return JS_NewInt32(ctx, size + index);
 }
 
 
-
 static JSValue
-qjs_buffer_prototype_to_string(JSContext *ctx, JSValueConst this_val,
-    int argc, JSValueConst *argv)
+qjs_buffer_prototype_write_float(JSContext *ctx, JSValueConst this_val,
+    int argc, JSValueConst *argv, int magic)
 {
-    JSValue                      ret;
-    njs_str_t                    src, data;
-    const qjs_buffer_encoding_t  *encoding;
-
-    ret = qjs_typed_array_data(ctx, this_val, &src);
+    double          v;
+    JSValue         ret;
+    uint32_t        u32;
+    uint64_t        u64, index, size;
+    njs_str_t       self;
+    njs_bool_t      little, swap;
+    njs_conv_f32_t  conv_f32;
+    njs_conv_f64_t  conv_f64;
+
+    ret = qjs_typed_array_data(ctx, this_val, &self);
     if (JS_IsException(ret)) {
         return ret;
     }
 
-    if (JS_IsUndefined(argv[0]) || src.length == 0) {
-        return JS_NewStringLen(ctx, (char *) src.start, src.length);
+    if (JS_ToFloat64(ctx, &v, argv[0])) {
+        return JS_EXCEPTION;
     }
 
-    encoding = qjs_buffer_encoding(ctx, argv[0], 1);
-    if (njs_slow_path(encoding == NULL)) {
+    if (JS_ToIndex(ctx, &index, argv[1])) {
         return JS_EXCEPTION;
     }
 
-    if (encoding->encode_length == NULL) {
-        return JS_NewStringLen(ctx, (char *) src.start, src.length);
-    }
+    size = magic >> 2;
 
-    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 (size + index > self.length) {
+        return JS_ThrowRangeError(ctx, "index %lu is outside the bound of the"
+                                  " buffer", index);
     }
 
-    if (encoding->encode(ctx, &src, &data) != 0) {
-        js_free(ctx, data.start);
-        JS_ThrowTypeError(ctx, "failed to encode buffer");
-        return JS_EXCEPTION;
-    }
+    little = magic & 1;
+    swap = little;
 
-    ret = JS_NewStringLen(ctx, (char *) data.start, data.length);
+#if NJS_HAVE_LITTLE_ENDIAN
+    swap = !swap;
+#endif
 
-    js_free(ctx, data.start);
+    switch (size) {
+    case 4:
+        conv_f32.f = (float) v;
 
-    return ret;
+        if (swap) {
+            conv_f32.u = njs_bswap_u32(conv_f32.u);
+        }
+
+        u32 = conv_f32.u;
+        memcpy(&self.start[index], &u32, size);
+        break;
+
+    case 8:
+    default:
+        conv_f64.f = v;
+
+        if (swap) {
+            conv_f64.u = njs_bswap_u64(conv_f64.u);
+        }
+
+        u64 = conv_f64.u;
+        memcpy(&self.start[index], &u64, size);
+        break;
+    }
+
+    return JS_NewInt32(ctx, size + index);
 }
 
 
@@ -424,6 +1883,11 @@ qjs_buffer_from_string(JSContext *ctx, JSValueConst str,
     njs_str_t                    src, dst;
     const qjs_buffer_encoding_t  *encoding;
 
+    if (!JS_IsString(str)) {
+        JS_ThrowTypeError(ctx, "first argument is not a string");
+        return JS_EXCEPTION;
+    }
+
     encoding = qjs_buffer_encoding(ctx, enc, 1);
     if (njs_slow_path(encoding == NULL)) {
         return JS_EXCEPTION;
@@ -552,57 +2016,6 @@ qjs_buffer_from_typed_array(JSContext *ctx, JSValueConst 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)
@@ -872,7 +2285,7 @@ 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)
 {
-    qjs_base64_encode_core(dst, src, qjs_basis64url_enc, 1);
+    qjs_base64_encode_core(dst, src, qjs_basis64url_enc, 0);
 
     return 0;
 }
@@ -1004,9 +2417,11 @@ qjs_hex_encode_length(JSContext *ctx, const njs_str_t *src)
 JSValue
 qjs_buffer_alloc(JSContext *ctx, size_t size)
 {
-    JSValue  ret, proto;
+    JSValue  ret, proto, value;
+
+    value = JS_NewInt64(ctx, size);
 
-    ret = qjs_new_uint8_array(ctx, size);
+    ret = qjs_new_uint8_array(ctx, 1, &value);
     if (JS_IsException(ret)) {
         return ret;
     }
@@ -1019,6 +2434,29 @@ qjs_buffer_alloc(JSContext *ctx, size_t size)
 }
 
 
+
+JSValue
+qjs_buffer_create(JSContext *ctx, u_char *start, size_t size)
+{
+    JSValue    buffer, ret;
+    njs_str_t  dst;
+
+    buffer = qjs_buffer_alloc(ctx, size);
+    if (JS_IsException(buffer)) {
+        return buffer;
+    }
+
+    ret = qjs_typed_array_data(ctx, buffer, &dst);
+    if (JS_IsException(ret)) {
+        return ret;
+    }
+
+    memcpy(dst.start, start, size);
+
+    return buffer;
+}
+
+
 JSValue
 qjs_buffer_chb_alloc(JSContext *ctx, njs_chb_t *chain)
 {
@@ -1047,24 +2485,20 @@ qjs_buffer_chb_alloc(JSContext *ctx, njs_chb_t *chain)
 
 
 static JSValue
-qjs_new_uint8_array(JSContext *ctx, size_t size)
+qjs_new_uint8_array(JSContext *ctx, int argc, JSValueConst *argv)
 {
-    JSValue  ret, value;
-
-    value = JS_NewInt64(ctx, size);
+    JSValue  ret;
 
 #ifdef NJS_HAVE_QUICKJS_NEW_TYPED_ARRAY
-    ret = JS_NewTypedArray(ctx, 1, &value, JS_TYPED_ARRAY_UINT8);
+    ret = JS_NewTypedArray(ctx, argc, argv, JS_TYPED_ARRAY_UINT8);
 #else
     JSValue ctor;
 
     ctor = JS_GetClassProto(ctx, qjs_uint8_array_ctor_id);
-    ret = JS_CallConstructor(ctx, ctor, 1, &value);
+    ret = JS_CallConstructor(ctx, ctor, argc, argv);
     JS_FreeValue(ctx, ctor);
 #endif
 
-    JS_FreeValue(ctx, value);
-
     return ret;
 }
 
@@ -1073,18 +2507,19 @@ static int
 qjs_buffer_builtin_init(JSContext *ctx)
 {
     int        rc;
-    JSValue    global_obj, buffer, proto, ctor, ta, ta_proto;
+    JSAtom     species_atom;
+    JSValue    global_obj, buffer, proto, ctor, ta, ta_proto, symbol, species;
     JSClassID  u8_ta_class_id;
 
     JS_NewClassID(&qjs_buffer_class_id);
     JS_NewClass(JS_GetRuntime(ctx), qjs_buffer_class_id, &qjs_buffer_class);
 
+    global_obj = JS_GetGlobalObject(ctx);
+
     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
@@ -1110,15 +2545,29 @@ qjs_buffer_builtin_init(JSContext *ctx)
 
     JS_SetClassProto(ctx, qjs_buffer_class_id, proto);
 
-    buffer = JS_NewCFunction2(ctx, qjs_buffer, "Buffer", 0,
-                              JS_CFUNC_generic, 0);
+    buffer = JS_NewCFunction2(ctx, qjs_buffer, "Buffer", 3,
+                              JS_CFUNC_constructor, 0);
     if (JS_IsException(buffer)) {
         return -1;
     }
 
+    JS_SetConstructor(ctx, buffer, proto);
+
     JS_SetPropertyFunctionList(ctx, buffer, qjs_buffer_props,
                                njs_nitems(qjs_buffer_props));
 
+    symbol = JS_GetPropertyStr(ctx, global_obj, "Symbol");
+    species = JS_GetPropertyStr(ctx, symbol, "species");
+    JS_FreeValue(ctx, symbol);
+    species_atom = JS_ValueToAtom(ctx, species);
+    JS_FreeValue(ctx, species);
+
+    ctor = JS_NewCFunction2(ctx, qjs_buffer_ctor, "Buffer species ctor", 3,
+                            JS_CFUNC_constructor, 0);
+
+    JS_SetProperty(ctx, buffer, species_atom, ctor);
+    JS_FreeAtom(ctx, species_atom);
+
     rc = JS_SetPropertyStr(ctx, global_obj, "Buffer", buffer);
     if (rc == -1) {
         return -1;
diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c
index c6979c13..10ee6c1e 100644
--- a/src/test/njs_unit_test.c
+++ b/src/test/njs_unit_test.c
@@ -21015,817 +21015,6 @@ static njs_unit_test_t  njs_querystring_module_test[] =
 };
 
 
-static njs_unit_test_t  njs_buffer_module_test[] =
-{
-    { njs_str("new Buffer();"),
-      njs_str("TypeError: Buffer is not a constructor") },
-
-    { njs_str("var buf = Buffer.alloc();"),
-      njs_str("TypeError: \"size\" argument must be of type number") },
-
-    { njs_str("var buf = Buffer.alloc('best buffer');"),
-      njs_str("TypeError: \"size\" argument must be of type number") },
-
-    { njs_str("var buf = Buffer.alloc(-1);"),
-      njs_str("RangeError: invalid size") },
-
-    { njs_str("var buf = Buffer.alloc(4); njs.dump(buf)"),
-      njs_str("Buffer [0,0,0,0]") },
-
-    { njs_str("var buf = Buffer.alloc(4, 88); buf"),
-      njs_str("XXXX") },
-
-    { njs_str("var buf = Buffer.alloc(4, 945); njs.dump(buf)"),
-      njs_str("Buffer [177,177,177,177]") },
-
-    { njs_str("var buf = Buffer.alloc(4, -1); njs.dump(buf)"),
-      njs_str("Buffer [255,255,255,255]") },
-
-    { njs_str("var buf = Buffer.alloc(4, -1, 'utf-128'); njs.dump(buf)"),
-      njs_str("Buffer [255,255,255,255]") },
-
-    { njs_str("var buf = Buffer.alloc(10, 'α'); buf"),
-      njs_str("ααααα") },
-
-    { njs_str("var buf = Buffer.alloc(4, 'α'); njs.dump(buf)"),
-      njs_str("Buffer [206,177,206,177]") },
-
-    { njs_str("var buf = Buffer.alloc(2, 'ααααα'); njs.dump(buf)"),
-      njs_str("Buffer [206,177]") },
-
-    { njs_str("var buf = Buffer.alloc(1, 'α'); njs.dump(buf)"),
-      njs_str("Buffer [206]") },
-
-    { njs_str("var buf = Buffer.alloc(4, 'ZXZpbA==', 'base64'); buf"),
-      njs_str("evil") },
-
-    { njs_str("var buf = Buffer.alloc(8, 'ZXZpbA==', 'base64'); buf"),
-      njs_str("evilevil") },
-
-    { njs_str("var buf = Buffer.alloc(8, 'evil', 'utf-128'); buf"),
-      njs_str("TypeError: \"utf-128\" encoding is not supported") },
-
-    { njs_str("var foo = new Uint8Array(10).fill(88);"
-              "var buf = Buffer.alloc(8, foo); buf"),
-      njs_str("XXXXXXXX") },
-
-    { njs_str("[1,2,10,20].every(v => {"
-              "     var src = new Uint16Array(v).fill(0xB1CE);"
-              "     var buf = Buffer.alloc(10, src);"
-              "     return buf.toString() === " njs_evar("'ααααα'", "'�αααα�'")
-              "})"),
-      njs_str("true") },
-
-    { njs_str("var foo = Buffer.alloc(10, 'α');"
-              "var buf = Buffer.alloc(4, foo); buf"),
-      njs_str("αα") },
-
-    { njs_str("var buf = Buffer.allocUnsafe(10).fill('α'); buf"),
-      njs_str("ααααα") },
-
-    { njs_str("var buf = Buffer.allocUnsafe(-1)"),
-      njs_str("RangeError: invalid size") },
-
-    { njs_str("["
-             " ['6576696c', 'hex', 4],"
-             " ['6576696', 'hex', 3],"
-             " ['', 'hex', 0],"
-             " ['', 'base64', 0],"
-             " ['ZXZpbA==', 'base64', 4],"
-             " ['ZXZpbA', 'base64url', 4],"
-             " ['ααααα', undefined, 10],"
-             "].every(args => Buffer.byteLength(args[0], args[1])  == args[2])"),
-      njs_str("true") },
-
-    { 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("['utf8', 'utf-8', 'hex', 'base64', 'base64url', 'utf-88', '1hex']"
-              ".map(v=>Buffer.isEncoding(v))"),
-      njs_str("true,true,true,true,true,false,false") },
-
-    { njs_str("["
-              " ['ABC', 'ABCD', -1],"
-              " ['ABCD', 'ABC', 1],"
-              " ['ABC', 'ACB', -1],"
-              " ['ACB', 'ABC', 1],"
-              " ['ABC', 'ABC', 0],"
-              " ['', 'ABC', -1],"
-              " ['', '', 0],"
-              "].every(args => {"
-              "   if (Buffer.compare(Buffer.from(args[0]), Buffer.from(args[1])) != args[2]) {"
-              "       throw new TypeError("
-              "            `Buffer.compare(Buffer.from(${args[0]}), Buffer.from(${args[1]})) != ${args[2]}`);"
-              "   }"
-              "   return true;"
-              "})"),
-      njs_str("true") },
-
-    { njs_str("["
-              " ['ABC', 'ABCD', -1],"
-              " ['ABCD', 'ABC', 1],"
-              " ['ABC', 'ACB', -1],"
-              " ['ACB', 'ABC', 1],"
-              " ['ABC', 'ABC', 0],"
-              " ['', 'ABC', -1],"
-              " ['', '', 0],"
-              "].every(args => {"
-              "   if (Buffer.from(args[0]).compare(Buffer.from(args[1])) != args[2]) {"
-              "       throw new TypeError("
-              "            `Buffer.from(${args[0]}).compare(Buffer.from(${args[1]})) != ${args[2]}`);"
-              "   }"
-              "   return true;"
-              "})"),
-      njs_str("true") },
-
-    { njs_str("var buf = Buffer.from('ABCD');"
-              "["
-              " [0,3,0,2, -1],"
-              " [0,2,0,3, 1],"
-              " [3,4,3,4, 0],"
-              " [undefined, undefined, undefined, undefined, 0],"
-              " [-1, undefined, undefined, undefined, 'invalid index'],"
-              " [0, -1, undefined, undefined, 'invalid index'],"
-              " [0, 0, -1, undefined, 'invalid index'],"
-              " [0, 0, 0, -1, 'invalid index'],"
-              "]"
-              ".every(as => {"
-              "   try {"
-              "       if (buf.compare(buf, as[0], as[1], as[2], as[3]) != as[4]) {"
-              "           throw new TypeError("
-              "                `buf.compare(${as[0]}, ${as[1]}, ${as[2]}, ${as[3]}) != ${as[4]}`);"
-              "       }"
-              "   } catch (e) { return e.message == as[4]}"
-              "   return true;"
-              "})"),
-      njs_str("true") },
-
-    { njs_str("var buf1 = Buffer.from('ABCD');"
-              "var buf2 = Buffer.from('ABCD');"
-              "buf1.compare(buf2, 5)"),
-      njs_str("RangeError: \"targetStart\" is out of range: 5") },
-
-    { njs_str("var buf1 = Buffer.from('ABCD');"
-              "var buf2 = Buffer.from('ABCD');"
-              "buf1.compare(buf2, 0, 3, 5)"),
-      njs_str("RangeError: \"sourceStart\" is out of range: 5") },
-
-    { njs_str("var arr = new Uint8Array(4);"
-              "arr[0] = 0x41; arr[1] = 0x42; arr[2] = 0x43; arr[3] = 0x44;"
-              "arr = arr.subarray(1, 4);"
-              "var buf = Buffer.from('ABCD');"
-              "buf.compare(arr, 0, 3, 1, 4)"),
-      njs_str("0") },
-
-    { njs_str("['123', 'abc', '124', '', 'AB', 'ABCD']"
-              ".map(v=>Buffer.from(v)).sort(Buffer.compare).map(v=>v.toString())"),
-      njs_str(",123,124,AB,ABCD,abc") },
-
-    { njs_str("Buffer.compare(Buffer.alloc(1), 'text')"),
-      njs_str("TypeError: \"target\" argument must be an instance of Buffer or Uint8Array") },
-
-    { njs_str("Buffer.compare('text', Buffer.from('ACB'))"),
-      njs_str("TypeError: \"source\" argument must be an instance of Buffer or Uint8Array") },
-
-    { njs_str("Buffer.concat()"),
-      njs_str("TypeError: \"list\" argument must be an instance of Array") },
-
-    { njs_str("Buffer.concat([])"),
-      njs_str("") },
-
-    { njs_str("Buffer.concat([new Uint16Array(10)])"),
-      njs_str("TypeError: \"list[0]\" argument must be an instance of Buffer or Uint8Array") },
-
-    { njs_str("Buffer.concat([new Uint8Array(2), new Uint8Array(1)]).fill('abc')"),
-      njs_str("abc") },
-
-    { njs_str("Buffer.concat([Buffer.from('AB'), Buffer.from('CD')])"),
-      njs_str("ABCD") },
-
-    { njs_str("Buffer.concat([new Uint8Array(2), new Uint8Array(1)], 2).fill('abc')"),
-      njs_str("ab") },
-
-    { njs_str("Buffer.concat([new Uint8Array(2), new Uint8Array(1)], 6).fill('abc')"),
-      njs_str("abcabc") },
-
-    { njs_str("Buffer.concat([Buffer.from('ABCD').slice(2,4), Buffer.from('ABCD').slice(0,2)])"),
-      njs_str("CDAB") },
-
-    { njs_str(njs_declare_sparse_array("list", 2)
-              "list[0] = Buffer.from('ABCD').slice(2,4);"
-              "list[1] = Buffer.from('ABCD').slice(0,2);"
-              "Buffer.concat(list);"),
-      njs_str("CDAB") },
-
-    { njs_str(njs_declare_sparse_array("list", 2)
-              "list[0] = new Uint8Array(2); list[1] = new Uint8Array(3);"
-              "Buffer.concat(list).fill('ab');"),
-      njs_str("ababa") },
-
-    { njs_str("Buffer.concat([], '123')"),
-      njs_str("TypeError: \"length\" argument must be of type number") },
-
-    { njs_str("Buffer.concat([], -1)"),
-      njs_str("RangeError: \"length\" is out of range") },
-
-    { njs_str("var buf = Buffer.from('α'); buf[1]"),
-      njs_str("177") },
-
-    { njs_str("var buf = Buffer.from('α'); buf[1] = 1; njs.dump(buf)"),
-      njs_str("Buffer [206,1]") },
-
-    { njs_str("var arrBuf = new ArrayBuffer(16);"
-              "var buf = Buffer.from(arrBuf); buf.buffer === arrBuf"),
-      njs_str("true") },
-
-    { njs_str("["
-              " [[0], 4, '65,66,67,68,0,0,0,0,0,0'],"
-              " [[5], 4, '0,0,0,0,0,65,66,67,68,0'],"
-              " [[8], 2, '0,0,0,0,0,0,0,0,65,66'],"
-              " [[8,2,4], 2, '0,0,0,0,0,0,0,0,67,68'],"
-              " [[10], 0, '0,0,0,0,0,0,0,0,0,0'],"
-              "]"
-              ".every(args => {"
-              " var buf1 = Buffer.from('ABCD');"
-              " var buf2 = Buffer.alloc(10, 0);"
-              " var as = args[0];"
-              " var length = buf1.copy(buf2, as[0], as[1], as[2]);"
-              ""
-              " if (length != args[1]) {"
-              "   throw new TypeError(`buf1.copy(buf2, ${as[0]}, ${as[1]}, ${as[2]}): ${length} != ${args[1]}`)"
-              " }"
-              ""
-              " if (njs.dump(buf2) != `Buffer [${args[2]}]`) {"
-              "   throw new TypeError("
-              "     `buf1.copy(buf2, ${as[0]}, ${as[1]}, ${as[2]}): ${njs.dump(buf2)} != Buffer [${args[2]}]`);"
-              " }"
-              " return true;"
-              "})"),
-      njs_str("true") },
-
-    { njs_str("["
-              " [[0], 'ABCDEF'],"
-              " [[0,2], 'CDEFEF'],"
-              " [[0,2,6], 'CDEFEF'],"
-              " [[1,2,4], 'ACDDEF'],"
-              " [[1,2,3], 'ACCDEF']"
-              "]"
-              ".every(args => {"
-              " var buf = Buffer.from('ABCDEF');"
-              " var as = args[0];"
-              " buf.copy(buf, as[0], as[1], as[2]);"
-              ""
-              " if (buf.toString() != args[1]) {"
-              "   throw new TypeError("
-              "     `buf.copy(buf, ${as[0]}, ${as[1]}, ${as[2]}): buf.toString() != ${args[1]}`);"
-              " }"
-              " return true;"
-              "})"),
-      njs_str("true") },
-
-    { njs_str("var buf1 = Buffer.from('ABCD');"
-              "var buf2 = Buffer.alloc(10, 0);"
-              "buf1.copy(buf2, -1)"),
-      njs_str("RangeError: invalid index") },
-
-    { njs_str("var buf1 = Buffer.from('ABCD');"
-              "var buf2 = Buffer.alloc(10, 0);"
-              "buf1.copy(buf2, 0, -1)"),
-      njs_str("RangeError: invalid index") },
-
-    { njs_str("var buf1 = Buffer.from('ABCD');"
-              "var buf2 = Buffer.alloc(10, 0);"
-              "buf1.copy(buf2, 0, 5)"),
-      njs_str("RangeError: \"sourceStart\" is out of range: 5") },
-
-    { njs_str("var arr = new Uint8Array(4);"
-              "arr[0] = 0x41; arr[1] = 0x42; arr[2] = 0x43; arr[3] = 0x44;"
-              "arr = arr.subarray(1, 4);"
-              "var buf1 = Buffer.from(arr);"
-              "var buf2 = Buffer.alloc(10, 0);"
-              "var length = buf1.copy(buf2, 1, 1, 2); [length, njs.dump(buf2)]"),
-      njs_str("1,Buffer [0,67,0,0,0,0,0,0,0,0]") },
-
-    { njs_str("var arr = new Uint8Array(4);"
-              "arr[0] = 0x41; arr[1] = 0x42; arr[2] = 0x43; arr[3] = 0x44;"
-              "arr = arr.subarray(1, 4);"
-              "var buf1 = Buffer.from(arr);"
-              "var buf2 = Buffer.alloc(10, 0);"
-              "var length = buf1.copy(buf2, 1, 1, 2); [length, njs.dump(buf2)]"),
-      njs_str("1,Buffer [0,67,0,0,0,0,0,0,0,0]") },
-
-    { njs_str("["
-              " ['ABC', 'ABCD', false],"
-              " ['ABCD', 'ABC', false],"
-              " ['ABC', 'ACB', false],"
-              " ['ACB', 'ABC', false],"
-              " ['ABC', 'ABC', true],"
-              " ['', 'ABC', false],"
-              " ['', '', true],"
-              "].every(args => {"
-              "   if (Buffer.from(args[0]).equals(Buffer.from(args[1])) != args[2]) {"
-              "       throw new TypeError("
-              "            `Buffer.from(${args[0]}).compare(Buffer.from(${args[1]})) != ${args[2]}`);"
-              "   }"
-              "   return true;"
-              "})"),
-      njs_str("true") },
-
-    { njs_str("Buffer.from([1,2]).equals(new ArrayBuffer(1))"),
-      njs_str("TypeError: \"target\" argument must be an instance of Buffer or Uint8Array") },
-
-    { njs_str("Buffer.from([1,2]).equals(1)"),
-      njs_str("TypeError: \"target\" argument must be an instance of Buffer or Uint8Array") },
-
-    { njs_str("var buf = Buffer.alloc(4);"
-              "buf.fill('ZXZpbA==', 'base64')"),
-      njs_str("evil") },
-
-    { njs_str("var buf = Buffer.alloc(4);"
-              "buf.fill('6576696c', 'hex')"),
-      njs_str("evil") },
-
-    { njs_str("var buf = Buffer.alloc(4);"
-              "buf.fill('ZXZpbA==', '')"),
-      njs_str("TypeError: \"\" encoding is not supported") },
-
-    { njs_str("var buf = Buffer.alloc(8);"
-              "buf.fill('6576696c', 'hex')"),
-      njs_str("evilevil") },
-
-    { njs_str("var buf = Buffer.alloc(10);"
-              "buf.fill('evil')"),
-      njs_str("evilevilev") },
-
-    { njs_str("var buf = Buffer.allocUnsafe(5);"
-              "buf[3] = 1;"
-              "buf.fill(''); njs.dump(buf)"),
-      njs_str("Buffer [0,0,0,0,0]") },
-
-    { njs_str("var arr = new Uint8Array(4);"
-              "arr[0] = 0x41; arr[1] = 0x42; arr[2] = 0x43; arr[3] = 0x44;"
-              "arr = arr.subarray(1, 4);"
-              "var buf = Buffer.allocUnsafe(6);"
-              "buf.fill(arr); njs.dump(buf)"),
-      njs_str("Buffer [66,67,68,66,67,68]") },
-
-    { njs_str("var buf = Buffer.alloc(6, 'ABCDEF');"
-              "buf.fill(buf, 2, 6)"),
-      njs_str("ABABCD") },
-
-    { njs_str("Buffer.alloc(6).fill(0x41)"),
-      njs_str("AAAAAA") },
-
-    { njs_str("Buffer.alloc(6).fill({valueOf(){return 0x42}})"),
-      njs_str("BBBBBB") },
-
-    { njs_str("njs.dump(Buffer.alloc(3).fill(-1))"),
-      njs_str("Buffer [255,255,255]") },
-
-    { njs_str("[NaN, Infinity, -Infinity, undefined, null, {}]"
-              ".every(v => njs.dump(Buffer.alloc(3).fill(v)) == 'Buffer [0,0,0]')"),
-      njs_str("true") },
-
-    { njs_str("njs.dump(Buffer.alloc(6).fill({valueOf(){throw 'Oops'}}, 4,3))"),
-      njs_str("Buffer [0,0,0,0,0,0]") },
-
-    { njs_str("njs.dump(Buffer.alloc(6).fill({valueOf(){throw 'Oops'}}, 3,3))"),
-      njs_str("Buffer [0,0,0,0,0,0]") },
-
-    { njs_str("njs.dump(Buffer.alloc(6).fill({valueOf(){throw 'Oops'}}, 2,3))"),
-      njs_str("Oops") },
-
-    { njs_str("njs.dump(Buffer.alloc(5).fill('α'))"),
-      njs_str("Buffer [206,177,206,177,206]") },
-
-    { njs_str("Buffer.alloc(4).fill('ABCD', -1)"),
-      njs_str("RangeError: invalid index") },
-
-    { njs_str("Buffer.alloc(4).fill('ABCD', 5)"),
-      njs_str("RangeError: \"offset\" is out of range") },
-
-    { njs_str("Buffer.alloc(4).fill('ABCD', 0, -1)"),
-      njs_str("RangeError: invalid index") },
-
-    { njs_str("Buffer.alloc(4).fill('ABCD', 0, 5)"),
-      njs_str("RangeError: \"end\" is out of range") },
-
-    { njs_str("Buffer.alloc(513).fill('A'.repeat(512)).length"),
-      njs_str("513") },
-
-    { njs_str("njs.dump(Buffer.alloc(4).fill((new Uint8Array(5)).fill(1)))"),
-      njs_str("Buffer [1,1,1,1]") },
-
-    { njs_str("var src = new Uint8Array(10).fill(255);"
-              "var u8 = new Uint8Array(src.buffer, 1, 8);"
-              "u8.set([1,2,3,4,5,6,7,8]);"
-              "njs.dump(Buffer.alloc(9).fill(u8))"),
-      njs_str("Buffer [1,2,3,4,5,6,7,8,1]") },
-
-    { njs_str("Buffer.alloc(513).fill((new Uint8Array(512)).fill(1)).length"),
-      njs_str("513") },
-
-    { njs_str("Buffer.alloc(4).fill('ABCD', undefined, undefined, 'utf-128')"),
-      njs_str("TypeError: \"utf-128\" encoding is not supported") },
-
-    { njs_str("var buf1 = Buffer.from('ABCD').subarray(1, 3);"
-              "buf1.fill('B', 1, 2); njs.dump(buf1)"),
-      njs_str("Buffer [66,66]") },
-
-    { njs_str("var buf1 = Buffer.from('ABCD').subarray(1, 3);"
-              "buf1.fill(0x42, 1, 2); njs.dump(buf1)"),
-      njs_str("Buffer [66,66]") },
-
-    { njs_str("var buf1 = Buffer.from('ABCD').subarray(1, 2);"
-              "var buf2 = Buffer.from('ABCD').subarray(1, 3);"
-              "buf2.fill(buf1, 1, 2); njs.dump(buf2)"),
-      njs_str("Buffer [66,66]") },
-
-    { njs_str("var buf = Buffer.from('ABCD');"
-              "['BC', 'CB', 'ABCD', 'ABCDE', ''].map(v=>buf.indexOf(v))"),
-      njs_str("1,-1,0,-1,0") },
-
-    { njs_str("var buf = Buffer.from('ABCD');"
-              "[0,5,-2,-1].map(v=>buf.indexOf('C', v))"),
-      njs_str("2,-1,2,-1") },
-
-    { njs_str("var buf = Buffer.from('evil');"
-              "buf.indexOf('ZXZpbA==', undefined, 'base64')"),
-      njs_str("0") },
-
-    { njs_str("var buf = Buffer.from('evil');"
-              "buf.indexOf('6576696c', undefined, 'hex')"),
-      njs_str("0") },
-
-    { njs_str("var buf = Buffer.from('ABCD');"
-              "buf.indexOf('C', undefined, 'utf-128')"),
-      njs_str("TypeError: \"utf-128\" encoding is not supported") },
-
-    { njs_str("var buf = Buffer.from('ABCDABC');"
-              "['BC', 'CB', 'ABCD', 'ABCDE', '', 'C', 'ABCDABCD'].map(v=>buf.indexOf(Buffer.from(v)))"),
-      njs_str("1,-1,0,-1,0,2,-1") },
-
-    { njs_str("var buf = Buffer.from('ABCDABC');"
-              "['BC', 'CB', 'ABCD', 'ABCDE', '', 'C', 'ABCDABCD'].map(v=>buf.includes(Buffer.from(v)))"),
-      njs_str("true,false,true,false,true,true,false") },
-
-    { njs_str("var buf = Buffer.from('ZABCDABC').subarray(1);"
-              "['BC', 'CB', 'ABCD', 'ABCDE', '', 'C', 'ABCDABCD'].map(v=>buf.indexOf(Buffer.from(v)))"),
-      njs_str("1,-1,0,-1,0,2,-1") },
-
-    { njs_str("var buf = Buffer.from('ABCD');"
-              "buf.indexOf(0x43)"),
-      njs_str("2") },
-
-    { njs_str("var buf = Buffer.from('ABCD');"
-              "buf.indexOf(0x43, -2)"),
-      njs_str("2") },
-
-    { njs_str("var buf = Buffer.from('ABCD');"
-              "buf.indexOf(0x43, -1)"),
-      njs_str("-1") },
-
-    { njs_str("var buf1 = Buffer.from('ABCD');"
-              "var buf2 = Buffer.from('XXCX').subarray(2, 3);"
-              "buf1.indexOf(buf2)"),
-      njs_str("2") },
-
-    { njs_str("var buf1 = Buffer.from('ABCD').subarray(1, 4);"
-              "buf1.indexOf(0x43)"),
-      njs_str("1") },
-
-    { njs_str("var buf1 = Buffer.from('ABCD').subarray(1, 4);"
-              "buf1.indexOf('C')"),
-      njs_str("1") },
-
-    { njs_str("var buf = Buffer.from('ABCDABC');"
-              "['BC', 'CB', 'ABCD', 'ABCDE', '', 'C', 'ABCDABCD'].map(v=>buf.lastIndexOf(v))"),
-      njs_str("5,-1,0,-1,7,6,-1") },
-
-    { njs_str("var buf = Buffer.from('ABCDABC');"
-              "['BC', 'CB', 'ABCD', 'ABCDE', '', 'C', 'ABCDABCD'].map(v=>buf.lastIndexOf(Buffer.from(v)))"),
-      njs_str("5,-1,0,-1,7,6,-1") },
-
-    { njs_str("var buf = Buffer.from('ZABCDABC').subarray(1);"
-              "['BC', 'CB', 'ABCD', 'ABCDE', '', 'C', 'ABCDABCD'].map(v=>buf.lastIndexOf(v))"),
-      njs_str("5,-1,0,-1,7,6,-1") },
-
-    { njs_str("var buf = Buffer.from('ZABCDABC').subarray(1);"
-              "['BC', 'CB', 'ABCD', 'ABCDE', '', 'C', 'ABCDABCD'].map(v=>buf.lastIndexOf(Buffer.from(v)))"),
-      njs_str("5,-1,0,-1,7,6,-1") },
-
-    { njs_str("var buf = Buffer.from('CABCD');"
-              "[2,-2,1,-10,10,-5,-4,0].map(v=>buf.lastIndexOf('C', v))"),
-      njs_str("0,3,0,-1,3,0,0,0") },
-
-    { njs_str("var buf = Buffer.from('CABCD');"
-              "[2,-2,1,-10,10,-5,-4,0].map(v=>buf.lastIndexOf(Buffer.from('CZ').subarray(0,1), v))"),
-      njs_str("0,3,0,-1,3,0,0,0") },
-
-    { njs_str("var buf = Buffer.from('CABCD');"
-              "buf.lastIndexOf(0x43)"),
-      njs_str("3") },
-
-    { njs_str("var buf = Buffer.from('CABCD');"
-              "[2,1,0,4,5,-1,-5].map(v=>buf.lastIndexOf(0x43, v))"),
-      njs_str("0,0,0,3,3,3,0") },
-
-    { njs_str("var buf1 = Buffer.from('ACBCD').subarray(1, 4);"
-              "var buf2 = Buffer.from('C');"
-              "buf1.lastIndexOf(buf2)"),
-      njs_str("2") },
-
-    { njs_str("var buf1 = Buffer.from('XXCXX').subarray(2,3);"
-              "buf1.lastIndexOf(Buffer.from('X'))"),
-      njs_str("-1") },
-
-    { njs_str("var buf = Buffer.from('ACBCD').subarray(1, 4);"
-              "buf.lastIndexOf(0x43)"),
-      njs_str("2") },
-
-    { njs_str("var buf = Buffer.from('ACBCD').subarray(1, 4);"
-              "buf.lastIndexOf('C')"),
-      njs_str("2") },
-
-    { njs_str("Buffer.from('abcdef').lastIndexOf('abc', 1)"),
-      njs_str("0") },
-
-    { njs_str("['swap16', 'swap32', 'swap64'].every(method => {"
-              "    var buf = Buffer.from([]);"
-              "    buf[method]();"
-              "    return njs.dump(buf) === 'Buffer []';"
-              "})"),
-      njs_str("true") },
-
-    { njs_str("['swap16', 'swap32', 'swap64'].every(method => {"
-              "    var buf = Buffer.from([1,2,3]);"
-              "    try { buf[method]() } "
-              "    catch(e) {return e.message === `Buffer size must be a multiple of ${method.substr(4)}-bits`};"
-              "})"),
-      njs_str("true") },
-
-    { njs_str("['swap16', 'swap32', 'swap64'].map(method => {"
-              "    var buf = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]);"
-              "    buf[method]();"
-              "    return njs.dump(buf);"
-              "})"),
-      njs_str("Buffer [2,1,4,3,6,5,8,7],"
-              "Buffer [4,3,2,1,8,7,6,5],"
-              "Buffer [8,7,6,5,4,3,2,1]") },
-
-    { njs_str("['swap16', 'swap32', 'swap64'].map(method => {"
-              "    var u8 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8]);"
-              "    var buf = Buffer.from(u8.buffer, 1);"
-              "    buf[method]();"
-              "    return njs.dump(buf);"
-              "})"),
-      njs_str("Buffer [2,1,4,3,6,5,8,7],"
-              "Buffer [4,3,2,1,8,7,6,5],"
-              "Buffer [8,7,6,5,4,3,2,1]") },
-
-    { njs_str("["
-              " [['base64'], 'ZXZpbA=='],"
-              " [['base64url'], 'ZXZpbA'],"
-              " [['hex'], '6576696c'],"
-              " [[undefined,1,3], 'vi'],"
-              " [[undefined,5], ''],"
-              " [[undefined,undefined,5], 'evil'],"
-              " [[undefined,undefined,undefined], 'evil'],"
-              "].every(args => {"
-              "   var buf = Buffer.from('evil');"
-              "   var as = args[0];"
-              "   if (buf.toString(as[0], as[1], as[2]) != args[1]) {"
-              "       throw new TypeError("
-              "            `buf.toString(${as[0]}, ${as[1]}, ${as[2]}) != ${args[1]}`);"
-              "   }"
-              "   return true;"
-              "})"),
-      njs_str("true") },
-
-    { njs_str("var buf = Buffer.allocUnsafe(4);"
-              "var len = buf.write('ZXZpbA==', 'base64'); [len, buf]"),
-      njs_str("4,evil") },
-
-    { njs_str("var buf = Buffer.allocUnsafe(4);"
-              "var len = buf.write('ZXZpbA==', undefined, 'base64'); [len, buf]"),
-      njs_str("4,evil") },
-
-    { njs_str("var buf = Buffer.allocUnsafe(4);"
-              "var len = buf.write('ZXZpbA==', undefined, undefined, 'base64'); [len, buf]"),
-      njs_str("4,evil") },
-
-    { njs_str("Buffer.allocUnsafe(4).write()"),
-      njs_str("TypeError: first argument must be a string") },
-
-    { njs_str("Buffer.allocUnsafe(4).write({a: 1})"),
-      njs_str("TypeError: first argument must be a string") },
-
-    { njs_str("Buffer.alloc(4).write('evil', 4, 1);"),
-      njs_str("RangeError: \"offset\" is out of range") },
-
-    { njs_str("Buffer.alloc(4).write('evil', -1);"),
-      njs_str("RangeError: invalid index") },
-
-    { njs_str("var buf = Buffer.alloc(4);"
-              "var len = buf.write('evil', 3, 1); [len, njs.dump(buf)]"),
-      njs_str("1,Buffer [0,0,0,101]") },
-
-    { njs_str("var buf = Buffer.alloc(4);"
-              "var len = buf.write('evil', 0, 5); [len, buf]"),
-      njs_str("4,evil") },
-
-    { njs_str("Buffer.alloc(4).write('evil', undefined, -1);"),
-      njs_str("RangeError: invalid index") },
-
-    { njs_str("var buf = Buffer.from([0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xF9, 0xf8]);"
-              "[1,2,3,4,5,6].map(byte => [buf.readUIntLE(0, byte), buf.readUIntLE(1, byte)])"),
-      njs_str("250,251,"
-              "64506,64763,"
-              "16579578,16645371,"
-              "4261215226,4278058235,"
-              "1095182908410,1099494718715,"
-              "281470647991290,274877890034939") },
-
-    { njs_str("var buf = Buffer.from([0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xF9, 0xf8]);"
-              "[1,2,3,4,5,6].map(byte => [buf.readIntLE(0, byte), buf.readIntLE(1, byte)])"),
-      njs_str("-6,-5,"
-              "-1030,-773,"
-              "-197638,-131845,"
-              "-33752070,-16909061,"
-              "-4328719366,-16909061,"
-              "-4328719366,-6597086675717") },
-
-    { njs_str("var buf = Buffer.from([0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xF9, 0xf8]);"
-              "[1,2,3,4,5,6].map(byte => [buf.readUIntBE(0, byte), buf.readUIntBE(1, byte)])"),
-      njs_str("250,251,"
-              "64251,64508,"
-              "16448508,16514301,"
-              "4210818301,4227661310,"
-              "1077969485310,1082281295615,"
-              "275960188239615,277064011677689") },
-
-    { njs_str("var buf = Buffer.from([0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xF9, 0xf8]);"
-              "[1,2,3,4,5,6].map(byte => [buf.readIntBE(0, byte), buf.readIntBE(1, byte)])"),
-      njs_str("-6,-5,"
-              "-1285,-1028,"
-              "-328708,-262915,"
-              "-84148995,-67305986,"
-              "-21542142466,-17230332161,"
-              "-5514788471041,-4410965032967") },
-
-    { njs_str("var buf = Buffer.from([0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xF9, 0xf8]);"
-              "function t(sign, endianness, offset) { "
-              "    return [1,2,4].every(size => {"
-              "        var method = `read${sign}Int${size * 8}`;"
-              "        if (size > 1) { method += endianness};"
-              "        var gmethod = `read${sign}Int${endianness}`;"
-              "        var gv = buf[gmethod](offset, size);"
-              "        var sv = buf[method](offset);"
-              "        if (gv != sv) {throw Error(`${gmethod}(${offset},${size}):${gv} != ${method}(${offset}):${sv}`)}"
-              "        return true;"
-              "   });"
-              "}; "
-              "t('U', 'LE', 0) && t('U', 'LE', 1)"
-              "&& t('', 'LE', 0) && t('', 'LE', 1)"
-              "&& t('U', 'BE', 0) && t('U', 'BE', 1)"
-              "&& t('', 'BE', 0) && t('', 'BE', 1)"),
-      njs_str("true") },
-
-    { njs_str("var buf = Buffer.alloc(9);"
-              "function t(sign, endianness, offset) { "
-              "    return [1,2,4].every(size => {"
-              "        var rgmethod = `read${sign}Int${endianness}`;"
-              "        var wgmethod = `write${sign}Int${endianness}`;"
-              "        var rmethod = `read${sign}Int${size * 8}`;"
-              "        var wmethod = `write${sign}Int${size * 8}`;"
-              "        if (size > 1) { rmethod += endianness; wmethod += endianness; };"
-              "        var v = 0x7abbccddeeff & (size * 8 - 1);"
-              ""
-              "        var ret = buf[wgmethod](v, offset, size);"
-              "        if(ret !== offset + size) {"
-              "            throw Error(`${wgmethod} returned ${ret}, need ${offset + size}`);"
-              "        }"
-              ""
-              "        var gv = buf[rgmethod](offset, size);"
-              ""
-              "        buf.fill(0);"
-              "        buf[wmethod](v, offset);"
-              "        var sv = buf[rmethod](offset);"
-              "        if (gv != sv) {throw Error(`${wmethod}(${v}, ${offset}):${sv} != ${wgmethod}(${v},${offset}):${gv}`)}"
-              "        return true;"
-              "   });"
-              "}; "
-              "t('U', 'LE', 0) && t('U', 'LE', 1)"
-              "&& t('', 'LE', 0) && t('', 'LE', 1)"
-              "&& t('U', 'BE', 0) && t('U', 'BE', 1)"
-              "&& t('', 'BE', 0) && t('', 'BE', 1)"),
-      njs_str("true") },
-
-    { njs_str(njs_buffer_byte_map("writeUIntLE", "+", 1)),
-      njs_str("Buffer [128,0,0,0,0,0],"
-              "Buffer [0,64,0,0,0,0],"
-              "Buffer [0,0,32,0,0,0],"
-              "Buffer [0,0,0,16,0,0],"
-              "Buffer [0,0,0,0,8,0],"
-              "Buffer [0,0,0,0,0,4]") },
-
-    { njs_str(njs_buffer_byte_map("writeUIntBE", "+", 1)),
-      njs_str("Buffer [128,0,0,0,0,0],"
-              "Buffer [64,0,0,0,0,0],"
-              "Buffer [32,0,0,0,0,0],"
-              "Buffer [16,0,0,0,0,0],"
-              "Buffer [8,0,0,0,0,0],"
-              "Buffer [4,0,0,0,0,0]") },
-
-    { njs_str(njs_buffer_byte_map("writeIntLE", "-", 2)),
-      njs_str("Buffer [192,0,0,0,0,0],"
-              "Buffer [0,224,0,0,0,0],"
-              "Buffer [0,0,240,0,0,0],"
-              "Buffer [0,0,0,248,0,0],"
-              "Buffer [0,0,0,0,252,0],"
-              "Buffer [0,0,0,0,0,254]") },
-
-    { njs_str(njs_buffer_byte_map("writeIntBE", "-", 2)),
-      njs_str("Buffer [192,0,0,0,0,0],"
-              "Buffer [224,0,0,0,0,0],"
-              "Buffer [240,0,0,0,0,0],"
-              "Buffer [248,0,0,0,0,0],"
-              "Buffer [252,0,0,0,0,0],"
-              "Buffer [254,0,0,0,0,0]") },
-
-    { njs_str("Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]).readDoubleBE()"),
-      njs_str("8.20788039913184e-304") },
-
-    { njs_str("Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]).readDoubleLE()"),
-      njs_str("5.447603722011605e-270") },
-
-    { njs_str("Buffer.from([1, 2, 3, 4]).readFloatBE()"),
-      njs_str("2.387939260590663e-38") },
-
-    { njs_str("Buffer.from([1, 2, 3, 4]).readFloatLE()"),
-      njs_str("1.539989614439558e-36") },
-
-    { njs_str("Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]).readDoubleBE(1)"),
-      njs_str("RangeError: index 1 is outside the bound of the buffer") },
-
-    { njs_str("Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]).readDoubleLE(1)"),
-      njs_str("RangeError: index 1 is outside the bound of the buffer") },
-
-    { njs_str("Buffer.from([1, 2, 3, 4]).readFloatBE(1)"),
-      njs_str("RangeError: index 1 is outside the bound of the buffer") },
-
-    { njs_str("Buffer.from([1, 2, 3, 4]).readFloatLE(1)"),
-      njs_str("RangeError: index 1 is outside the bound of the buffer") },
-
-    { njs_str("var buf = Buffer.allocUnsafe(8);"
-              "buf.writeDoubleBE(123.456); njs.dump(buf)"),
-      njs_str("Buffer [64,94,221,47,26,159,190,119]") },
-
-    { njs_str("var buf = Buffer.allocUnsafe(8);"
-              "buf.writeDoubleLE(123.456); njs.dump(buf)"),
-      njs_str("Buffer [119,190,159,26,47,221,94,64]") },
-
-    { njs_str("var buf = Buffer.allocUnsafe(4);"
-              "buf.writeFloatBE(123.456); njs.dump(buf)"),
-      njs_str("Buffer [66,246,233,121]") },
-
-    { njs_str("var buf = Buffer.allocUnsafe(4);"
-              "buf.writeFloatLE(123.456); njs.dump(buf)"),
-      njs_str("Buffer [121,233,246,66]") },
-
-    { njs_str("var buf = Buffer.allocUnsafe(8).writeDoubleBE(123.456, 1)"),
-      njs_str("RangeError: index 1 is outside the bound of the buffer") },
-
-    { njs_str("var buf = Buffer.allocUnsafe(8).writeDoubleLE(123.456, 1)"),
-      njs_str("RangeError: index 1 is outside the bound of the buffer") },
-
-    { njs_str("var buf = Buffer.allocUnsafe(4).writeFloatBE(123.456, 1)"),
-      njs_str("RangeError: index 1 is outside the bound of the buffer") },
-
-    { njs_str("var buf = Buffer.allocUnsafe(4).writeFloatLE(123.456, 1)"),
-      njs_str("RangeError: index 1 is outside the bound of the buffer") },
-
-    { njs_str("var buffer = require('buffer');"
-              "buffer.Buffer.alloc(5).fill('ABC')"),
-      njs_str("ABCAB") },
-
-    { njs_str("var buffer = require('buffer');"
-              "typeof buffer.kMaxLength === 'number' "),
-      njs_str("true") },
-
-    { njs_str("var buffer = require('buffer');"
-              "typeof buffer.constants.MAX_LENGTH === 'number' "),
-      njs_str("true") },
-
-    { njs_str("var buffer = require('buffer');"
-              "typeof buffer.constants.MAX_STRING_LENGTH === 'number' "),
-      njs_str("true") },
-};
-
-
 static njs_unit_test_t  njs_webcrypto_test[] =
 {
     /* Statistic test
@@ -24891,12 +24080,6 @@ static njs_test_suite_t  njs_suites[] =
       njs_nitems(njs_querystring_module_test),
       njs_unit_test },
 
-    { njs_str("buffer module"),
-      { .repeat = 1, .unsafe = 1 },
-      njs_buffer_module_test,
-      njs_nitems(njs_buffer_module_test),
-      njs_unit_test },
-
     { njs_str("externals"),
       { .externals = 1, .repeat = 1, .unsafe = 1 },
       njs_externals_test,
diff --git a/test/buffer.t.js b/test/buffer.t.js
index 1d96d342..55227b3a 100644
--- a/test/buffer.t.js
+++ b/test/buffer.t.js
@@ -1,5 +1,5 @@
 /*---
-includes: [compatBuffer.js, runTsuite.js]
+includes: [compatBuffer.js, runTsuite.js, compareArray.js]
 flags: [async]
 ---*/
 
@@ -10,6 +10,297 @@ function p(args, default_opts) {
     return params;
 }
 
+let alloc_tsuite = {
+    name: "Buffer.alloc() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let r = Buffer.alloc(params.size, params.fill, params.encoding);
+
+        if (r.toString() !== params.expected) {
+            throw Error(`unexpected output "${r.toString()}" != "${params.expected}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: { encoding: 'utf-8' },
+
+    tests: [
+        { size: 3, fill: 0x61, expected: 'aaa' },
+        { size: 3, fill: 'A', expected: 'AAA' },
+        { size: 3, fill: 'ABCD', expected: 'ABC' },
+        { size: 3, fill: '414243', encoding: 'hex', expected: 'ABC' },
+        { size: 4, fill: '414243', encoding: 'hex', expected: 'ABCA' },
+        { size: 3, fill: 'QUJD', encoding: 'base64', expected: 'ABC' },
+        { size: 3, fill: 'QUJD', encoding: 'base64url', expected: 'ABC' },
+        { size: 3, fill: Buffer.from('ABCD'), encoding: 'utf-8', expected: 'ABC' },
+        { size: 3, fill: Buffer.from('ABCD'), encoding: 'utf8', expected: 'ABC' },
+        { size: 3, fill: 'ABCD', encoding: 'utf-128',
+          exception: 'TypeError: "utf-128" encoding is not supported' },
+        { size: 3, fill: Buffer.from('def'), expected: 'def' },
+    ],
+};
+
+
+let byteLength_tsuite = {
+    name: "Buffer.byteLength() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let r = Buffer.byteLength(params.value, params.encoding);
+
+        if (r !== params.expected) {
+            throw Error(`unexpected output "${r}" != "${params.expected}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: { encoding: 'utf-8' },
+
+    tests: [
+        { value: 'abc', expected: 3 },
+        { value: 'αβγ', expected: 6 },
+        { value: 'αβγ', encoding: 'utf-8', expected: 6 },
+        { value: 'αβγ', encoding: 'utf8', expected: 6 },
+        { value: 'αβγ', encoding: 'utf-128', exception: 'TypeError: "utf-128" encoding is not supported' },
+        { value: '414243', encoding: 'hex', expected: 3 },
+        { value: 'QUJD', encoding: 'base64', expected: 3 },
+        { value: 'QUJD', encoding: 'base64url', expected: 3 },
+        { value: Buffer.from('αβγ'), expected: 6 },
+        { value: Buffer.alloc(3).buffer, expected: 3 },
+        { value: Buffer.from(new Uint8Array([0x60, 0x61, 0x62, 0x63]).buffer, 1), expected: 3 },
+    ],
+};
+
+
+let concat_tsuite = {
+    name: "Buffer.concat() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let r = Buffer.concat(params.buffers, params.length);
+
+        if (r.toString() !== params.expected) {
+            throw Error(`unexpected output "${r.toString()}" != "${params.expected}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: {},
+    tests: [
+        { buffers: [ Buffer.from('abc'),
+                     Buffer.from(new Uint8Array([0x64, 0x65, 0x66]).buffer, 1) ],
+          expected: 'abcef' },
+        { buffers: [ Buffer.from('abc'), Buffer.from('def'), Buffer.from('') ],
+          expected: 'abcdef' },
+        { buffers: [ Buffer.from(''), Buffer.from('abc'), Buffer.from('def') ],
+          length: 4, expected: 'abcd' },
+    ],
+};
+
+
+let compare_tsuite = {
+    name: "Buffer.compare() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let r = Buffer.compare(params.buf1, params.buf2);
+
+        if (r !== params.expected) {
+            throw Error(`unexpected output "${r}" != "${params.expected}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: {},
+
+    tests: [
+        { buf1: Buffer.from('abc'), buf2: Buffer.from('abc'), expected: 0 },
+        { buf1: Buffer.from('abc'),
+          buf2: Buffer.from(new Uint8Array([0x60, 0x61, 0x62, 0x63]).buffer, 1),
+          expected: 0 },
+        { buf1: Buffer.from(new Uint8Array([0x60, 0x61, 0x62, 0x63]).buffer, 1),
+          buf2: Buffer.from('abc'),
+          expected: 0 },
+        { buf1: Buffer.from('abc'), buf2: Buffer.from('def'), expected: -1 },
+        { buf1: Buffer.from('def'), buf2: Buffer.from('abc'), expected: 1 },
+        { buf1: Buffer.from('abc'), buf2: Buffer.from('abcd'), expected: -1 },
+        { buf1: Buffer.from('abcd'), buf2: Buffer.from('abc'), expected: 1 },
+        { buf1: Buffer.from('abc'), buf2: Buffer.from('ab'), expected: 1 },
+        { buf1: Buffer.from('ab'), buf2: Buffer.from('abc'), expected: -1 },
+        { buf1: Buffer.from('abc'), buf2: Buffer.from(''), expected: 1 },
+        { buf1: Buffer.from(''), buf2: Buffer.from('abc'), expected: -1 },
+        { buf1: Buffer.from(''), buf2: Buffer.from(''), expected: 0 },
+    ],
+};
+
+
+let comparePrototype_tsuite = {
+    name: "buf.compare() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let r = params.buf.compare(params.target, params.tStart, params.tEnd,
+                                   params.sStart, params.sEnd);
+
+
+        if (r !== params.expected) {
+            throw Error(`unexpected output "${r}" != "${params.expected}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: {},
+
+    tests: [
+        { buf: Buffer.from('abc'), target: Buffer.from('abc'), expected: 0 },
+        { buf: Buffer.from('abc'),
+          target: Buffer.from(new Uint8Array([0x60, 0x61, 0x62, 0x63]).buffer, 1),
+          expected: 0 },
+        { buf: Buffer.from(new Uint8Array([0x60, 0x61, 0x62, 0x63]).buffer, 1),
+          target: Buffer.from('abc'), expected: 0 },
+        { buf: Buffer.from('abc'), target: Buffer.from('def'), expected: -1 },
+        { buf: Buffer.from('def'), target: Buffer.from('abc'), expected: 1 },
+        { buf: Buffer.from('abc'), target: Buffer.from('abcd'), expected: -1 },
+        { buf: Buffer.from('abcd'), target: Buffer.from('abc'), expected: 1 },
+        { buf: Buffer.from('abc'), target: Buffer.from('ab'), expected: 1 },
+        { buf: Buffer.from('ab'), target: Buffer.from('abc'), expected: -1 },
+        { buf: Buffer.from('abc'), target: Buffer.from(''), expected: 1 },
+        { buf: Buffer.from(''), target: Buffer.from('abc'), expected: -1 },
+        { buf: Buffer.from(''), target: Buffer.from(''), expected: 0 },
+
+        { buf: Buffer.from('abcdef'), target: Buffer.from('abc'),
+          sEnd: 3, expected: 0 },
+        { buf: Buffer.from('abcdef'), target: Buffer.from('def'),
+          sStart: 3, expected: 0 },
+        { buf: Buffer.from('abcdef'), target: Buffer.from('abc'),
+          sStart: 0, sEnd: 3, expected: 0 },
+        { buf: Buffer.from('abcdef'), target: Buffer.from('def'),
+          sStart: 3, sEnd: 6, expected: 0 },
+        { buf: Buffer.from('abcdef'), target: Buffer.from('def'),
+          sStart: 3, sEnd: 5, tStart: 0, tEnd: 2, expected: 0 },
+    ],
+};
+
+
+let copy_tsuite = {
+    name: "buf.copy() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let r = params.buf.copy(params.target, params.tStart, params.sStart, params.sEnd);
+
+        if (r !== params.expected) {
+            throw Error(`unexpected output "${r}" != "${params.expected}"`);
+        }
+
+        if (params.target.toString() !== params.expected_buf) {
+            throw Error(`unexpected buf "${params.target.toString()}" != "${params.expected_buf}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: {},
+
+    tests: [
+        { buf: Buffer.from('abcdef'), target: Buffer.from('123456'),
+          expected: 6, expected_buf: 'abcdef' },
+        { buf: Buffer.from('abcdef'), target: Buffer.from('123456'),
+          tStart: 0, expected: 6, expected_buf: 'abcdef' },
+        { buf: Buffer.from('abc'), target: Buffer.from('123456789'),
+          tStart: 5, expected: 3, expected_buf: '12345abc9' },
+        { buf: Buffer.from('abcdef'), target: Buffer.from('123456'),
+          tStart: 0, sStart: 0, expected: 6, expected_buf: 'abcdef' },
+        { buf: Buffer.from('abcdef'), target: Buffer.from('123456'),
+          tStart: 0, sStart: 0, sEnd: 3, expected: 3, expected_buf: 'abc456' },
+        { buf: Buffer.from('abcdef'), target: Buffer.from('123456'),
+          tStart: 2, sStart: 2, sEnd: 3, expected: 1, expected_buf: '12c456' },
+        { buf: Buffer.from('abcdef'), target: Buffer.from('123456'),
+          tStart: 7, exception: 'RangeError: \"targetStart\" is out of bounds' },
+        { buf: Buffer.from('abcdef'), target: Buffer.from('123456'),
+          sStart: 7, exception: 'RangeError: \"sourceStart\" is out of bounds' },
+    ],
+};
+
+
+let equals_tsuite = {
+    name: "buf.equals() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let r = params.buf1.equals(params.buf2);
+
+        if (r !== params.expected) {
+            throw Error(`unexpected output "${r}" != "${params.expected}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: {},
+    tests: [
+
+        { buf1: Buffer.from('abc'), buf2: Buffer.from('abc'), expected: true },
+        { buf1: Buffer.from('abc'),
+          buf2: Buffer.from(new Uint8Array([0x60, 0x61, 0x62, 0x63]).buffer, 1),
+          expected: true },
+        { buf1: Buffer.from(new Uint8Array([0x60, 0x61, 0x62, 0x63]).buffer, 1),
+          buf2: Buffer.from('abc'), expected: true },
+        { buf1: Buffer.from('abc'), buf2: Buffer.from('def'), expected: false },
+        { buf1: Buffer.from('def'), buf2: Buffer.from('abc'), expected: false },
+        { buf1: Buffer.from('abc'), buf2: Buffer.from('abcd'), expected: false },
+        { buf1: Buffer.from('abcd'), buf2: Buffer.from('abc'), expected: false },
+        { buf1: Buffer.from('abc'), buf2: Buffer.from('ab'), expected: false },
+        { buf1: Buffer.from('ab'), buf2: Buffer.from('abc'), expected: false },
+        { buf1: Buffer.from('abc'), buf2: Buffer.from(''), expected: false },
+        { buf1: Buffer.from(''), buf2: Buffer.from('abc'), expected: false },
+        { buf1: Buffer.from(''), buf2: Buffer.from(''), expected: true },
+    ],
+};
+
+
+let fill_tsuite = {
+    name: "buf.fill() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let r = params.buf.fill(params.value, params.offset, params.end);
+
+        if (r.toString() !== params.expected) {
+            throw Error(`unexpected output "${r.toString()}" != "${params.expected}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: {},
+    tests: [
+        { buf: Buffer.from('abc'), value: 0x61, expected: 'aaa' },
+        { buf: Buffer.from('abc'), value: 0x61, expected: 'aaa', offset: 0, end: 3 },
+        { buf: Buffer.from('abc'), value: 0x61, expected: 'abc', offset: 0, end: 0 },
+        { buf: Buffer.from('abc'), value: 'A', expected: 'AAA' },
+        { buf: Buffer.from('abc'), value: 'ABCD', expected: 'ABC' },
+        { buf: Buffer.from('abc'), value: '414243', offset: 'hex', expected: 'ABC' },
+        { buf: Buffer.from('abc'), value: '414243', offset: 'utf-128',
+          exception: 'TypeError: "utf-128" encoding is not supported' },
+        { buf: Buffer.from('abc'), value: 'ABCD', offset: 1, expected: 'aAB' },
+        { buf: Buffer.from('abc'), value: Buffer.from('def'), expected: 'def' },
+        { buf: Buffer.from('def'),
+          value: Buffer.from(new Uint8Array([0x60, 0x61, 0x62, 0x63]).buffer, 1),
+          expected: 'abc' },
+        { buf: Buffer.from(new Uint8Array([0x60, 0x61, 0x62, 0x63]).buffer, 1),
+          value: Buffer.from('def'),
+          expected: 'def' },
+    ],
+};
+
+
 let from_tsuite = {
     name: "Buffer.from() tests",
     skip: () => (!has_buffer()),
@@ -20,6 +311,12 @@ let from_tsuite = {
             params.modify(buf);
         }
 
+        if (params.args[0] instanceof ArrayBuffer) {
+            if (buf.buffer !== params.args[0]) {
+                throw Error(`unexpected buffer "${buf.buffer}" != "${params.args[0]}"`);
+            }
+        }
+
         let r = buf.toString(params.fmt);
 
         if (r.length !== params.expected.length) {
@@ -48,7 +345,6 @@ let from_tsuite = {
         { 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; },
@@ -128,6 +424,68 @@ let from_tsuite = {
         { args: ['QUJDRA#', "base64url"], expected: 'ABCD' },
 ]};
 
+
+let includes_tsuite = {
+    name: "buf.includes() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let r = params.buf.includes(params.value, params.offset, params.encoding);
+
+        if (r !== params.expected) {
+            throw Error(`unexpected output "${r}" != "${params.expected}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: {},
+
+    tests: [
+        { buf: Buffer.from('abcdef'), value: 'abc', expected: true },
+        { buf: Buffer.from('abcdef'), value: 'def', expected: true },
+        { buf: Buffer.from('abcdef'), value: 'abc', offset: 1, expected: false },
+        { buf: Buffer.from('abcdef'), value: {},
+          exception: 'TypeError: "value" argument must be of type string or an instance of Buffer' },
+    ],
+};
+
+
+let indexOf_tsuite = {
+    name: "buf.indexOf() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let r = params.buf.indexOf(params.value, params.offset, params.encoding);
+
+        if (r !== params.expected) {
+            throw Error(`unexpected output "${r}" != "${params.expected}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: {},
+
+    tests: [
+        { buf: Buffer.from('abcdef'), value: 'abc', expected: 0 },
+        { buf: Buffer.from('abcdef'), value: 'def', expected: 3 },
+        { buf: Buffer.from('abcdef'), value: 'abc', offset: 1, expected: -1 },
+        { buf: Buffer.from('abcdef'), value: 'def', offset: 1, expected: 3 },
+        { buf: Buffer.from('abcdef'), value: 'def', offset: -3, expected: 3 },
+        { buf: Buffer.from('abcdef'), value: '626364', encoding: 'hex', expected: 1 },
+        { buf: Buffer.from('abcdef'), value: '626364', encoding: 'utf-128',
+          exception: 'TypeError: "utf-128" encoding is not supported' },
+        { buf: Buffer.from('abcdef'), value: 0x62, expected: 1 },
+        { buf: Buffer.from('abcabc'), value: 0x61, offset: 1, expected: 3 },
+        { buf: Buffer.from('abcdef'), value: Buffer.from('def'), expected: 3 },
+        { buf: Buffer.from('abcdef'), value: Buffer.from(new Uint8Array([0x60, 0x62, 0x63]).buffer, 1), expected: 1 },
+        { buf: Buffer.from('abcdef'), value: {},
+          exception: 'TypeError: "value" argument must be of type string or an instance of Buffer' },
+    ],
+};
+
+
 let isBuffer_tsuite = {
     name: "Buffer.isBuffer() tests",
     skip: () => (!has_buffer()),
@@ -151,6 +509,33 @@ let isBuffer_tsuite = {
 ]};
 
 
+let isEncoding_tsuite = {
+    name: "Buffer.isEncoding() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let r = Buffer.isEncoding(params.value);
+
+        if (r !== params.expected) {
+            throw Error(`unexpected output "${r}" != "${params.expected}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: {},
+
+    tests: [
+        { value: 'utf-8', expected: true },
+        { value: 'utf8', expected: true },
+        { value: 'utf-128', expected: false },
+        { value: 'hex', expected: true },
+        { value: 'base64', expected: true },
+        { value: 'base64url', expected: true },
+    ],
+};
+
+
 function compare_object(a, b) {
     if (a === b) {
         return true;
@@ -175,6 +560,264 @@ function compare_object(a, b) {
 }
 
 
+let lastIndexOf_tsuite = {
+    name: "buf.lastIndexOf() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let r = params.buf.lastIndexOf(params.value, params.offset, params.encoding);
+
+        if (r !== params.expected) {
+            throw Error(`unexpected output "${r}" != "${params.expected}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: {},
+
+    tests: [
+        { buf: Buffer.from('abcdef'), value: 'abc', expected: 0 },
+        { buf: Buffer.from('abcabc'), value: 'abc', expected: 3 },
+        { buf: Buffer.from('abcdef'), value: 'def', expected: 3 },
+        { buf: Buffer.from('abcdef'), value: 'abc', offset: 1, expected: 0 },
+        { buf: Buffer.from('abcdef'), value: 'def', offset: 1, expected: -1 },
+        { buf: Buffer.from(Buffer.alloc(7).fill('Zabcdef').buffer, 1), value: 'abcdef', expected: 0 },
+        { buf: Buffer.from(Buffer.alloc(7).fill('Zabcdef').buffer, 1), value: 'abcdefg', expected: -1 },
+        { buf: Buffer.from('abcdef'), value: '626364', encoding: 'hex', expected: 1 },
+        { buf: Buffer.from('abcdef'), value: '626364', encoding: 'utf-128',
+          exception: 'TypeError: "utf-128" encoding is not supported' },
+        { buf: Buffer.from('abcabc'), value: 0x61, expected: 3 },
+        { buf: Buffer.from('abcabc'), value: 0x61, offset: 1, expected: 0 },
+        { buf: Buffer.from('abcdef'), value: Buffer.from('def'), expected: 3 },
+        { buf: Buffer.from('abcdef'), value: Buffer.from(new Uint8Array([0x60, 0x62, 0x63]).buffer, 1), expected: 1 },
+        { buf: Buffer.from('abcdef'), value: {},
+          exception: 'TypeError: "value" argument must be of type string or an instance of Buffer' },
+    ],
+};
+
+
+let readXIntXX_tsuite = {
+    name: "buf.readXIntXX() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let b = params.buf;
+        let r = [
+                  b.readInt8(params.offset),
+                  b.readUInt8(params.offset),
+                  b.readInt16LE(params.offset),
+                  b.readInt16BE(params.offset),
+                  b.readUInt16LE(params.offset),
+                  b.readUInt16BE(params.offset),
+                  b.readInt32LE(params.offset),
+                  b.readInt32BE(params.offset),
+                  b.readUInt32LE(params.offset),
+                  b.readUInt32BE(params.offset),
+                ];
+
+
+        if (!compareArray(r, params.expected)) {
+            throw Error(`unexpected output "${r}" != "${params.expected}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: {},
+
+    tests: [
+        { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 0,
+          expected: [ -86,170,-17494,-21829,48042,43707,-573785174,-1430532899,3721182122,2864434397 ] },
+        { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 1,
+          expected: [ -69,187,-13125,-17460,52411,48076,-287454021,-1144201746,4007513275,3150765550 ] },
+        { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 2,
+          expected: [ -52,204,-8756,-13091,56780,52445,-1122868,-857870593,4293844428,3437096703 ] },
+        { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 3,
+          exception: 'RangeError: Index out of range' },
+    ],
+};
+
+
+let readFloat_tsuite = {
+    name: "buf.readFloat() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let b = Buffer.alloc(9);
+        let r = b.writeFloatLE(123.125, 0);
+        if (r !== 4) {
+            throw Error(`unexpected output "${r}" != "4"`);
+        }
+
+        if (b.readFloatLE(0) !== 123.125) {
+            throw Error(`unexpected output "${b.readFloatLE(0)}" != "123.125"`);
+        }
+
+        r = b.writeFloatBE(123.125, 0);
+        if (r !== 4) {
+            throw Error(`unexpected output "${r}" != "4"`);
+        }
+
+        if (b.readFloatBE(0) !== 123.125) {
+            throw Error(`unexpected output "${b.readFloatBE(0)}" != "123.125"`);
+        }
+
+        r = b.writeDoubleLE(123.125, 1);
+        if (r !== 9) {
+            throw Error(`unexpected output "${r}" != "9"`);
+        }
+
+        if (b.readDoubleLE(1) !== 123.125) {
+            throw Error(`unexpected output "${b.readDoubleLE(1)}" != "123.125"`);
+        }
+
+        r = b.writeDoubleBE(123.125, 1);
+        if (r !== 9) {
+            throw Error(`unexpected output "${r}" != "9"`);
+        }
+
+        if (b.readDoubleBE(1) !== 123.125) {
+            throw Error(`unexpected output "${b.readDoubleBE(1)}" != "123.125"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: {},
+
+    tests: [
+        {}
+    ],
+};
+
+
+let readGeneric_tsuite = {
+    name: "buf.readGeneric() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let b = params.buf;
+        let r = [
+                  b.readUIntLE(params.offset, params.length),
+                  b.readUIntBE(params.offset, params.length),
+                  b.readIntLE(params.offset, params.length),
+                  b.readIntBE(params.offset, params.length),
+                ];
+
+
+        if (!compareArray(r, params.expected)) {
+            throw Error(`unexpected output "${r}" != "${params.expected}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: {},
+
+    tests: [
+        { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 0, length: 1,
+          expected: [ 170, 170, -86, -86 ] },
+        { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 1, length: 2,
+          expected: [ 52411,48076,-13125,-17460 ] },
+        { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 2, length: 3,
+          expected: [ 15654348,13426158,-1122868,-3351058 ] },
+        { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 3, length: 4,
+          exception: 'RangeError: Index out of range' },
+        { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 0, length: 0,
+          exception: 'RangeError: byteLength must be <= 6' },
+        { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 0, length: 5,
+          expected: [ 1025923398570,733295205870,-73588229206,-366216421906 ] },
+        { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 0, length: 6,
+          expected: [ 281401388481450,187723572702975,-73588229206,-93751404007681 ] },
+    ],
+};
+
+
+let slice_tsuite = {
+    name: "buf.slice() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let r = params.buf.slice(params.start, params.end);
+
+        if (r.toString() !== params.expected) {
+            throw Error(`unexpected output "${r.toString()}" != "${params.expected}"`);
+        }
+
+        params.buf[2] = 0x5a;
+
+        if (r.constructor.name !== 'Buffer' || r.__proto__ !== params.buf.__proto__) {
+            throw Error(`unexpected output "${r.constructor.name}" != "Buffer"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: {},
+
+    tests: [
+        { buf: Buffer.from('abcdef'), start: 1, expected: 'bcdef' },
+        { buf: Buffer.from('abcdef'), start: 1, end: 3, expected: 'bc' },
+    ],
+};
+
+
+let subarray_tsuite = {
+    name: "buf.subarray() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let r = params.buf.subarray(params.start, params.end);
+
+        params.buf[0] = 0x5a;
+
+        if (r.toString() !== params.expected) {
+            throw Error(`unexpected output "${r.toString()}" != "${params.expected}"`);
+        }
+
+        if (r.constructor.name !== 'Buffer' || r.__proto__ !== params.buf.__proto__) {
+            throw Error(`unexpected output "${r.constructor.name}" != "Buffer"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: {},
+
+    tests: [
+        { buf: Buffer.from('abcdef'), start: 0, end: 3, expected: 'Zbc' },
+        { buf: Buffer.from('abcdef'), start: 1, expected: 'bcdef' },
+    ],
+};
+
+
+let swap_tsuite = {
+    name: "buf.swap() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let r = Buffer.from(params.value, 'hex')[params.swap]();
+
+        if (r.toString('hex') !== params.expected) {
+            throw Error(`unexpected output "${r.toString('hex')}" != "${params.expected}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: { swap: 'swap16' },
+
+    tests: [
+        { value: '01020304', expected: '02010403' },
+        { value: '010203', exception: 'RangeError: Buffer size must be a multiple of 2' },
+        { value: 'aabbccddeeff0011', swap: 'swap32', expected: 'ddccbbaa1100ffee' },
+        { value: 'aabbcc', swap: 'swap32', exception: 'RangeError: Buffer size must be a multiple of 4' },
+        { value: 'aabbccddeeff00112233445566778899', swap: 'swap64', expected: '1100ffeeddccbbaa9988776655443322' },
+    ],
+};
+
+
 let toJSON_tsuite = {
     name: "Buffer.toJSON() tests",
     skip: () => (!has_buffer()),
@@ -224,13 +867,181 @@ let toString_tsuite = {
         { 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: "ABCD", fmt: "base64", expected: 'QUJDRA==' },
+        { value: "ABCD", fmt: "base64url", expected: 'QUJDRA' },
         { value: '', fmt: "utf-128", exception: 'TypeError: "utf-128" encoding is not supported' },
 ]};
 
+
+let write_tsuite = {
+    name: "buf.write() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let b = Buffer.alloc(10).fill('Z');
+        let r;
+
+        if (typeof params.offset != 'undefined' && typeof params.length != 'undefined') {
+            r = b.write(params.value, params.offset, params.length, params.encoding);
+
+        } else if (typeof params.offset != 'undefined') {
+            r = b.write(params.value, params.offset, params.encoding);
+
+        } else {
+            r = b.write(params.value, params.encoding);
+        }
+
+        if (r !== params.expected) {
+            throw Error(`unexpected output "${r}" != "${params.expected}"`);
+        }
+
+        if (b.toString() !== params.expected_buf) {
+            throw Error(`unexpected output "${b.toString()}" != "${params.expected_buf}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: {},
+
+    tests: [
+        { value: 'abc', expected: 3, expected_buf: 'abcZZZZZZZ' },
+        { value: 'abc', offset: 1, expected: 3, expected_buf: 'ZabcZZZZZZ' },
+        { value: 'abc', offset: 1, length: 2, expected: 2, expected_buf: 'ZabZZZZZZZ' },
+        { value: 'αβγ', offset: 1, expected: 6, expected_buf: 'ZαβγZZZ' },
+        { value: 'αβγ', offset: 1, length: 1, expected: 0, expected_buf: 'ZZZZZZZZZZ' },
+        { value: 'αβγ', offset: 1, length: 2, expected: 2, expected_buf: 'ZαZZZZZZZ' },
+        { value: '414243', encoding: 'hex', expected: 3, expected_buf: 'ABCZZZZZZZ' },
+        { value: '414243', encoding: 'hex', offset: 8, expected: 2, expected_buf: 'ZZZZZZZZAB' },
+        { value: "x".repeat(12), expected: 10, expected_buf: 'xxxxxxxxxx' },
+        { value: "x".repeat(12), offset: 1, expected: 9, expected_buf: 'Zxxxxxxxxx' },
+        { value: "x", offset: 1, length: 2, encoding: 'utf-128',
+          exception: 'TypeError: "utf-128" encoding is not supported' },
+        { value: "x".repeat(10), offset: 10, expected: 0, expected_buf: 'ZZZZZZZZZZ' },
+        { value: "x".repeat(10), offset: 11, exception: 'RangeError: Index out of range' },
+    ],
+};
+
+
+let writeXIntXX_tsuite = {
+    name: "buf.writeXIntXX() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let b = Buffer.alloc(10).fill('Z');
+        let r = b[params.write](params.value, params.offset);
+
+        if (params.exception) {
+            throw Error(`expected exception "${params.exception}"`);
+        }
+
+        if (r !== params.expected) {
+            throw Error(`unexpected output "${r}" != "${params.expected}"`);
+        }
+
+        if (b.toString('hex') !== params.expected_buf) {
+            throw Error(`unexpected output "${b.toString('hex')}" != "${params.expected_buf}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: {},
+
+    tests: [
+        { write: 'writeInt8', value: 0xaa, exception: 'RangeError: Index out of range' },
+        { write: 'writeInt8', value: 0x00, offset: 3, expected: 4, expected_buf: '5a5a5a005a5a5a5a5a5a' },
+        { write: 'writeUInt8', value: 0xaa, offset: 0, expected: 1, expected_buf: 'aa5a5a5a5a5a5a5a5a5a' },
+        { write: 'writeInt16LE', value: 0xaabb, exception: 'RangeError: Index out of range' },
+        { write: 'writeInt16BE', value: 0x7788, offset: 1, expected: 3, expected_buf: '5a77885a5a5a5a5a5a5a' },
+        { write: 'writeUInt16LE', value: 0xaabb, offset: 0, expected: 2, expected_buf: 'bbaa5a5a5a5a5a5a5a5a' },
+        { write: 'writeUInt16BE', value: 0x7788, offset: 1, expected: 3, expected_buf: '5a77885a5a5a5a5a5a5a' },
+        { write: 'writeInt32LE', value: 0xaabbccdd, exception: 'RangeError: Index out of range' },
+        { write: 'writeInt32BE', value: 0x778899aa, offset: 1, expected: 5, expected_buf: '5a778899aa5a5a5a5a5a' },
+        { write: 'writeUInt32LE', value: 0xaabbccdd, offset: 0, expected: 4, expected_buf: 'ddccbbaa5a5a5a5a5a5a' },
+        { write: 'writeUInt32BE', value: 0x778899aa, offset: 1, expected: 5, expected_buf: '5a778899aa5a5a5a5a5a' },
+    ],
+};
+
+
+let writeGeneric_tsuite = {
+    name: "buf.writeGeneric() tests",
+    skip: () => (!has_buffer()),
+    T: async (params) => {
+        let b = Buffer.alloc(10).fill('Z');
+        let r = b[params.write](params.value, params.offset, params.length);
+
+        if (params.exception) {
+            throw Error(`expected exception "${params.exception}"`);
+        }
+
+        if (r !== params.expected) {
+            throw Error(`unexpected output "${r}" != "${params.expected}"`);
+        }
+
+        if (b.toString('hex') !== params.expected_buf) {
+            throw Error(`unexpected output "${b.toString('hex')}" != "${params.expected_buf}"`);
+        }
+
+        return 'SUCCESS';
+    },
+
+    prepare_args: p,
+    opts: {},
+
+    tests: [
+        { write: 'writeUIntLE', value: 0xaa, length: 1,
+          exception: 'RangeError: Index out of range' },
+        { write: 'writeUIntLE', value: 0x44, length: 1, offset: 3,
+          expected: 4, expected_buf: '5a5a5a445a5a5a5a5a5a' },
+        { write: 'writeUIntBE', value: 0xaabb, length: 2, offset: 0,
+          expected: 2, expected_buf: 'aabb5a5a5a5a5a5a5a5a' },
+        { write: 'writeUIntBE', value: 0x7788, length: 2, offset: 1,
+          expected: 3, expected_buf: '5a77885a5a5a5a5a5a5a' },
+        { write: 'writeIntLE', value: 0x445566, length: 3, offset: 5,
+          expected: 8, expected_buf: '5a5a5a5a5a6655445a5a' },
+        { write: 'writeIntBE', value: 0x778899, length: 3, offset: 1,
+          expected: 4, expected_buf: '5a7788995a5a5a5a5a5a' },
+        { write: 'writeIntLE', value: 0x44556677, length: 4, offset: 5,
+          expected: 9, expected_buf: '5a5a5a5a5a776655445a' },
+        { write: 'writeIntBE', value: 0xaabbccdd, length: 4, offset: 1,
+          exception: 'RangeError: Index out of range' },
+        { write: 'writeUIntLE', value: 0xaabbccddee, length: 5, offset: 0,
+          expected: 5, expected_buf: 'eeddccbbaa5a5a5a5a5a' },
+        { write: 'writeUIntBE', value: 0x778899aabbcc, length: 6, offset: 1,
+          expected: 7, expected_buf: '5a778899aabbcc5a5a5a' },
+        { write: 'writeUIntBE', value: 0, length: 7,
+          exception: 'The value of "byteLength" is out of range' },
+
+    ],
+};
+
+
 run([
+    alloc_tsuite,
+    byteLength_tsuite,
+    concat_tsuite,
+    compare_tsuite,
+    comparePrototype_tsuite,
+    copy_tsuite,
+    equals_tsuite,
+    fill_tsuite,
     from_tsuite,
+    includes_tsuite,
+    indexOf_tsuite,
     isBuffer_tsuite,
+    isEncoding_tsuite,
+    lastIndexOf_tsuite,
+    readXIntXX_tsuite,
+    readFloat_tsuite,
+    readGeneric_tsuite,
+    slice_tsuite,
+    subarray_tsuite,
+    swap_tsuite,
     toJSON_tsuite,
     toString_tsuite,
+    write_tsuite,
+    writeXIntXX_tsuite,
+    writeGeneric_tsuite,
 ])
 .then($DONE, $DONE);
diff --git a/test/harness/runTsuite.js b/test/harness/runTsuite.js
index dc2034fd..aa3f5c0f 100644
--- a/test/harness/runTsuite.js
+++ b/test/harness/runTsuite.js
@@ -48,6 +48,9 @@ function merge(to, from) {
             } else if (from[v] instanceof Uint8Array) {
                 r[v] = new Uint8Array(from[v]);
 
+            } else if (from[v] instanceof ArrayBuffer) {
+                r[v] = new ArrayBuffer(from[v].byteLength);
+
             } else {
                 r[v] = Object.assign(Array.isArray(from[v]) ? [] : {}, from[v]);
             }


More information about the nginx-devel mailing list