
/*
 * Copyright (C) Alexander Borisov
 * Copyright (C) Dmitry Volyntsev
 * Copyright (C) NGINX, Inc.
 */


#include <njs_main.h>


#define INT24_MAX  0x7FFFFF
#define INT40_MAX  0x7FFFFFFFFFULL
#define INT48_MAX  0x7FFFFFFFFFFFULL

#define njs_buffer_magic(size, sign, little)                                 \
    ((size << 2) | (sign << 1) | little)


static njs_buffer_encoding_t  njs_buffer_encodings[] =
{
    {
        njs_str("utf-8"),
        njs_string_decode_utf8,
        njs_string_decode_utf8,
        njs_decode_utf8_length
    },

    {
        njs_str("utf8"),
        njs_string_decode_utf8,
        njs_string_decode_utf8,
        njs_decode_utf8_length
    },

    {
        njs_str("hex"),
        njs_string_hex,
        njs_string_decode_hex,
        njs_decode_hex_length
    },

    {
        njs_str("base64"),
        njs_string_base64,
        njs_string_decode_base64,
        njs_decode_base64_length
    },

    {
        njs_str("base64url"),
        njs_string_base64url,
        njs_string_decode_base64url,
        njs_decode_base64url_length
    },

    { njs_null_str, 0, 0, 0 }
};


static njs_int_t njs_buffer_from_object(njs_vm_t *vm, njs_value_t *value);
static njs_int_t njs_buffer_from_array_buffer(njs_vm_t *vm, njs_value_t *value,
    njs_value_t *length, njs_value_t *offset);
static njs_int_t njs_buffer_from_typed_array(njs_vm_t *vm, njs_value_t *value);
static njs_int_t njs_buffer_from_string(njs_vm_t *vm, njs_value_t *value,
    const njs_buffer_encoding_t *encoding);
static njs_int_t njs_buffer_write_string(njs_vm_t *vm, njs_value_t *value,
    njs_typed_array_t *array, const njs_buffer_encoding_t *encoding,
    uint64_t offset, uint64_t length);
static njs_int_t njs_buffer_fill(njs_vm_t *vm, njs_typed_array_t *array,
    const njs_value_t *fill, const njs_value_t *encoding, uint64_t offset,
    uint64_t end);
static njs_int_t njs_buffer_fill_string(njs_vm_t *vm, const njs_value_t *value,
    njs_typed_array_t *array, const njs_buffer_encoding_t *encoding,
    uint8_t *start, uint8_t *end);
static njs_int_t njs_buffer_fill_typed_array(njs_vm_t *vm,
    const njs_value_t *value, njs_typed_array_t *array, uint8_t *start,
    uint8_t *end);
static void njs_buffer_decode_destroy(njs_vm_t *vm, const njs_value_t *source,
    njs_value_t *target);


njs_int_t
njs_buffer_set(njs_vm_t *vm, njs_value_t *value, const u_char *start,
    uint32_t size)
{
    njs_object_t        *proto;
    njs_typed_array_t   *array;
    njs_array_buffer_t  *buffer;

    array = njs_mp_alloc(vm->mem_pool, sizeof(njs_typed_array_t)
                                       + sizeof(njs_array_buffer_t));
    if (njs_slow_path(array == NULL)) {
        njs_memory_error(vm);
        return NJS_ERROR;
    }

    buffer = (njs_array_buffer_t *) &array[1];

    proto = &vm->prototypes[NJS_OBJ_TYPE_ARRAY_BUFFER].object;

    njs_lvlhsh_init(&buffer->object.hash);
    njs_lvlhsh_init(&buffer->object.shared_hash);
    buffer->object.__proto__ = proto;
    buffer->object.slots = NULL;
    buffer->object.type = NJS_ARRAY_BUFFER;
    buffer->object.shared = 1;
    buffer->object.extensible = 1;
    buffer->object.error_data = 0;
    buffer->object.fast_array = 0;
    buffer->u.data = (void *) start;
    buffer->size = size;

    proto = &vm->prototypes[NJS_OBJ_TYPE_BUFFER].object;

    array->type = NJS_OBJ_TYPE_UINT8_ARRAY;
    njs_lvlhsh_init(&array->object.hash);
    njs_lvlhsh_init(&array->object.shared_hash);
    array->object.__proto__ = proto;
    array->object.slots = NULL;
    array->object.type = NJS_TYPED_ARRAY;
    array->object.shared = 0;
    array->object.extensible = 1;
    array->object.error_data = 0;
    array->object.fast_array = 1;
    array->buffer = buffer;
    array->offset = 0;
    array->byte_length = size;

    njs_set_typed_array(value, array);

    return NJS_OK;
}


njs_typed_array_t *
njs_buffer_alloc(njs_vm_t *vm, size_t size, njs_bool_t zeroing)
{
    njs_value_t        value;
    njs_typed_array_t  *array;

    njs_set_number(&value, size);

    array = njs_typed_array_alloc(vm, &value, 1, zeroing,
                                  NJS_OBJ_TYPE_UINT8_ARRAY);
    if (njs_slow_path(array == NULL)) {
        return NULL;
    }

    array->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_BUFFER].object;

    return array;
}


njs_int_t
njs_buffer_new(njs_vm_t *vm, njs_value_t *value, const u_char *start,
    uint32_t size)
{
    njs_typed_array_t  *buffer;

    buffer = njs_buffer_alloc(vm, size, 0);
    if (njs_slow_path(buffer == NULL)) {
        return NJS_ERROR;
    }

    memcpy(njs_typed_array_buffer(buffer)->u.u8, start, size);

    njs_set_typed_array(value, buffer);

    return NJS_OK;
}


static njs_int_t
njs_buffer_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    njs_type_error(vm, "Buffer is not a constructor");

    return NJS_ERROR;
}


static njs_int_t
njs_buffer_alloc_safe(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t safe)
{
    double             size;
    njs_int_t          ret;
    njs_typed_array_t  *array;
    const njs_value_t  *fill;

    if (njs_slow_path(!njs_is_number(njs_arg(args, nargs, 1)))) {
        njs_type_error(vm, "\"size\" argument must be of type number");
        return NJS_ERROR;
    }

    size = njs_number(njs_argument(args, 1));
    if (njs_slow_path(size < 0 || size > INT32_MAX)) {
        njs_range_error(vm, "invalid size");
        return NJS_ERROR;
    }

    array = njs_buffer_alloc(vm, size, safe || nargs <= 2);
    if (njs_slow_path(array == NULL)) {
        return NJS_ERROR;
    }

    fill = njs_arg(args, nargs, 2);

    if (safe && njs_is_defined(fill)) {
        ret = njs_buffer_fill(vm, array, fill, njs_arg(args, nargs, 3), 0,
                              array->byte_length);
        if (njs_slow_path(ret != NJS_OK)) {
            return NJS_ERROR;
        }
    }

    njs_set_typed_array(&vm->retval, array);

    return NJS_OK;
}


static njs_int_t
njs_buffer_from(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    njs_int_t                    ret;
    njs_value_t                  *value, retval;
    const njs_buffer_encoding_t  *encoding;

    value = njs_arg(args, nargs, 1);

next:

    switch (value->type) {
    case NJS_TYPED_ARRAY:
        return njs_buffer_from_typed_array(vm, value);

    case NJS_ARRAY_BUFFER:
        return njs_buffer_from_array_buffer(vm, value, njs_arg(args, nargs, 2),
                                            njs_arg(args, nargs, 3));

    case NJS_STRING:
        encoding = njs_buffer_encoding(vm, njs_arg(args, nargs, 2));
        if (njs_slow_path(encoding == NULL)) {
            return NJS_ERROR;
        }

        return njs_buffer_from_string(vm, value, encoding);

    default:
        if (njs_is_object(value)) {
            ret = njs_value_of(vm, value, &retval);
            if (njs_slow_path(ret == NJS_ERROR)) {
                return ret;
            }

            if (ret == NJS_OK && !njs_is_null(&retval)
                && !(njs_is_object(&retval)
                     && njs_object(&retval) == njs_object(value)))
            {
                *value = retval;
                goto next;
            }

            ret = njs_buffer_from_object(vm, value);
            if (njs_slow_path(ret != NJS_DECLINED)) {
                return ret;
            }
        }

        njs_type_error(vm, "first argument %s is not a string "
                       "or Buffer-like object", njs_type_string(value->type));
    }

    return NJS_ERROR;
}


