/* * Copyright (C) Alexander Borisov * Copyright (C) Dmitry Volyntsev * Copyright (C) NGINX, Inc. */ #include #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 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, njs_value_t *retval); 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, njs_value_t *retval); static njs_int_t njs_buffer_from_typed_array(njs_vm_t *vm, njs_value_t *value, njs_value_t *retval); static njs_int_t njs_buffer_from_string(njs_vm_t *vm, njs_value_t *value, const njs_buffer_encoding_t *encoding, njs_value_t *retval); 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, njs_value_t *retval); static njs_int_t njs_buffer_fill(njs_vm_t *vm, njs_typed_array_t *array, const njs_value_t *fill, 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 njs_int_t njs_buffer(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t atom_id, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); static njs_int_t njs_buffer_constants(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t atom_id, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); static njs_int_t njs_buffer_constant(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t atom_id, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); static njs_int_t njs_buffer_init(njs_vm_t *vm); static njs_external_t njs_ext_buffer[] = { { .flags = NJS_EXTERN_PROPERTY, .name.string = njs_str("Buffer"), .enumerable = 1, .u.property = { .handler = njs_buffer, } }, { .flags = NJS_EXTERN_PROPERTY, .name.string = njs_str("constants"), .enumerable = 1, .u.property = { .handler = njs_buffer_constants, } }, { .flags = NJS_EXTERN_PROPERTY, .name.string = njs_str("kMaxLength"), .enumerable = 1, .u.property = { .handler = njs_buffer_constant, .magic32 = INT32_MAX, } }, }; njs_module_t njs_buffer_module = { .name = njs_str("buffer"), .preinit = NULL, .init = njs_buffer_init, }; 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 = njs_vm_proto(vm, NJS_OBJ_TYPE_ARRAY_BUFFER); 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 = njs_vm_proto(vm, NJS_OBJ_TYPE_BUFFER); 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; } static 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__ = njs_vm_proto(vm, NJS_OBJ_TYPE_BUFFER); 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_value_t *retval) { 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, njs_value_t *retval) { 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(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_value_t *retval) { njs_int_t ret; njs_value_t *value; 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, retval); case NJS_ARRAY_BUFFER: return njs_buffer_from_array_buffer(vm, value, njs_arg(args, nargs, 2), njs_arg(args, nargs, 3), retval); case NJS_STRING: encoding = njs_buffer_encoding(vm, njs_arg(args, nargs, 2), 1); if (njs_slow_path(encoding == NULL)) { return NJS_ERROR; } return njs_buffer_from_string(vm, value, encoding, retval); 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))) { njs_value_assign(value, retval); goto next; } ret = njs_buffer_from_object(vm, value, retval); 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, njs_value_t *retval) { double num; int64_t len; uint8_t *p; uint32_t i; njs_str_t str; njs_int_t ret; njs_value_t val, data, length; njs_typed_array_t *buffer; static const njs_str_t str_buffer = njs_str("Buffer"); next: ret = njs_value_property(vm, value, NJS_ATOM_STRING_length, &length); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } if (ret == NJS_DECLINED) { ret = njs_value_property(vm, value, NJS_ATOM_STRING_type, &val); if (njs_slow_path(ret != NJS_OK)) { return ret; } ret = njs_value_to_string(vm, &val, &val); if (njs_slow_path(ret != NJS_OK)) { return ret; } njs_string_get(vm, &val, &str); if (!njs_strstr_eq(&str, &str_buffer)) { return NJS_DECLINED; } ret = njs_value_property(vm, value, NJS_ATOM_STRING_data, &val); if (njs_slow_path(ret != NJS_OK)) { return ret; } if (njs_is_object(&val)) { njs_value_assign(&data, &val); value = &data; 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; for (i = 0; i < len; i++) { ret = njs_value_property_i64(vm, value, i, &val); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } ret = njs_value_to_number(vm, &val, &num); if (njs_slow_path(ret != NJS_OK)) { return ret; } *p++ = njs_number_to_int32(num); } njs_set_typed_array(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, njs_value_t *retval) { 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__ = njs_vm_proto(vm, NJS_OBJ_TYPE_BUFFER); buffer->offset = off; buffer->byte_length = len; njs_set_typed_array(retval, buffer); return NJS_OK; } static njs_int_t njs_buffer_from_typed_array(njs_vm_t *vm, njs_value_t *value, njs_value_t *retval) { 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(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_value_t *retval) { 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(vm, &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_set_typed_array(retval, buffer); return NJS_OK; } static size_t njs_buffer_decode_string_length(njs_vm_t *vm, 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(vm, &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, njs_value_t *retval) { 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(retval, njs_typed_array(value)->byte_length); return NJS_OK; case NJS_ARRAY_BUFFER: njs_set_number(retval, njs_array_buffer(value)->size); return NJS_OK; case NJS_DATA_VIEW: njs_set_number(retval, njs_data_view(value)->byte_length); return NJS_OK; case NJS_STRING: encoding = njs_buffer_encoding(vm, njs_arg(args, nargs, 2), 1); if (njs_slow_path(encoding == NULL)) { return NJS_ERROR; } size = njs_buffer_decode_string_length(vm, value, encoding); njs_set_number(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; } njs_int_t njs_value_buffer_get(njs_vm_t *vm, njs_value_t *value, njs_str_t *dst) { njs_typed_array_t *array; njs_array_buffer_t *array_buffer; if (njs_slow_path(!(njs_is_typed_array(value) || njs_is_data_view(value)))) { njs_type_error(vm, "first argument must be a Buffer or DataView"); return NJS_ERROR; } array = njs_typed_array(value); if (njs_slow_path(array == NULL)) { return NJS_ERROR; } array_buffer = njs_typed_array_writable(vm, array); if (njs_slow_path(array_buffer == NULL)) { return NJS_ERROR; } dst->length = array->byte_length; dst->start = &array_buffer->u.u8[array->offset]; return NJS_OK; } 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, njs_value_t *retval) { 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(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(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, njs_value_t *retval) { 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, retval); } static njs_int_t njs_buffer_concat(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { u_char *p, *src; size_t n; int64_t i, len, list_len; njs_int_t ret; njs_value_t *list, *value, *length, val; 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, &val); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } if (njs_slow_path(!njs_is_typed_array(&val))) { njs_type_error(vm, "\"list[%L]\" argument must be an " "instance of Buffer or Uint8Array", i); return NJS_ERROR; } arr = njs_typed_array(&val); 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); src = &njs_typed_array_buffer(arr)->u.u8[arr->offset]; p = njs_cpymem(p, src, n); len -= n; } } else { for (i = 0; len != 0 && i < list_len; i++) { ret = njs_value_property_i64(vm, list, i, &val); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } arr = njs_typed_array(&val); n = njs_min((size_t) len, arr->byte_length); src = &njs_typed_array_buffer(arr)->u.u8[arr->offset]; p = njs_cpymem(p, src, n); len -= n; } } if (len != 0) { njs_memzero(p, len); } njs_set_typed_array(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_value_t *retval) { njs_bool_t is; njs_typed_array_t *array; is = 0; array = njs_buffer_slot_internal(vm, njs_arg(args, nargs, 1)); if (njs_fast_path(array != NULL && array->object.__proto__ == njs_vm_proto(vm, NJS_OBJ_TYPE_BUFFER))) { is = 1; } njs_set_boolean(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, njs_value_t *retval) { njs_value_t *value; value = njs_arg(args, nargs, 1); njs_set_boolean(retval, njs_is_defined(value) && njs_buffer_encoding(vm, value, 0) != NULL); return NJS_OK; } static njs_int_t njs_buffer_prototype_length(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t unused, 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, njs_value_t *retval) { 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(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, njs_value_t *retval) { 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(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, njs_value_t *retval) { uint8_t *u8; int64_t i64; uint32_t u32; uint64_t index, size; njs_int_t ret; njs_bool_t little, swap, sign; 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; sign = (magic >> 1) & 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: if (sign) { if (i64 < INT8_MIN || i64 > INT8_MAX) { njs_range_error(vm, "value is outside the range of " "a representable int8"); return NJS_ERROR; } } else { if (i64 < 0 || i64 > UINT8_MAX) { njs_range_error(vm, "value is outside the range of " "a representable uint8"); return NJS_ERROR; } } *u8 = i64; break; case 2: u32 = (uint16_t) i64; if (sign) { if (i64 < INT16_MIN || i64 > INT16_MAX) { njs_range_error(vm, "value is outside the range of " "a representable int16"); return NJS_ERROR; } } else { if (i64 < 0 || i64 > UINT16_MAX) { njs_range_error(vm, "value is outside the range of " "a representable uint16"); return NJS_ERROR; } } if (swap) { u32 = njs_bswap_u16(u32); } njs_set_u16(u8, u32); break; case 3: if (sign) { if (i64 < INT24_MIN || i64 > INT24_MAX) { njs_range_error(vm, "value is outside the range of " "a representable int24"); return NJS_ERROR; } } else { if (i64 < 0 || i64 > UINT24_MAX) { njs_range_error(vm, "value is outside the range of " "a representable uint24"); return NJS_ERROR; } } 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: if (sign) { if (i64 < INT32_MIN || i64 > INT32_MAX) { njs_range_error(vm, "value is outside the range of " "a representable int32"); return NJS_ERROR; } } else { if (i64 < 0 || i64 > UINT32_MAX) { njs_range_error(vm, "value is outside the range of " "a representable uint32"); return NJS_ERROR; } } u32 = i64; if (swap) { u32 = njs_bswap_u32(u32); } njs_set_u32(u8, u32); break; case 5: if (sign) { if (i64 < INT40_MIN || i64 > INT40_MAX) { njs_range_error(vm, "value is outside the range of " "a representable int40"); return NJS_ERROR; } } else { if (i64 < 0 || i64 > UINT40_MAX) { njs_range_error(vm, "value is outside the range of " "a representable uint40"); return NJS_ERROR; } } 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 (sign) { if (i64 < INT48_MIN || i64 > INT48_MAX) { njs_range_error(vm, "value is outside the range of " "a representable int48"); return NJS_ERROR; } } else { if (i64 < 0 || i64 > UINT48_MAX) { njs_range_error(vm, "value is outside the range of " "a representable uint48"); return NJS_ERROR; } } 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(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, njs_value_t *retval) { 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_number(retval, index + size); 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, njs_value_t *retval) { 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, 1); 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, retval); } 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, njs_value_t *retval) { uint8_t *start; njs_int_t ret; njs_str_t str; njs_value_t dst; const u_char *p, *end, *prev; 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(vm, &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; } length = njs_min(str.length, (size_t) length); if (encoding->decode == njs_string_decode_utf8) { /* Avoid writing incomplete UTF-8 characters. */ p = prev = str.start; end = p + length; while (p < end) { p = njs_utf8_next(p, str.start + str.length); if (p <= end) { prev = p; } } length = prev - str.start; } memcpy(start, str.start, length); done: njs_set_number(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, njs_value_t *retval) { 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_value_assign(retval, 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, 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, 1); 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(vm, &dst, &str); if (str.length == 0) { memset(start, 0, end - start); return NJS_OK; } while (start < end) { n = njs_min(str.length, (size_t) (end - start)); start = njs_cpymem(start, str.start, n); } 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, njs_value_t *retval) { 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), 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_set_empty_string(vm, retval); return NJS_OK; } return encoding->encode(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, njs_value_t *retval) { 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), retval); } static njs_int_t njs_buffer_prototype_copy(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { 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(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_value_t *retval) { 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, retval); if (njs_slow_path(ret != NJS_OK)) { return ret; } njs_set_boolean(retval, njs_number(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, njs_value_t *retval) { uint8_t byte; int64_t from, to, increment, length, 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; 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 (from >= 0) { from = njs_min(from, length); } else { from = njs_max(0, length + from); } } encoding: encoding = njs_buffer_encoding(vm, enc, 1); 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[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(vm, &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 (last) { from = njs_min(from, length - (int64_t) str.length); if (to > from) { goto done; } } else { to -= (int64_t) str.length - 1; if (from > to) { goto done; } } if (from == to && str.length == 0) { index = 0; goto done; } for (i = from; i != to; i += increment) { if (memcmp(&u8[i], str.start, str.length) == 0) { index = i; goto done; } } break; case NJS_NUMBER: byte = njs_number_to_uint32(njs_number(value)); if (last) { from = njs_min(from, length - 1); } for (i = from; i != to; i += increment) { if (u8[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(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_value_t *retval) { njs_int_t ret; ret = njs_buffer_prototype_index_of(vm, args, nargs, unused, retval); if (njs_slow_path(ret != NJS_OK)) { return ret; } njs_set_boolean(retval, (njs_number(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_value_t *retval) { njs_int_t ret; njs_typed_array_t *array; ret = njs_typed_array_prototype_slice(vm, args, nargs, unused, retval); if (njs_slow_path(ret != NJS_OK)) { return ret; } array = njs_typed_array(retval); array->object.__proto__ = njs_vm_proto(vm, NJS_OBJ_TYPE_BUFFER); 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, njs_value_t *retval) { 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(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, njs_value_t *retval) { u_char *p, *end; njs_int_t ret; njs_value_t *value, setval; njs_value_t object, array; njs_array_t *arr; njs_object_t *obj; njs_typed_array_t *ta; njs_array_buffer_t *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); njs_atom_to_value(vm, &setval, NJS_ATOM_STRING_Buffer); ret = njs_value_property_set(vm, &object, NJS_ATOM_STRING_type, &setval); 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_ATOM_STRING_data, &array); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } njs_set_object(retval, obj); return NJS_OK; } const njs_buffer_encoding_t * njs_buffer_encoding(njs_vm_t *vm, njs_value_t *value, njs_bool_t throw) { njs_str_t name; const 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(vm, value, &name); for (encoding = &njs_buffer_encodings[0]; encoding->name.length != 0; encoding++) { if (njs_strstr_eq(&name, &encoding->name)) { return encoding; } } if (throw) { 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(vm, &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 const njs_object_prop_init_t njs_buffer_prototype_properties[] = { NJS_DECLARE_PROP_VALUE(SYMBOL_toStringTag, njs_ascii_strval("Buffer"), NJS_OBJECT_PROP_VALUE_C), NJS_DECLARE_PROP_HANDLER(STRING_constructor, njs_object_prototype_create_constructor, 0, NJS_OBJECT_PROP_VALUE_CW), NJS_DECLARE_PROP_HANDLER(STRING_length, njs_buffer_prototype_length, 0, 0), NJS_DECLARE_PROP_NATIVE(STRING_readInt8, njs_buffer_prototype_read_int, 1, njs_buffer_magic(1, 1, 1)), NJS_DECLARE_PROP_NATIVE(STRING_readUInt8, njs_buffer_prototype_read_int, 1, njs_buffer_magic(1, 0, 1)), NJS_DECLARE_PROP_NATIVE(STRING_readInt16LE, njs_buffer_prototype_read_int, 1, njs_buffer_magic(2, 1, 1)), NJS_DECLARE_PROP_NATIVE(STRING_readUInt16LE, njs_buffer_prototype_read_int, 1, njs_buffer_magic(2, 0, 1)), NJS_DECLARE_PROP_NATIVE(STRING_readInt16BE, njs_buffer_prototype_read_int, 1, njs_buffer_magic(2, 1, 0)), NJS_DECLARE_PROP_NATIVE(STRING_readUInt16BE, njs_buffer_prototype_read_int, 1, njs_buffer_magic(2, 0, 0)), NJS_DECLARE_PROP_NATIVE(STRING_readInt32LE, njs_buffer_prototype_read_int, 1, njs_buffer_magic(4, 1, 1)), NJS_DECLARE_PROP_NATIVE(STRING_readUInt32LE, njs_buffer_prototype_read_int, 1, njs_buffer_magic(4, 0, 1)), NJS_DECLARE_PROP_NATIVE(STRING_readInt32BE, njs_buffer_prototype_read_int, 1, njs_buffer_magic(4, 1, 0)), NJS_DECLARE_PROP_NATIVE(STRING_readUInt32BE, njs_buffer_prototype_read_int, 1, njs_buffer_magic(4, 0, 0)), NJS_DECLARE_PROP_NATIVE(STRING_readIntLE, njs_buffer_prototype_read_int, 2, njs_buffer_magic(0, 1, 1)), NJS_DECLARE_PROP_NATIVE(STRING_readUIntLE, njs_buffer_prototype_read_int, 2, njs_buffer_magic(0, 0, 1)), NJS_DECLARE_PROP_NATIVE(STRING_readIntBE, njs_buffer_prototype_read_int, 2, njs_buffer_magic(0, 1, 0)), NJS_DECLARE_PROP_NATIVE(STRING_readUIntBE, njs_buffer_prototype_read_int, 2, njs_buffer_magic(0, 0, 0)), NJS_DECLARE_PROP_NATIVE(STRING_readFloatLE, njs_buffer_prototype_read_float, 1, njs_buffer_magic(4, 0, 1)), NJS_DECLARE_PROP_NATIVE(STRING_readFloatBE, njs_buffer_prototype_read_float, 1, njs_buffer_magic(4, 0, 0)), NJS_DECLARE_PROP_NATIVE(STRING_readDoubleLE, njs_buffer_prototype_read_float, 1, njs_buffer_magic(8, 0, 1)), NJS_DECLARE_PROP_NATIVE(STRING_readDoubleBE, njs_buffer_prototype_read_float, 1, njs_buffer_magic(8, 0, 0)), NJS_DECLARE_PROP_NATIVE(STRING_writeInt8, njs_buffer_prototype_write_int, 1, njs_buffer_magic(1, 1, 0)), NJS_DECLARE_PROP_NATIVE(STRING_writeUInt8, njs_buffer_prototype_write_int, 1, njs_buffer_magic(1, 0, 0)), NJS_DECLARE_PROP_NATIVE(STRING_writeInt16LE, njs_buffer_prototype_write_int, 1, njs_buffer_magic(2, 1, 1)), NJS_DECLARE_PROP_NATIVE(STRING_writeUInt16LE, njs_buffer_prototype_write_int, 1, njs_buffer_magic(2, 0, 1)), NJS_DECLARE_PROP_NATIVE(STRING_writeInt16BE, njs_buffer_prototype_write_int, 1, njs_buffer_magic(2, 1, 0)), NJS_DECLARE_PROP_NATIVE(STRING_writeUInt16BE, njs_buffer_prototype_write_int, 1, njs_buffer_magic(2, 0, 0)), NJS_DECLARE_PROP_NATIVE(STRING_writeInt32LE, njs_buffer_prototype_write_int, 1, njs_buffer_magic(4, 1, 1)), NJS_DECLARE_PROP_NATIVE(STRING_writeUInt32LE, njs_buffer_prototype_write_int, 1, njs_buffer_magic(4, 0, 1)), NJS_DECLARE_PROP_NATIVE(STRING_writeInt32BE, njs_buffer_prototype_write_int, 1, njs_buffer_magic(4, 1, 0)), NJS_DECLARE_PROP_NATIVE(STRING_writeUInt32BE, njs_buffer_prototype_write_int, 1, njs_buffer_magic(4, 0, 0)), NJS_DECLARE_PROP_NATIVE(STRING_writeIntLE, njs_buffer_prototype_write_int, 1, njs_buffer_magic(0, 1, 1)), NJS_DECLARE_PROP_NATIVE(STRING_writeUIntLE, njs_buffer_prototype_write_int, 1, njs_buffer_magic(0, 0, 1)), NJS_DECLARE_PROP_NATIVE(STRING_writeIntBE, njs_buffer_prototype_write_int, 1, njs_buffer_magic(0, 1, 0)), NJS_DECLARE_PROP_NATIVE(STRING_writeUIntBE, njs_buffer_prototype_write_int, 1, njs_buffer_magic(0, 0, 0)), NJS_DECLARE_PROP_NATIVE(STRING_writeFloatLE, njs_buffer_prototype_write_float, 1, njs_buffer_magic(4, 0, 1)), NJS_DECLARE_PROP_NATIVE(STRING_writeFloatBE, njs_buffer_prototype_write_float, 1, njs_buffer_magic(4, 0, 0)), NJS_DECLARE_PROP_NATIVE(STRING_writeDoubleLE, njs_buffer_prototype_write_float, 1, njs_buffer_magic(8, 0, 1)), NJS_DECLARE_PROP_NATIVE(STRING_writeDoubleBE, njs_buffer_prototype_write_float, 1, njs_buffer_magic(8, 0, 0)), NJS_DECLARE_PROP_NATIVE(STRING_write, njs_buffer_prototype_write, 1, 0), NJS_DECLARE_PROP_NATIVE(STRING_fill, njs_buffer_prototype_fill, 1, 0), NJS_DECLARE_PROP_NATIVE(STRING_toString, njs_buffer_prototype_to_string, 0, 0), NJS_DECLARE_PROP_NATIVE(STRING_compare, njs_buffer_prototype_compare, 1, 0), NJS_DECLARE_PROP_NATIVE(STRING_copy, njs_buffer_prototype_copy, 1, 0), NJS_DECLARE_PROP_NATIVE(STRING_equals, njs_buffer_prototype_equals, 1, 0), NJS_DECLARE_PROP_NATIVE(STRING_indexOf, njs_buffer_prototype_index_of, 1, 0), NJS_DECLARE_PROP_NATIVE(STRING_lastIndexOf, njs_buffer_prototype_index_of, 1, 1), NJS_DECLARE_PROP_NATIVE(STRING_includes, njs_buffer_prototype_includes, 1, 0), NJS_DECLARE_PROP_NATIVE(STRING_subarray, njs_buffer_prototype_slice, 2, 0), NJS_DECLARE_PROP_NATIVE(STRING_slice, njs_buffer_prototype_slice, 2, 0), NJS_DECLARE_PROP_NATIVE(STRING_swap16, njs_buffer_prototype_swap, 0, 2), NJS_DECLARE_PROP_NATIVE(STRING_swap32, njs_buffer_prototype_swap, 0, 4), NJS_DECLARE_PROP_NATIVE(STRING_swap64, njs_buffer_prototype_swap, 0, 8), NJS_DECLARE_PROP_NATIVE(STRING_toJSON, njs_buffer_prototype_to_json, 0, 0), }; const njs_object_init_t njs_buffer_prototype_init = { njs_buffer_prototype_properties, njs_nitems(njs_buffer_prototype_properties), }; static const njs_object_prop_init_t njs_buffer_constructor_properties[] = { NJS_DECLARE_PROP_LENGTH(0), NJS_DECLARE_PROP_NAME("Buffer"), NJS_DECLARE_PROP_HANDLER(STRING_prototype, njs_object_prototype_create, 0, 0), NJS_DECLARE_PROP_NATIVE(STRING_alloc, njs_buffer_alloc_safe, 0, 1), NJS_DECLARE_PROP_NATIVE(STRING_allocUnsafe, njs_buffer_alloc_safe, 0, 0), NJS_DECLARE_PROP_NATIVE(STRING_allocUnsafeSlow, njs_buffer_alloc_safe, 1, 0), NJS_DECLARE_PROP_NATIVE(STRING_byteLength, njs_buffer_byte_length, 1, 0), NJS_DECLARE_PROP_NATIVE(STRING_compare, njs_buffer_compare, 2, 0), NJS_DECLARE_PROP_NATIVE(STRING_concat, njs_buffer_concat, 1, 0), NJS_DECLARE_PROP_NATIVE(STRING_from, njs_buffer_from, 3, 0), NJS_DECLARE_PROP_NATIVE(STRING_isBuffer, njs_buffer_is_buffer, 1, 0), NJS_DECLARE_PROP_NATIVE(STRING_isEncoding, njs_buffer_is_encoding, 1, 0), }; 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_init_t njs_buffer_constants_properties[] = { NJS_DECLARE_PROP_VALUE(STRING_MAX_LENGTH, njs_value(NJS_NUMBER, 1, INT32_MAX), NJS_OBJECT_PROP_VALUE_E), NJS_DECLARE_PROP_VALUE(STRING_MAX_STRING_LENGTH, njs_value(NJS_NUMBER, 1, NJS_STRING_MAX_LENGTH), NJS_OBJECT_PROP_VALUE_E), }; 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(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t atom_id, njs_value_t *value, njs_value_t *unused, njs_value_t *retval) { return njs_object_prop_init(vm, &njs_buffer_constructor_init, prop, atom_id, value, retval); } static njs_int_t njs_buffer_constants(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t atom_id, njs_value_t *value, njs_value_t *unused, njs_value_t *retval) { return njs_object_prop_init(vm, &njs_buffer_constants_init, prop, atom_id, value, retval); } static njs_int_t njs_buffer_constant(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t not_used, njs_value_t *value, njs_value_t *unused, njs_value_t *retval) { njs_value_number_set(retval, njs_vm_prop_magic32(prop)); return NJS_OK; } static njs_int_t njs_buffer_init(njs_vm_t *vm) { njs_int_t ret, proto_id; njs_mod_t *module; njs_opaque_value_t value; proto_id = njs_vm_external_prototype(vm, njs_ext_buffer, njs_nitems(njs_ext_buffer)); if (njs_slow_path(proto_id < 0)) { return NJS_ERROR; } ret = njs_vm_external_create(vm, njs_value_arg(&value), proto_id, NULL, 1); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } module = njs_vm_add_module(vm, &njs_str_value("buffer"), njs_value_arg(&value)); if (njs_slow_path(module == NULL)) { return NJS_ERROR; } return NJS_OK; }