static njs_int_t
njs_buffer_from_object(njs_vm_t *vm, njs_value_t *value)
{
    double             num;
    int64_t            len;
    uint8_t            *p;
    uint32_t           i;
    njs_str_t          str;
    njs_int_t          ret;
    njs_array_t        *array;
    njs_value_t        retval, length;
    njs_typed_array_t  *buffer;

    static const njs_value_t  string_length = njs_string("length");
    static const njs_str_t  str_buffer = njs_str("Buffer");

next:

    ret = njs_value_property(vm, value, njs_value_arg(&string_length),
                             &length);
    if (njs_slow_path(ret == NJS_ERROR)) {
        return ret;
    }

    if (ret == NJS_DECLINED) {
        ret = njs_value_property(vm, value, njs_value_arg(&njs_string_type),
                                 &retval);
        if (njs_slow_path(ret != NJS_OK)) {
            return NJS_DECLINED;
        }

        ret = njs_value_to_string(vm, &retval, &retval);
        if (njs_slow_path(ret != NJS_OK)) {
            return NJS_DECLINED;
        }

        njs_string_get(&retval, &str);

        if (!njs_strstr_eq(&str, &str_buffer)) {
            return NJS_DECLINED;
        }

        ret = njs_value_property(vm, value, njs_value_arg(&njs_string_data),
                                 &retval);
        if (njs_slow_path(ret != NJS_OK)) {
            return NJS_DECLINED;
        }

        if (njs_is_object(&retval)) {
            value = &retval;
            goto next;
        }

        return NJS_DECLINED;
    }

    ret = njs_value_to_length(vm, &length, &len);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    buffer = njs_buffer_alloc(vm, len, 0);
    if (njs_slow_path(buffer == NULL)) {
        return NJS_ERROR;
    }

    p = njs_typed_array_buffer(buffer)->u.u8;

    if (njs_is_fast_array(value)) {
        array = njs_array(value);

        for (i = 0; i < array->length; i++) {
            ret = njs_value_to_number(vm, &array->start[i], &num);
            if (njs_slow_path(ret != NJS_OK)) {
                return ret;
            }

            *p++ = njs_number_to_int32(num);
        }

        njs_set_typed_array(&vm->retval, buffer);

        return NJS_OK;
    }

    for (i = 0; i < len; i++) {
        ret = njs_value_property_i64(vm, value, i, &retval);
        if (njs_slow_path(ret == NJS_ERROR)) {
            return ret;
        }

        ret = njs_value_to_number(vm, &retval, &num);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        *p++ = njs_number_to_int32(num);
    }

    njs_set_typed_array(&vm->retval, buffer);

    return NJS_OK;
}


static njs_int_t
njs_buffer_from_array_buffer(njs_vm_t *vm, njs_value_t *value,
    njs_value_t *offset, njs_value_t *length)
{
    int64_t             off, len;
    njs_int_t           ret;
    njs_value_t         arg;
    njs_typed_array_t   *buffer;
    njs_array_buffer_t  *array;

    array = njs_array_buffer(value);

    ret = njs_value_to_index(vm, offset, (uint64_t *) &off);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    if ((size_t) off > array->size) {
        njs_range_error(vm, "\"offset\" is outside of buffer bounds");
        return NJS_ERROR;
    }

    if (njs_is_defined(length)) {
        ret = njs_value_to_length(vm, length, &len);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

    } else {
        len = array->size - off;
    }

    if ((size_t) (off + len) > array->size) {
        njs_range_error(vm, "\"length\" is outside of buffer bounds");
        return NJS_ERROR;
    }

    njs_set_array_buffer(&arg, array);

    buffer = njs_typed_array_alloc(vm, &arg, 1, 0, NJS_OBJ_TYPE_UINT8_ARRAY);
    if (njs_slow_path(buffer == NULL)) {
        return NJS_ERROR;
    }

    buffer->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_BUFFER].object;

    buffer->offset = off;
    buffer->byte_length = len;

    njs_set_typed_array(&vm->retval, buffer);

    return NJS_OK;
}


static njs_int_t
njs_buffer_from_typed_array(njs_vm_t *vm, njs_value_t *value)
{
    uint8_t            *p;
    uint32_t           i, length;
    njs_typed_array_t  *buffer, *array;

    array = njs_typed_array(value);
    if (njs_slow_path(njs_is_detached_buffer(array->buffer))) {
        njs_type_error(vm, "detached buffer");
        return NJS_ERROR;
    }

    length = njs_typed_array_length(array);

    buffer = njs_buffer_alloc(vm, length, 0);
    if (njs_slow_path(buffer == NULL)) {
        return NJS_ERROR;
    }

    p = njs_typed_array_buffer(buffer)->u.u8;

    for (i = 0; i < length; i++) {
        *p++ = njs_number_to_int32(njs_typed_array_prop(array, i));
    }

    njs_set_typed_array(&vm->retval, buffer);

    return NJS_OK;
}


static njs_int_t
njs_buffer_from_string(njs_vm_t *vm, njs_value_t *value,
    const njs_buffer_encoding_t *encoding)
{
    njs_int_t          ret;
    njs_str_t          str;
    njs_value_t        dst;
    njs_typed_array_t  *buffer;

    ret = njs_buffer_decode_string(vm, value, &dst, encoding);
    if (njs_slow_path(ret != NJS_OK)) {
        return NJS_ERROR;
    }

    njs_string_get(&dst, &str);

    buffer = njs_buffer_alloc(vm, str.length, 0);
    if (njs_slow_path(buffer == NULL)) {
        return NJS_ERROR;
    }

    memcpy(njs_typed_array_buffer(buffer)->u.u8, str.start, str.length);

    njs_buffer_decode_destroy(vm, value, &dst);

    njs_set_typed_array(&vm->retval, buffer);

    return NJS_OK;
}


static size_t
njs_buffer_decode_string_length(njs_value_t *value,
    const njs_buffer_encoding_t *encoding)
{
    size_t             size;
    njs_str_t          str;
    njs_string_prop_t  string;

    (void) njs_string_prop(&string, value);

    str.start = string.start;
    str.length = string.size;
    size = string.size;

    if (encoding->decode == njs_string_decode_utf8 && string.length != 0) {
        return size;
    }

    encoding->decode_length(&str, &size);

    return size;
}


static njs_int_t
njs_buffer_byte_length(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    size_t                       size;
    njs_value_t                  *value;
    const njs_buffer_encoding_t  *encoding;

    value = njs_arg(args, nargs, 1);

    switch (value->type) {
    case NJS_TYPED_ARRAY:
        njs_set_number(&vm->retval, njs_typed_array(value)->byte_length);
        return NJS_OK;

    case NJS_ARRAY_BUFFER:
        njs_set_number(&vm->retval, njs_array_buffer(value)->size);
        return NJS_OK;

    case NJS_DATA_VIEW:
        njs_set_number(&vm->retval, njs_data_view(value)->byte_length);
        return NJS_OK;

    case NJS_STRING:
        encoding = njs_buffer_encoding(vm, njs_arg(args, nargs, 2));
        if (njs_slow_path(encoding == NULL)) {
            return NJS_ERROR;
        }

        size = njs_buffer_decode_string_length(value, encoding);

        njs_set_number(&vm->retval, size);

        return NJS_OK;

    default:
        njs_type_error(vm, "first argument %s is not a string "
                       "or Buffer-like object", njs_type_string(value->type));
    }

    return NJS_ERROR;
}


static njs_typed_array_t *
njs_buffer_slot_internal(njs_vm_t *vm, njs_value_t *value)
{
    njs_typed_array_t  *array;

    if (njs_is_object(value)) {
        array = njs_object_proto_lookup(njs_object(value), NJS_TYPED_ARRAY,
                                        njs_typed_array_t);

        if (array != NULL && array->type == NJS_OBJ_TYPE_UINT8_ARRAY) {
            return array;
        }
    }

    return NULL;
}


static njs_typed_array_t *
njs_buffer_slot(njs_vm_t *vm, njs_value_t *value, const char *name)
{
    njs_typed_array_t  *array;

    array = njs_buffer_slot_internal(vm, value);
    if (njs_slow_path(array == NULL)) {
        njs_type_error(vm, "\"%s\" argument must be an instance "
                           "of Buffer or Uint8Array", name);
        return NULL;
    }

    return array;
}


static njs_int_t
njs_buffer_array_range(njs_vm_t *vm, njs_typed_array_t *array,
    const njs_value_t *start, const njs_value_t *end, const char *name,
    uint8_t **out_start, uint8_t **out_end)
{
    uint64_t            num_start, num_end;
    njs_int_t           ret;
    njs_array_buffer_t  *buffer;

    num_start = 0;

    if (njs_is_defined(start)) {
        ret = njs_value_to_index(vm, njs_value_arg(start), &num_start);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    if (num_start > array->byte_length) {
        njs_range_error(vm, "\"%sStart\" is out of range: %L", name, num_start);
        return NJS_ERROR;
    }

    num_end = array->byte_length;

    if (njs_is_defined(end)) {
        ret = njs_value_to_index(vm, njs_value_arg(end), &num_end);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    if (num_end > array->byte_length) {
        njs_range_error(vm, "\"%sEnd\" is out of range: %L", name, num_end);
        return NJS_ERROR;
    }

    if (num_start > num_end) {
        num_end = num_start;
    }

    buffer = njs_typed_array_buffer(array);
    if (njs_slow_path(njs_is_detached_buffer(buffer))) {
        njs_type_error(vm, "detached buffer");
        return NJS_ERROR;
    }

    *out_start = &buffer->u.u8[array->offset + num_start];
    *out_end = &buffer->u.u8[array->offset + num_end];

    return NJS_OK;
}


static njs_int_t
njs_buffer_compare_array(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2,
    const njs_value_t *target_start, const njs_value_t *target_end,
    const njs_value_t *source_start, const njs_value_t *source_end)
{
    size_t             size, src_size, trg_size;
    uint8_t            *src, *src_end, *trg, *trg_end;
    njs_int_t          ret;
    njs_typed_array_t  *source, *target;

    source = njs_buffer_slot(vm, val1, "source");
    if (njs_slow_path(source == NULL)) {
        return NJS_ERROR;
    }

    target = njs_buffer_slot(vm, val2, "target");
    if (njs_slow_path(target == NULL)) {
        return NJS_ERROR;
    }

    ret = njs_buffer_array_range(vm, target, target_start, target_end, "target",
                                 &trg, &trg_end);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ret = njs_buffer_array_range(vm, source, source_start, source_end, "source",
                                 &src, &src_end);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    trg_size = trg_end - trg;
    src_size = src_end - src;

    size = njs_min(trg_size, src_size);

    ret = memcmp(trg, src, size);

    if (ret != 0) {
        njs_set_number(&vm->retval, (ret < 0) ? 1 : -1);
        return NJS_OK;
    }

    if (trg_size > src_size) {
        ret = -1;

    } else if (trg_size < src_size) {
        ret = 1;
    }

    njs_set_number(&vm->retval, ret);

    return NJS_OK;
}


static njs_int_t
njs_buffer_compare(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    return njs_buffer_compare_array(vm, njs_arg(args, nargs, 1),
                                    njs_arg(args, nargs, 2),
                                    &njs_value_undefined, &njs_value_undefined,
                                    &njs_value_undefined, &njs_value_undefined);
}


static njs_int_t
njs_buffer_concat(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    u_char             *p;
    size_t             n;
    int64_t            i, len, list_len;
    njs_int_t          ret;
    njs_value_t        *list, *value, *length, retval;
    njs_array_t        *array;
    njs_typed_array_t  *buffer, *arr;

    list = njs_arg(args, nargs, 1);

    if (njs_slow_path(!njs_is_array(list))) {
        njs_type_error(vm, "\"list\" argument must be an instance of Array");
        return NJS_ERROR;
    }

    len = 0;
    ret = njs_object_length(vm, list, &list_len);
    if (njs_slow_path(ret == NJS_ERROR)) {
        return ret;
    }

    if (njs_is_fast_array(list)) {
        array = njs_array(list);
        for (i = 0; i < list_len; i++) {
            value = &array->start[i];

            if (njs_slow_path(!njs_is_typed_array_uint8(value))) {
                njs_type_error(vm, "\"list[%L]\" argument must be an "
                                   "instance of Buffer or Uint8Array", i);
                return NJS_ERROR;
            }

            arr = njs_typed_array(value);
            if (njs_slow_path(njs_is_detached_buffer(arr->buffer))) {
                njs_type_error(vm, "detached buffer");
                return NJS_ERROR;
            }

            if (njs_slow_path((SIZE_MAX - len) < arr->byte_length)) {
                njs_type_error(vm, "Invalid length");
                return NJS_ERROR;
            }

            len += arr->byte_length;
        }

    } else {

        for (i = 0; i < list_len; i++) {
            ret = njs_value_property_i64(vm, list, i, &retval);
            if (njs_slow_path(ret == NJS_ERROR)) {
                return ret;
            }

            if (njs_slow_path(!njs_is_typed_array(&retval))) {
                njs_type_error(vm, "\"list[%L]\" argument must be an "
                                   "instance of Buffer or Uint8Array", i);
                return NJS_ERROR;
            }

            arr = njs_typed_array(&retval);
            if (njs_slow_path(njs_is_detached_buffer(arr->buffer))) {
                njs_type_error(vm, "detached buffer");
                return NJS_ERROR;
            }

            if (njs_slow_path((SIZE_MAX - len) < arr->byte_length)) {
                njs_type_error(vm, "Invalid length");
                return NJS_ERROR;
            }

            len += arr->byte_length;
        }
    }

    length = njs_arg(args, nargs, 2);
    if (njs_is_defined(length)) {
        if (njs_slow_path(!njs_is_number(length))) {
            njs_type_error(vm, "\"length\" argument must be of type number");
            return NJS_ERROR;
        }

        len = njs_number(length);
        if (njs_slow_path(len < 0)) {
            njs_range_error(vm, "\"length\" is out of range");
            return NJS_ERROR;
        }
    }

    buffer = njs_buffer_alloc(vm, len, 0);
    if (njs_slow_path(buffer == NULL)) {
        return NJS_ERROR;
    }

    p = njs_typed_array_buffer(buffer)->u.u8;

    if (njs_is_fast_array(list)) {
        array = njs_array(list);

        for (i = 0; len != 0 && i < list_len; i++) {
            arr = njs_typed_array(&array->start[i]);
            n = njs_min((size_t) len, arr->byte_length);

            p = njs_cpymem(p, njs_typed_array_buffer(arr)->u.u8, n);

            len -= n;
        }

    } else {
        for (i = 0; len != 0 && i < list_len; i++) {
            ret = njs_value_property_i64(vm, list, i, &retval);
            if (njs_slow_path(ret == NJS_ERROR)) {
                return ret;
            }

            arr = njs_typed_array(&retval);
            n = njs_min((size_t) len, arr->byte_length);

            p = njs_cpymem(p, njs_typed_array_buffer(arr)->u.u8, n);

            len -= n;
        }
    }

    if (len != 0) {
        njs_memzero(p, len);
    }

    njs_set_typed_array(&vm->retval, buffer);

    return NJS_OK;
}


static njs_int_t
njs_buffer_is_buffer(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    njs_bool_t         is;
    njs_typed_array_t  *array;

    is = 0;

    array = njs_buffer_slot(vm, njs_arg(args, nargs, 1), "source");

    if (njs_fast_path(array != NULL && array->object.__proto__
                      == &vm->prototypes[NJS_OBJ_TYPE_BUFFER].object))
    {
        is = 1;
    }

    njs_set_boolean(&vm->retval, is);

    return NJS_OK;
}


static njs_int_t
njs_buffer_is_encoding(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    const njs_value_t  *value;

    value = njs_arg(args, nargs, 1);
    njs_set_boolean(&vm->retval, njs_is_defined(value)
                                 && njs_buffer_encoding(vm, value) != NULL);

    return NJS_OK;
}


static njs_int_t
njs_buffer_prototype_length(njs_vm_t *vm, njs_object_prop_t *prop,
    njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
{
    njs_typed_array_t  *array;

    array = njs_buffer_slot_internal(vm, value);
    if (njs_slow_path(array == NULL)) {
        njs_set_undefined(retval);
        return NJS_DECLINED;
    }

    njs_set_number(retval, array->byte_length);

    return NJS_OK;
}


static njs_int_t
njs_buffer_prototype_read_int(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t magic)
{
    double              v;
    uint32_t            u32;
    uint64_t            u64, index, size;
    njs_int_t           ret;
    njs_bool_t          little, swap, sign;
    njs_value_t         *this, *value;
    const uint8_t       *u8;
    njs_typed_array_t   *array;
    njs_array_buffer_t  *buffer;

    this = njs_argument(args, 0);
    array = njs_buffer_slot(vm, this, "this");
    if (njs_slow_path(array == NULL)) {
        return NJS_ERROR;
    }

    ret = njs_value_to_index(vm, njs_arg(args, nargs, 1), &index);
    if (njs_slow_path(ret != NJS_OK)) {
        return NJS_ERROR;
    }

    size = magic >> 2;

    if (!size) {
        value = njs_arg(args, nargs, 2);
        if (njs_slow_path(!njs_is_number(value))) {
            njs_type_error(vm, "\"byteLength\" is not a number");
            return NJS_ERROR;
        }

        size = (size_t) njs_number(value);
        if (njs_slow_path(size > 6)) {
            njs_type_error(vm, "\"byteLength\" must be <= 6");
            return NJS_ERROR;
        }
    }

    if (njs_slow_path(size + index > array->byte_length)) {
        njs_range_error(vm, "index %uL is outside the bound of the buffer",
                        index);
        return NJS_ERROR;
    }

    sign = (magic >> 1) & 1;
    little = magic & 1;
    swap = little;

#if NJS_HAVE_LITTLE_ENDIAN
    swap = !swap;
#endif

    buffer = array->buffer;
    if (njs_slow_path(njs_is_detached_buffer(buffer))) {
        njs_type_error(vm, "detached buffer");
        return NJS_ERROR;
    }

    u8 = &buffer->u.u8[index + array->offset];

    switch (size) {
    case 1:
        if (sign) {
            v = (int8_t) *u8;

        } else {
            v = *u8;
        }

        break;

    case 2:
        u32 = njs_get_u16(u8);

        if (swap) {
            u32 = njs_bswap_u16(u32);
        }

        if (sign) {
            /* Sign extension. */

            u32 |= (u32 & (INT16_MAX + 1ULL)) * UINT32_MAX;
            v = (int16_t) u32;

        } else {
            v = u32;
        }

        break;

    case 3:
        if (little) {
            u32 = (u8[2] << 16) | (u8[1] << 8) | u8[0];

        } else {
            u32 = (u8[0] << 16) | (u8[1] << 8) | u8[2];
        }

        if (sign) {
            /* Sign extension. */

            u32 |= (u32 & (INT24_MAX + 1ULL)) * UINT32_MAX;
            v = (int32_t) u32;

        } else {
            v = u32;
        }

        break;

    case 4:
        u32 = njs_get_u32(u8);

        if (swap) {
            u32 = njs_bswap_u32(u32);
        }

        if (sign) {
            /* Sign extension. */

            u32 |= (u32 & (INT32_MAX + 1ULL)) * UINT32_MAX;
            v = (int32_t) u32;

        } else {
            v = u32;
        }

        break;

    case 5:
        if (little) {
            u64 = ((uint64_t) u8[4] << 32)
                  | ((uint64_t) u8[3] << 24)
                  | (u8[2] << 16)
                  | (u8[1] << 8)
                  | u8[0];

        } else {
            u64 = ((uint64_t) u8[0] << 32)
                  | ((uint64_t) u8[1] << 24)
                  | (u8[2] << 16)
                  | (u8[3] << 8)
                  | u8[4];
        }

        if (sign) {
            /* Sign extension. */

            u64 |= (u64 & (INT40_MAX + 1ULL)) * UINT64_MAX;
            v = (int64_t) u64;

        } else {
            v = u64;
        }

        break;

    case 6:
    default:
        if (little) {
            u64 = ((uint64_t) u8[5] << 40)
                  | ((uint64_t) u8[4] << 32)
                  |((uint64_t) u8[3] << 24)
                  | (u8[2] << 16)
                  | (u8[1] << 8)
                  | u8[0];

        } else {
            u64 = ((uint64_t) u8[0] << 40)
                  | ((uint64_t) u8[1] << 32)
                  | ((uint64_t) u8[2] << 24)
                  | (u8[3] << 16)
                  | (u8[4] << 8)
                  | u8[5];
        }

        if (sign) {
            /* Sign extension. */

            u64 |= (u64 & (INT48_MAX + 1ULL)) * UINT64_MAX;
            v = (int64_t) u64;

        } else {
            v = u64;
        }

        break;
    }

    njs_set_number(&vm->retval, v);

    return NJS_OK;
}


static njs_int_t
njs_buffer_prototype_read_float(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t magic)
{
    double              v;
    uint32_t            u32;
    uint64_t            index, size;
    njs_int_t           ret;
    njs_bool_t          little, swap;
    njs_value_t         *this;
    const uint8_t       *u8;
    njs_conv_f32_t      conv_f32;
    njs_conv_f64_t      conv_f64;
    njs_typed_array_t   *array;
    njs_array_buffer_t  *buffer;

    this = njs_argument(args, 0);
    array = njs_buffer_slot(vm, this, "this");
    if (njs_slow_path(array == NULL)) {
        return NJS_ERROR;
    }

    ret = njs_value_to_index(vm, njs_arg(args, nargs, 1), &index);
    if (njs_slow_path(ret != NJS_OK)) {
        return NJS_ERROR;
    }

    size = magic >> 2;

    if (njs_slow_path(size + index > array->byte_length)) {
        njs_range_error(vm, "index %uL is outside the bound of the buffer",
                        index);
        return NJS_ERROR;
    }

    little = magic & 1;
    swap = little;

#if NJS_HAVE_LITTLE_ENDIAN
    swap = !swap;
#endif

    buffer = array->buffer;
    if (njs_slow_path(njs_is_detached_buffer(buffer))) {
        njs_type_error(vm, "detached buffer");
        return NJS_ERROR;
    }

    u8 = &buffer->u.u8[index + array->offset];

    switch (size) {
    case 4:
        u32 = *((uint32_t *) u8);

        if (swap) {
            u32 = njs_bswap_u32(u32);
        }

        conv_f32.u = u32;
        v = conv_f32.f;
        break;

    case 8:
    default:
        conv_f64.u = *((uint64_t *) u8);

        if (swap) {
            conv_f64.u = njs_bswap_u64(conv_f64.u);
        }

        v = conv_f64.f;
    }

    njs_set_number(&vm->retval, v);

    return NJS_OK;
}


static njs_int_t
njs_buffer_prototype_write_int(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t magic)
{
    uint8_t             *u8;
    int64_t             i64;
    uint32_t            u32;
    uint64_t            index, size;
    njs_int_t           ret;
    njs_bool_t          little, swap;
    njs_value_t         *this, *value;
    njs_typed_array_t   *array;
    njs_array_buffer_t  *buffer;

    this = njs_argument(args, 0);
    array = njs_buffer_slot(vm, this, "this");
    if (njs_slow_path(array == NULL)) {
        return NJS_ERROR;
    }

    ret = njs_value_to_integer(vm, njs_arg(args, nargs, 1), &i64);
    if (njs_slow_path(ret != NJS_OK)) {
        return NJS_ERROR;
    }

    index = 0;

    if (nargs > 2) {
        ret = njs_value_to_index(vm, njs_argument(args, 2), &index);
        if (njs_slow_path(ret != NJS_OK)) {
            return NJS_ERROR;
        }
    }

    size = magic >> 2;

    if (!size) {
        value = njs_arg(args, nargs, 3);
        if (njs_slow_path(!njs_is_number(value))) {
            njs_type_error(vm, "\"byteLength\" is not a number");
            return NJS_ERROR;
        }

        size = (size_t) njs_number(value);
        if (njs_slow_path(size > 6)) {
            njs_type_error(vm, "\"byteLength\" must be <= 6");
            return NJS_ERROR;
        }
    }

    if (njs_slow_path(size + index > array->byte_length)) {
        njs_range_error(vm, "index %uL is outside the bound of the buffer",
                        index);
        return NJS_ERROR;
    }

    little = magic & 1;
    swap = little;

#if NJS_HAVE_LITTLE_ENDIAN
    swap = !swap;
#endif

    buffer = njs_typed_array_writable(vm, array);
    if (njs_slow_path(buffer == NULL)) {
        return NJS_ERROR;
    }

    u8 = &buffer->u.u8[index + array->offset];

    switch (size) {
    case 1:
        *u8 = i64;
        break;

    case 2:
        u32 = (uint16_t) i64;

        if (swap) {
            u32 = njs_bswap_u16(u32);
        }

        njs_set_u16(u8, u32);
        break;

    case 3:
        if (little) {
            *u8++ = i64; i64 >>= 8;
            *u8++ = i64; i64 >>= 8;
            *u8++ = i64;

        } else {
            u8 += 2;

            *u8-- = i64; i64 >>= 8;
            *u8-- = i64; i64 >>= 8;
            *u8 = i64;
        }

        break;

    case 4:
        u32 = i64;

        if (swap) {
            u32 = njs_bswap_u32(u32);
        }

        njs_set_u32(u8, u32);
        break;

    case 5:
        if (little) {
            *u8++ = i64; i64 >>= 8;
            *u8++ = i64; i64 >>= 8;
            *u8++ = i64; i64 >>= 8;
            *u8++ = i64; i64 >>= 8;
            *u8++ = i64;

        } else {
            u8 += 4;

            *u8-- = i64; i64 >>= 8;
            *u8-- = i64; i64 >>= 8;
            *u8-- = i64; i64 >>= 8;
            *u8-- = i64; i64 >>= 8;
            *u8 = i64;
        }

        break;

    case 6:
    default:
        if (little) {
            *u8++ = i64; i64 >>= 8;
            *u8++ = i64; i64 >>= 8;
            *u8++ = i64; i64 >>= 8;
            *u8++ = i64; i64 >>= 8;
            *u8++ = i64; i64 >>= 8;
            *u8++ = i64;

        } else {
            u8 += 5;

            *u8-- = i64; i64 >>= 8;
            *u8-- = i64; i64 >>= 8;
            *u8-- = i64; i64 >>= 8;
            *u8-- = i64; i64 >>= 8;
            *u8-- = i64; i64 >>= 8;
            *u8 = i64;
        }

        break;
    }

    njs_set_number(&vm->retval, index + size);

    return NJS_OK;
}


static njs_int_t
njs_buffer_prototype_write_float(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t magic)
{
    double              v;
    uint8_t             *u8;
    uint64_t            index, size;
    njs_int_t           ret;
    njs_bool_t          little, swap;
    njs_value_t         *this;
    njs_conv_f32_t      conv_f32;
    njs_conv_f64_t      conv_f64;
    njs_typed_array_t   *array;
    njs_array_buffer_t  *buffer;

    this = njs_argument(args, 0);
    array = njs_buffer_slot(vm, this, "this");
    if (njs_slow_path(array == NULL)) {
        return NJS_ERROR;
    }

    ret = njs_value_to_number(vm, njs_arg(args, nargs, 1), &v);
    if (njs_slow_path(ret != NJS_OK)) {
        return NJS_ERROR;
    }

    index = 0;

    if (nargs > 2) {
        ret = njs_value_to_index(vm, njs_argument(args, 2), &index);
        if (njs_slow_path(ret != NJS_OK)) {
            return NJS_ERROR;
        }
    }

    size = magic >> 2;

    if (njs_slow_path(size + index > array->byte_length)) {
        njs_range_error(vm, "index %uL is outside the bound of the buffer",
                        index);
        return NJS_ERROR;
    }

    little = magic & 1;
    swap = little;

#if NJS_HAVE_LITTLE_ENDIAN
    swap = !swap;
#endif

    buffer = njs_typed_array_writable(vm, array);
    if (njs_slow_path(buffer == NULL)) {
        return NJS_ERROR;
    }

    u8 = &buffer->u.u8[index + array->offset];

    switch (size) {
    case 4:
        conv_f32.f = (float) v;

        if (swap) {
            conv_f32.u = njs_bswap_u32(conv_f32.u);
        }

        *((uint32_t *) u8) = conv_f32.u;
        break;

    case 8:
    default:
        conv_f64.f = v;

        if (swap) {
            conv_f64.u = njs_bswap_u64(conv_f64.u);
        }

        *((uint64_t *) u8) = conv_f64.u;
    }

    njs_set_undefined(&vm->retval);

    return NJS_OK;
}


static njs_int_t
njs_buffer_prototype_write(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    uint64_t                     offset, length;
    njs_int_t                    ret;
    njs_value_t                  *this, *value, *value_offset, *value_length,
                                 *enc;
    njs_typed_array_t            *array;
    njs_array_buffer_t           *buffer;
    const njs_buffer_encoding_t  *encoding;

    this = njs_argument(args, 0);
    array = njs_buffer_slot(vm, this, "this");
    if (njs_slow_path(array == NULL)) {
        return NJS_ERROR;
    }

    value = njs_arg(args, nargs, 1);
    value_offset = njs_arg(args, nargs, 2);
    value_length = njs_arg(args, nargs, 3);
    enc = njs_arg(args, nargs, 4);

    offset = 0;
    length = array->byte_length;

    if (njs_slow_path(!njs_is_string(value))) {
        njs_type_error(vm, "first argument must be a string");
        return NJS_ERROR;
    }

    if (njs_is_defined(value_offset)) {
        if (njs_is_string(value_offset)) {
            enc = value_offset;
            goto encoding;
        }

        ret = njs_value_to_index(vm, value_offset, &offset);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    if (njs_is_defined(value_length)) {
        if (njs_is_string(value_length)) {
            enc = value_length;
            goto encoding;
        }

        ret = njs_value_to_index(vm, value_length, &length);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

encoding:

    encoding = njs_buffer_encoding(vm, enc);
    if (njs_slow_path(encoding == NULL)) {
        return NJS_ERROR;
    }

    buffer = njs_typed_array_writable(vm, array);
    if (njs_slow_path(buffer == NULL)) {
        return NJS_ERROR;
    }

    if (offset >= array->byte_length) {
        njs_range_error(vm, "\"offset\" is out of range");
        return NJS_ERROR;
    }

    return njs_buffer_write_string(vm, value, array, encoding, offset, length);
}


static njs_int_t
njs_buffer_write_string(njs_vm_t *vm, njs_value_t *value,
    njs_typed_array_t *array, const njs_buffer_encoding_t *encoding,
    uint64_t offset, uint64_t length)
{
    uint8_t             *start;
    njs_int_t           ret;
    njs_str_t           str;
    njs_value_t         dst;
    njs_array_buffer_t  *buffer;

    buffer = njs_typed_array_buffer(array);

    ret = njs_buffer_decode_string(vm, value, &dst, encoding);
    if (njs_slow_path(ret != NJS_OK)) {
        return NJS_ERROR;
    }

    njs_string_get(&dst, &str);

    start = &buffer->u.u8[array->offset + offset];

    if (length > array->byte_length - offset) {
        length = array->byte_length - offset;
    }

    if (str.length == 0) {
        length = 0;
        goto done;
    }

    memcpy(start, str.start, length);

done:

    njs_buffer_decode_destroy(vm, value, &dst);

    njs_set_number(&vm->retval, length);

    return NJS_OK;
}


static njs_int_t
njs_buffer_prototype_fill(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    uint64_t           offset, end;
    njs_int_t          ret;
    njs_value_t        *this, *value, *value_offset, *value_end,
                       *encode;
    njs_typed_array_t  *array;

    this = njs_argument(args, 0);
    if (njs_slow_path(nargs < 2)) {
        goto done;
    }

    array = njs_buffer_slot(vm, this, "this");
    if (njs_slow_path(array == NULL)) {
        return NJS_ERROR;
    }

    value = njs_arg(args, nargs, 1);
    value_offset = njs_arg(args, nargs, 2);
    value_end = njs_arg(args, nargs, 3);
    encode = njs_arg(args, nargs, 4);

    offset = 0;
    end = array->byte_length;

    if (njs_is_defined(value_offset)) {
        if (njs_is_string(value) && njs_is_string(value_offset)) {
            encode = value_offset;
            goto fill;
        }

        ret = njs_value_to_index(vm, value_offset, &offset);
        if (njs_slow_path(ret != NJS_OK)) {
            return NJS_ERROR;
        }
    }

    if (njs_is_defined(value_end)) {
        if (njs_is_string(value) && njs_is_string(value_end)) {
            encode = value_end;
            goto fill;
        }

        ret = njs_value_to_index(vm, value_end, &end);
        if (njs_slow_path(ret != NJS_OK)) {
            return NJS_ERROR;
        }
    }

fill:

    ret = njs_buffer_fill(vm, array, value, encode, offset, end);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

done:

    njs_vm_retval_set(vm, this);

    return NJS_OK;
}


static njs_int_t
njs_buffer_fill(njs_vm_t *vm, njs_typed_array_t *array, const njs_value_t *fill,
    const njs_value_t *encode, uint64_t offset, uint64_t end)
{
    double                       num;
    uint8_t                      *start, *stop;
    njs_int_t                    ret;
    njs_array_buffer_t           *buffer;
    const njs_buffer_encoding_t  *encoding;

    buffer = njs_typed_array_writable(vm, array);
    if (njs_slow_path(buffer == NULL)) {
        return NJS_ERROR;
    }

    if (njs_slow_path(offset > array->byte_length)) {
        njs_range_error(vm, "\"offset\" is out of range");
        return NJS_ERROR;
    }

    if (njs_slow_path(end > array->byte_length)) {
        njs_range_error(vm, "\"end\" is out of range");
        return NJS_ERROR;
    }

    if (njs_slow_path(offset >= end)) {
        return NJS_OK;
    }

    start = &buffer->u.u8[array->offset + offset];
    stop = &buffer->u.u8[array->offset + end];

    switch (fill->type) {
    case NJS_STRING:
        encoding = njs_buffer_encoding(vm, encode);
        if (njs_slow_path(encoding == NULL)) {
            return NJS_ERROR;
        }

        return njs_buffer_fill_string(vm, fill, array, encoding, start, stop);

    case NJS_TYPED_ARRAY:
        return njs_buffer_fill_typed_array(vm, fill, array, start, stop);

    default:
        ret = njs_value_to_number(vm, njs_value_arg(fill), &num);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        if (njs_slow_path(njs_is_detached_buffer(buffer))) {
            njs_type_error(vm, "detached buffer");
            return NJS_ERROR;
        }

        memset(start, njs_number_to_uint32(num) & 0xff, end - offset);
    }

    return NJS_OK;
}


static njs_int_t
njs_buffer_fill_string(njs_vm_t *vm, const njs_value_t *value,
    njs_typed_array_t *array, const njs_buffer_encoding_t *encoding,
    uint8_t *start, uint8_t *end)
{
    uint64_t     n;
    njs_int_t    ret;
    njs_str_t    str;
    njs_value_t  dst;

    ret = njs_buffer_decode_string(vm, value, &dst, encoding);
    if (njs_slow_path(ret != NJS_OK)) {
        return NJS_ERROR;
    }

    njs_string_get(&dst, &str);

    if (str.length == 0) {
        memset(start, 0, end - start);
        goto done;
    }

    while (start < end) {
        n = njs_min(str.length, (size_t) (end - start));
        start = njs_cpymem(start, str.start, n);
    }

done:

    njs_buffer_decode_destroy(vm, value, &dst);

    return NJS_OK;
}


static njs_int_t
njs_buffer_fill_typed_array(njs_vm_t *vm, const njs_value_t *value,
    njs_typed_array_t *array, uint8_t *to, uint8_t *end)
{
    size_t              byte_length;
    uint8_t             *from;
    uint64_t            n;
    njs_typed_array_t   *arr_from;
    njs_array_buffer_t  *buffer;

    buffer = njs_typed_array_buffer(array);

    arr_from = njs_typed_array(value);
    byte_length = arr_from->byte_length;
    from = &njs_typed_array_buffer(arr_from)->u.u8[arr_from->offset];

    if (njs_typed_array_buffer(arr_from)->u.u8 == buffer->u.u8) {
        while (to < end) {
            n = njs_min(byte_length, (size_t) (end - to));
            memmove(to, from, n);
            to += n;
        }

    } else {
        while (to < end) {
            n = njs_min(byte_length, (size_t) (end - to));
            to = njs_cpymem(to, from, n);
        }
    }

    return NJS_OK;
}


static njs_int_t
njs_buffer_prototype_to_string(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t unused)
{
    uint64_t                     start, end;
    njs_int_t                    ret;
    njs_str_t                    str;
    njs_value_t                  *this, *value_start, *value_end;
    njs_typed_array_t            *array;
    const njs_buffer_encoding_t  *encoding;

    this = njs_argument(args, 0);
    array = njs_buffer_slot(vm, this, "this");
    if (njs_slow_path(array == NULL)) {
        return NJS_ERROR;
    }

    value_start = njs_arg(args, nargs, 2);
    value_end = njs_arg(args, nargs, 3);

    start = 0;
    end = array->byte_length;

    encoding = njs_buffer_encoding(vm,  njs_arg(args, nargs, 1));
    if (njs_slow_path(encoding == NULL)) {
        return NJS_ERROR;
    }

    if (njs_is_defined(value_start)) {
        ret = njs_value_to_index(vm, value_start, &start);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        start = njs_min(start, array->byte_length);
    }

    if (njs_is_defined(value_end)) {
        ret = njs_value_to_index(vm, value_end, &end);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        end = njs_min(end, array->byte_length);
    }

    if (njs_slow_path(njs_is_detached_buffer(array->buffer))) {
        njs_type_error(vm, "detached buffer");
        return NJS_ERROR;
    }

    str.start = &njs_typed_array_buffer(array)->u.u8[array->offset + start];
    str.length = end - start;

    if (njs_slow_path(str.length == 0)) {
        njs_vm_retval_set(vm, &njs_string_empty);
        return NJS_OK;
    }

    return encoding->encode(vm, &vm->retval, &str);
}


static njs_int_t
njs_buffer_prototype_compare(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    return njs_buffer_compare_array(vm, njs_argument(args, 0),
                               njs_arg(args, nargs, 1), njs_arg(args, nargs, 2),
                               njs_arg(args, nargs, 3), njs_arg(args, nargs, 4),
                               njs_arg(args, nargs, 5));
}


static njs_int_t
njs_buffer_prototype_copy(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    size_t              size;
    uint8_t             *src, *src_end, *trg, *trg_end;
    njs_int_t           ret;
    njs_value_t         *val1, *val2;
    njs_typed_array_t   *source, *target;
    njs_array_buffer_t  *buffer, *array;

    val1 = njs_argument(args, 0);
    val2 = njs_arg(args, nargs, 1);

    source = njs_buffer_slot(vm, val1, "source");
    if (njs_slow_path(source == NULL)) {
        return NJS_ERROR;
    }

    target = njs_buffer_slot(vm, val2, "target");
    if (njs_slow_path(target == NULL)) {
        return NJS_ERROR;
    }

    ret = njs_buffer_array_range(vm, target, njs_arg(args, nargs, 2),
                                 &njs_value_undefined, "target", &trg,
                                 &trg_end);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ret = njs_buffer_array_range(vm, source, njs_arg(args, nargs, 3),
                                 njs_arg(args, nargs, 4), "source", &src,
                                 &src_end);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    buffer = njs_typed_array_writable(vm, target);
    if (njs_slow_path(buffer == NULL)) {
        return NJS_ERROR;
    }

    array = njs_typed_array_buffer(source);

    size = njs_min(trg_end - trg, src_end - src);

    if (buffer->u.data != array->u.data) {
        memcpy(trg, src, size);

    } else {
        memmove(trg, src, size);
    }

    njs_set_number(&vm->retval, size);

    return NJS_OK;
}


static njs_int_t
njs_buffer_prototype_equals(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    njs_int_t  ret;

    ret = njs_buffer_compare_array(vm, njs_argument(args, 0),
                                  njs_arg(args, nargs, 1), &njs_value_undefined,
                                  &njs_value_undefined, &njs_value_undefined,
                                  &njs_value_undefined);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    njs_set_boolean(&vm->retval, njs_number(&vm->retval) == 0);

    return NJS_OK;
}


static njs_int_t
njs_buffer_prototype_index_of(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t last)
{
    uint8_t                      byte;
    int64_t                      from, to, increment, length, offset, index, i;
    njs_int_t                    ret;
    njs_str_t                    str;
    njs_value_t                  *this, *value, *value_from, *enc, dst;
    const uint8_t                *u8;
    njs_typed_array_t            *array, *src;
    njs_array_buffer_t           *buffer;
    const njs_buffer_encoding_t  *encoding;

    this = njs_argument(args, 0);
    value = njs_arg(args, nargs, 1);
    value_from = njs_arg(args, nargs, 2);
    enc = njs_arg(args, nargs, 3);

    array = njs_buffer_slot(vm, this, "this");
    if (njs_slow_path(array == NULL)) {
        return NJS_ERROR;
    }

    index = -1;

    if (njs_slow_path(array->byte_length == 0)) {
        goto done;
    }

    length = array->byte_length;

    if (last) {
        from = length - 1;
        to = -1;
        increment = -1;

    } else {
        from = 0;
        to = length;
        increment = 1;
    }

    if (njs_is_defined(value_from)) {
        if (njs_is_string(value) && njs_is_string(value_from)) {
            enc = value_from;
            goto encoding;
        }

        ret = njs_value_to_integer(vm, value_from, &from);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        if (last) {
            if (from >= 0) {
                from = njs_min(from, length - 1);

            } else if (from < 0) {
                from += length;
            }

            if (from <= to) {
                goto done;
            }

        } else {
            if (from < 0) {
                from += length;

                if (from < 0) {
                    from = 0;
                }
            }

            if (from >= to) {
                goto done;
            }
        }
    }

encoding:

    encoding = njs_buffer_encoding(vm, enc);
    if (njs_slow_path(encoding == NULL)) {
        return NJS_ERROR;
    }

    buffer = njs_typed_array_buffer(array);
    if (njs_slow_path(njs_is_detached_buffer(buffer))) {
        njs_type_error(vm, "detached buffer");
        return NJS_ERROR;
    }

    u8 = &buffer->u.u8[0];
    offset = array->offset;

    switch (value->type) {
    case NJS_STRING:
    case NJS_TYPED_ARRAY:
        if (njs_is_string(value)) {
            ret = njs_buffer_decode_string(vm, value, &dst, encoding);
            if (njs_slow_path(ret != NJS_OK)) {
                return ret;
            }

            njs_string_get(&dst, &str);

        } else {
            src = njs_typed_array(value);
            if (njs_slow_path(src->type != NJS_OBJ_TYPE_UINT8_ARRAY)) {
                goto fail;
            }

            if (njs_slow_path(njs_is_detached_buffer(src->buffer))) {
                njs_type_error(vm, "detached buffer");
                return NJS_ERROR;
            }

            str.start = &src->buffer->u.u8[src->offset];
            str.length = src->byte_length;
        }

        if (njs_slow_path(str.length == 0)) {
            index = (last) ? length : 0;
            goto done;
        }

        if (last) {
            if (from - to < (int64_t) str.length) {
                goto done;
            }

            from -= str.length - 1;

        } else {
            if (to - from < (int64_t) str.length) {
                goto done;
            }

            to -= str.length - 1;
        }

        for (i = from; i != to; i += increment) {
            if (memcmp(&u8[offset + i], str.start, str.length) == 0) {
                index = i;
                goto done;
            }
        }

        break;

    case NJS_NUMBER:
        byte = njs_number_to_uint32(njs_number(value));

        for (i = from; i != to; i += increment) {
            if (u8[offset + i] == byte) {
                index = i;
                goto done;
            }
        }

        break;

    default:
fail:
        njs_type_error(vm, "\"value\" argument %s is not a string "
                       "or Buffer-like object", njs_type_string(value->type));
        return NJS_ERROR;
    }

done:

    njs_set_number(&vm->retval, index);

    return NJS_OK;
}


static njs_int_t
njs_buffer_prototype_includes(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    njs_int_t  ret;

    ret = njs_buffer_prototype_index_of(vm, args, nargs, unused);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    njs_set_boolean(&vm->retval, (njs_number(&vm->retval) != -1));

    return NJS_OK;
}


static njs_int_t
njs_buffer_prototype_slice(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    njs_int_t          ret;
    njs_typed_array_t  *array;

    ret = njs_typed_array_prototype_slice(vm, args, nargs, unused);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    array = njs_typed_array(&vm->retval);
    array->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_BUFFER].object;

    return NJS_OK;
}


static njs_int_t
njs_buffer_prototype_swap(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t size)
{
    uint8_t             *p, *end;
    njs_typed_array_t   *array;
    njs_array_buffer_t  *buffer;

    array = njs_buffer_slot(vm, njs_argument(args, 0), "this");
    if (njs_slow_path(array == NULL)) {
        return NJS_ERROR;
    }

    if ((array->byte_length % size) != 0) {
        njs_range_error(vm, "Buffer size must be a multiple of %d-bits",
                        (int) (size << 3));
        return NJS_ERROR;
    }

    buffer = njs_typed_array_writable(vm, array);
    if (njs_slow_path(buffer == NULL)) {
        return NJS_ERROR;
    }

    p = &buffer->u.u8[array->offset];
    end = p + array->byte_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)));
        }
    }

    njs_set_typed_array(&vm->retval, array);

    return NJS_OK;
}


static njs_int_t
njs_buffer_prototype_to_json(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    u_char              *p, *end;
    njs_int_t           ret;
    njs_value_t         *value;
    njs_value_t         object, array;
    njs_array_t         *arr;
    njs_object_t        *obj;
    njs_typed_array_t   *ta;
    njs_array_buffer_t  *buffer;

    static const njs_value_t  string_buffer = njs_string("Buffer");

    ta = njs_buffer_slot(vm, njs_argument(args, 0), "this");
    if (njs_slow_path(ta == NULL)) {
        return NJS_ERROR;
    }

    obj = njs_object_alloc(vm);
    if (njs_slow_path(obj == NULL)) {
        return NJS_ERROR;
    }

    njs_set_object(&object, obj);

    ret = njs_value_property_set(vm, &object, njs_value_arg(&njs_string_type),
                                 njs_value_arg(&string_buffer));
    if (njs_slow_path(ret != NJS_OK)) {
        return NJS_ERROR;
    }

    arr = njs_array_alloc(vm, 1, ta->byte_length, 0);
    if (njs_slow_path(arr == NULL)) {
        return NJS_ERROR;
    }

    buffer = njs_typed_array_buffer(ta);
    if (njs_slow_path(njs_is_detached_buffer(buffer))) {
        njs_type_error(vm, "detached buffer");
        return NJS_ERROR;
    }

    p = &buffer->u.u8[ta->offset];
    end = p + ta->byte_length;
    value = arr->start;

    while (p < end) {
        njs_set_number(value++, *p++);
    }

    njs_set_array(&array, arr);

    ret = njs_value_property_set(vm, &object, njs_value_arg(&njs_string_data),
                                 &array);
    if (njs_slow_path(ret != NJS_OK)) {
        return NJS_ERROR;
    }

    njs_set_object(&vm->retval, obj);

    return NJS_OK;
}


const njs_buffer_encoding_t *
njs_buffer_encoding(njs_vm_t *vm, const njs_value_t *value)
{
    njs_str_t              name;
    njs_buffer_encoding_t  *encoding;

    if (njs_slow_path(!njs_is_string(value))) {
        if (njs_is_defined(value)) {
            njs_type_error(vm, "encoding must be a string");
            return NULL;
        }

        return &njs_buffer_encodings[0];
    }

    njs_string_get(value, &name);

    for (encoding = &njs_buffer_encodings[0];
         encoding->name.length != 0;
         encoding++)
    {
        if (njs_strstr_eq(&name, &encoding->name)) {
            return encoding;
        }
    }

    njs_type_error(vm, "\"%V\" encoding is not supported", &name);

    return NULL;
}


njs_int_t
njs_buffer_decode_string(njs_vm_t *vm, const njs_value_t *value,
    njs_value_t *dst, const njs_buffer_encoding_t *encoding)
{
    njs_int_t          ret;
    njs_str_t          str;
    njs_string_prop_t  string;

    (void) njs_string_prop(&string, value);

    str.start = string.start;
    str.length = string.size;

    *dst = *value;

    if (encoding->decode == njs_string_decode_utf8 && string.length != 0) {
        return NJS_OK;
    }

    ret = encoding->decode(vm, dst, &str);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return NJS_OK;
}


static void
njs_buffer_decode_destroy(njs_vm_t *vm, const njs_value_t *source,
    njs_value_t *target)
{
    njs_str_t  src, trg;

    njs_string_get(source, &src);
    njs_string_get(target, &trg);

    if (src.start != trg.start) {
        njs_mp_free(vm->mem_pool, trg.start);
    }
}


static const njs_object_prop_t  njs_buffer_prototype_properties[] =
{
    {
        .type = NJS_PROPERTY,
        .name = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG),
        .value = njs_string("Buffer"),
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY_HANDLER,
        .name = njs_string("constructor"),
        .value = njs_prop_handler(njs_object_prototype_create_constructor),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY_HANDLER,
        .name = njs_string("length"),
        .value = njs_prop_handler(njs_buffer_prototype_length),
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("readInt8"),
        .value = njs_native_function2(njs_buffer_prototype_read_int, 1,
                                      njs_buffer_magic(1, 1, 1)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("readUInt8"),
        .value = njs_native_function2(njs_buffer_prototype_read_int, 1,
                                      njs_buffer_magic(1, 0, 1)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("readInt16LE"),
        .value = njs_native_function2(njs_buffer_prototype_read_int, 1,
                                      njs_buffer_magic(2, 1, 1)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("readUInt16LE"),
        .value = njs_native_function2(njs_buffer_prototype_read_int, 1,
                                      njs_buffer_magic(2, 0, 1)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("readInt16BE"),
        .value = njs_native_function2(njs_buffer_prototype_read_int, 1,
                                      njs_buffer_magic(2, 1, 0)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("readUInt16BE"),
        .value = njs_native_function2(njs_buffer_prototype_read_int, 1,
                                      njs_buffer_magic(2, 0, 0)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("readInt32LE"),
        .value = njs_native_function2(njs_buffer_prototype_read_int, 1,
                                      njs_buffer_magic(4, 1, 1)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("readUInt32LE"),
        .value = njs_native_function2(njs_buffer_prototype_read_int, 1,
                                      njs_buffer_magic(4, 0, 1)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("readInt32BE"),
        .value = njs_native_function2(njs_buffer_prototype_read_int, 1,
                                      njs_buffer_magic(4, 1, 0)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("readUInt32BE"),
        .value = njs_native_function2(njs_buffer_prototype_read_int, 1,
                                      njs_buffer_magic(4, 0, 0)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("readIntLE"),
        .value = njs_native_function2(njs_buffer_prototype_read_int, 2,
                                      njs_buffer_magic(0, 1, 1)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("readUIntLE"),
        .value = njs_native_function2(njs_buffer_prototype_read_int, 2,
                                      njs_buffer_magic(0, 0, 1)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("readIntBE"),
        .value = njs_native_function2(njs_buffer_prototype_read_int, 2,
                                      njs_buffer_magic(0, 1, 0)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("readUIntBE"),
        .value = njs_native_function2(njs_buffer_prototype_read_int, 2,
                                      njs_buffer_magic(0, 0, 0)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("readFloatLE"),
        .value = njs_native_function2(njs_buffer_prototype_read_float, 1,
                                      njs_buffer_magic(4, 0, 1)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("readFloatBE"),
        .value = njs_native_function2(njs_buffer_prototype_read_float, 1,
                                      njs_buffer_magic(4, 0, 0)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("readDoubleLE"),
        .value = njs_native_function2(njs_buffer_prototype_read_float, 1,
                                      njs_buffer_magic(8, 0, 1)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("readDoubleBE"),
        .value = njs_native_function2(njs_buffer_prototype_read_float, 1,
                                      njs_buffer_magic(8, 0, 0)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("writeInt8"),
        .value = njs_native_function2(njs_buffer_prototype_write_int, 1,
                                      njs_buffer_magic(1, 1, 0)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("writeUInt8"),
        .value = njs_native_function2(njs_buffer_prototype_write_int, 1,
                                      njs_buffer_magic(1, 0, 0)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("writeInt16LE"),
        .value = njs_native_function2(njs_buffer_prototype_write_int, 1,
                                      njs_buffer_magic(2, 1, 1)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("writeUInt16LE"),
        .value = njs_native_function2(njs_buffer_prototype_write_int, 1,
                                      njs_buffer_magic(2, 0, 1)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("writeInt16BE"),
        .value = njs_native_function2(njs_buffer_prototype_write_int, 1,
                                      njs_buffer_magic(2, 1, 0)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("writeUInt16BE"),
        .value = njs_native_function2(njs_buffer_prototype_write_int, 1,
                                      njs_buffer_magic(2, 0, 0)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("writeInt32LE"),
        .value = njs_native_function2(njs_buffer_prototype_write_int, 1,
                                      njs_buffer_magic(4, 1, 1)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("writeUInt32LE"),
        .value = njs_native_function2(njs_buffer_prototype_write_int, 1,
                                      njs_buffer_magic(4, 0, 1)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("writeInt32BE"),
        .value = njs_native_function2(njs_buffer_prototype_write_int, 1,
                                      njs_buffer_magic(4, 1, 0)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("writeUInt32BE"),
        .value = njs_native_function2(njs_buffer_prototype_write_int, 1,
                                      njs_buffer_magic(4, 0, 0)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("writeIntLE"),
        .value = njs_native_function2(njs_buffer_prototype_write_int, 3,
                                      njs_buffer_magic(0, 1, 1)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("writeUIntLE"),
        .value = njs_native_function2(njs_buffer_prototype_write_int, 3,
                                      njs_buffer_magic(0, 0, 1)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("writeIntBE"),
        .value = njs_native_function2(njs_buffer_prototype_write_int, 3,
                                      njs_buffer_magic(0, 1, 0)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("writeUIntBE"),
        .value = njs_native_function2(njs_buffer_prototype_write_int, 3,
                                      njs_buffer_magic(0, 0, 0)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("writeFloatLE"),
        .value = njs_native_function2(njs_buffer_prototype_write_float, 1,
                                      njs_buffer_magic(4, 0, 1)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("writeFloatBE"),
        .value = njs_native_function2(njs_buffer_prototype_write_float, 1,
                                      njs_buffer_magic(4, 0, 0)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("writeDoubleLE"),
        .value = njs_native_function2(njs_buffer_prototype_write_float, 1,
                                      njs_buffer_magic(8, 0, 1)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("writeDoubleBE"),
        .value = njs_native_function2(njs_buffer_prototype_write_float, 1,
                                      njs_buffer_magic(8, 0, 0)),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("write"),
        .value = njs_native_function(njs_buffer_prototype_write, 1),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("fill"),
        .value = njs_native_function(njs_buffer_prototype_fill, 1),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("toString"),
        .value = njs_native_function(njs_buffer_prototype_to_string, 0),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("compare"),
        .value = njs_native_function(njs_buffer_prototype_compare, 1),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("copy"),
        .value = njs_native_function(njs_buffer_prototype_copy, 1),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("equals"),
        .value = njs_native_function(njs_buffer_prototype_equals, 1),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("indexOf"),
        .value = njs_native_function2(njs_buffer_prototype_index_of, 1, 0),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("lastIndexOf"),
        .value = njs_native_function2(njs_buffer_prototype_index_of, 1, 1),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("includes"),
        .value = njs_native_function(njs_buffer_prototype_includes, 1),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("subarray"),
        .value = njs_native_function2(njs_buffer_prototype_slice, 2, 0),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("slice"),
        .value = njs_native_function2(njs_buffer_prototype_slice, 2, 0),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("swap16"),
        .value = njs_native_function2(njs_buffer_prototype_swap, 0, 2),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("swap32"),
        .value = njs_native_function2(njs_buffer_prototype_swap, 0, 4),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("swap64"),
        .value = njs_native_function2(njs_buffer_prototype_swap, 0, 8),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("toJSON"),
        .value = njs_native_function(njs_buffer_prototype_to_json, 0),
        .writable = 1,
        .configurable = 1,
    },
};


const njs_object_init_t  njs_buffer_prototype_init = {
    njs_buffer_prototype_properties,
    njs_nitems(njs_buffer_prototype_properties),
};


static const njs_object_prop_t  njs_buffer_constructor_properties[] =
{
    {
        .type = NJS_PROPERTY,
        .name = njs_string("name"),
        .value = njs_string("Buffer"),
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("length"),
        .value = njs_value(NJS_NUMBER, 0, 0.0),
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("alloc"),
        .value = njs_native_function2(njs_buffer_alloc_safe, 0, 1),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("allocUnsafe"),
        .value = njs_native_function2(njs_buffer_alloc_safe, 1, 0),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_long_string("allocUnsafeSlow"),
        .value = njs_native_function2(njs_buffer_alloc_safe, 1, 0),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_long_string("byteLength"),
        .value = njs_native_function(njs_buffer_byte_length, 1),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_long_string("compare"),
        .value = njs_native_function(njs_buffer_compare, 2),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_long_string("concat"),
        .value = njs_native_function(njs_buffer_concat, 1),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("from"),
        .value = njs_native_function(njs_buffer_from, 3),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("isBuffer"),
        .value = njs_native_function(njs_buffer_is_buffer, 0),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("isEncoding"),
        .value = njs_native_function(njs_buffer_is_encoding, 0),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY_HANDLER,
        .name = njs_string("prototype"),
        .value = njs_prop_handler(njs_object_prototype_create),
    },
};


const njs_object_init_t  njs_buffer_constructor_init = {
    njs_buffer_constructor_properties,
    njs_nitems(njs_buffer_constructor_properties),
};


const njs_object_type_init_t  njs_buffer_type_init = {
   .constructor = njs_native_ctor(njs_buffer_constructor, 0, 0),
   .prototype_props = &njs_buffer_prototype_init,
   .constructor_props = &njs_buffer_constructor_init,
   .prototype_value = { .object = { .type = NJS_OBJECT } },
};


static const njs_object_prop_t  njs_buffer_constants_properties[] =
{
    {
        .type = NJS_PROPERTY,
        .name = njs_string("MAX_LENGTH"),
        .value = njs_value(NJS_NUMBER, 1, INT32_MAX),
        .enumerable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_long_string("MAX_STRING_LENGTH"),
        .value = njs_value(NJS_NUMBER, 1, NJS_STRING_MAX_LENGTH),
        .enumerable = 1,
    },
};


static const njs_object_init_t  njs_buffer_constants_init = {
    njs_buffer_constants_properties,
    njs_nitems(njs_buffer_constants_properties),
};


static njs_int_t
njs_buffer_constants(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value,
    njs_value_t *unused, njs_value_t *retval)
{
    return njs_object_prop_init(vm, &njs_buffer_constants_init, prop, value,
                                retval);
}


static njs_int_t
njs_buffer(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value,
    njs_value_t *unused, njs_value_t *retval)
{
    return njs_object_prop_init(vm, &njs_buffer_constructor_init, prop, value,
                                retval);
}


static const njs_object_prop_t  njs_buffer_object_properties[] =
{
    {
        .type = NJS_PROPERTY,
        .name = njs_string("name"),
        .value = njs_string("buffer"),
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY_HANDLER,
        .name = njs_string("constants"),
        .value = njs_prop_handler(njs_buffer_constants),
        .enumerable = 1,
    },

    {
        .type = NJS_PROPERTY_HANDLER,
        .name = njs_string("Buffer"),
        .value = njs_prop_handler(njs_buffer),
        .enumerable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("kMaxLength"),
        .value = njs_value(NJS_NUMBER, 1, INT32_MAX),
        .enumerable = 1,
    },
};


const njs_object_init_t  njs_buffer_object_init = {
    njs_buffer_object_properties,
    njs_nitems(njs_buffer_object_properties),
};
