njs_array_prototype_shift(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    int64_t      i, length;
    njs_int_t    ret;
    njs_array_t  *array;
    njs_value_t  *this, entry;

    this = njs_argument(args, 0);

    ret = njs_value_to_object(vm, this);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ret = njs_object_length(vm, this, &length);
    if (njs_slow_path(ret == NJS_ERROR)) {
        return ret;
    }

    if (length == 0) {
        ret = njs_object_length_set(vm, this, length);
        if (njs_slow_path(ret == NJS_ERROR)) {
            return ret;
        }

        njs_set_undefined(&vm->retval);

        return NJS_OK;
    }

    ret = njs_value_property_i64(vm, this, 0, &vm->retval);
    if (njs_slow_path(ret == NJS_ERROR)) {
        return NJS_ERROR;
    }

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

        array->start++;
        array->length--;

    } else {

        ret = njs_value_property_i64_delete(vm, this, 0, &vm->retval);
        if (njs_slow_path(ret == NJS_ERROR)) {
            return ret;
        }

        for (i = 1; i < length; i++) {
            ret = njs_value_property_i64_delete(vm, this, i, &entry);
            if (njs_slow_path(ret == NJS_ERROR)) {
                return ret;
            }

            if (ret == NJS_OK) {
                ret = njs_value_property_i64_set(vm, this, i - 1, &entry);
                if (njs_slow_path(ret == NJS_ERROR)) {
                    return ret;
                }
            }
        }

        ret = njs_object_length_set(vm, this, length - 1);
        if (njs_slow_path(ret == NJS_ERROR)) {
            return ret;
        }
    }

    return NJS_OK;
}


njs_array_handler_map(njs_vm_t *vm, njs_iterator_args_t *args,
    njs_value_t *entry, int64_t n)
{
    njs_int_t    ret;
    njs_array_t  *retval;
    njs_value_t  this;

    retval = args->data;

    if (retval->object.fast_array) {
        njs_set_invalid(&retval->start[n]);
    }

    if (njs_is_valid(entry)) {
        ret = njs_array_iterator_call(vm, args, entry, n);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        if (njs_is_valid(&vm->retval)) {
            if (retval->object.fast_array) {
                retval->start[n] = vm->retval;

            } else {
                njs_set_array(&this, retval);

                ret = njs_value_property_i64_set(vm, &this, n, &vm->retval);
                if (njs_slow_path(ret != NJS_OK)) {
                    return ret;
                }
            }
        }
    }

    return NJS_OK;
}

Fixed Array.prototype.slice() with slow "this" argument.

Previously, when "this" argument was not a fast array, but the "deleted" array
was a fast array, the "deleted" array may be left in uninitialized state if
"this" argument had gaps.

This fix is to ensure that "deleted" is properly initialized.

This fixes #485 issue on Github.


njs_array_compare(const void *a, const void *b, void *c)
{
    double                 num;
    njs_int_t              ret;
    njs_value_t            arguments[3], retval;
    njs_array_sort_ctx_t   *ctx;
    njs_array_sort_slot_t  *aslot, *bslot;

    ctx = c;

    if (ctx->exception) {
        return 0;
    }

    aslot = (njs_array_sort_slot_t *) a;
    bslot = (njs_array_sort_slot_t *) b;

    if (ctx->function != NULL) {
        njs_set_undefined(&arguments[0]);
        arguments[1] = aslot->value;
        arguments[2] = bslot->value;

        ret = njs_function_apply(ctx->vm, ctx->function, arguments, 3, &retval);
        if (njs_slow_path(ret != NJS_OK)) {
            goto exception;
        }

        ret = njs_value_to_number(ctx->vm, &retval, &num);
        if (njs_slow_path(ret != NJS_OK)) {
            goto exception;
        }

        if (njs_slow_path(isnan(num))) {
            return 0;
        }

        if (num != 0) {
            return (num > 0) - (num < 0);
        }

        goto compare_same;
    }

    if (aslot->str == NULL) {
        aslot->str = njs_arr_add(&ctx->strings);
        ret = njs_value_to_string(ctx->vm, aslot->str, &aslot->value);
        if (njs_slow_path(ret != NJS_OK)) {
            goto exception;
        }
    }

    if (bslot->str == NULL) {
        bslot->str = njs_arr_add(&ctx->strings);
        ret = njs_value_to_string(ctx->vm, bslot->str, &bslot->value);
        if (njs_slow_path(ret != NJS_OK)) {
            goto exception;
        }
    }

    ret = njs_string_cmp(aslot->str, bslot->str);

    if (ret != 0) {
        return ret;
    }

compare_same:

    /* Ensures stable sorting. */

    return (aslot->pos > bslot->pos) - (aslot->pos < bslot->pos);

exception:

    ctx->exception = 1;

    return 0;
}


njs_array_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    double       num;
    uint32_t     size;
    njs_value_t  *value;
    njs_array_t  *array;

    args = &args[1];
    size = nargs - 1;

    if (size == 1 && njs_is_number(&args[0])) {
        num = njs_number(&args[0]);
        size = (uint32_t) njs_number_to_length(num);

        if ((double) size != num) {
            njs_range_error(vm, "Invalid array length");
            return NJS_ERROR;
        }

        args = NULL;
    }

    array = njs_array_alloc(vm, size <= NJS_ARRAY_FLAT_MAX_LENGTH,
                            size, NJS_ARRAY_SPARE);

    if (njs_fast_path(array != NULL)) {

        if (array->object.fast_array) {
            value = array->start;

            if (args == NULL) {
                while (size != 0) {
                    njs_set_invalid(value);
                    value++;
                    size--;
                }

            } else {
                while (size != 0) {
                    njs_retain(args);
                    *value++ = *args++;
                    size--;
                }
            }
        }

        njs_set_array(&vm->retval, array);

        return NJS_OK;
    }

    return NJS_ERROR;
}


njs_array_string_add(njs_vm_t *vm, njs_array_t *array, const u_char *start,
    size_t size, size_t length)
{
    njs_int_t  ret;

    ret = njs_array_expand(vm, array, 0, 1);

    if (njs_fast_path(ret == NJS_OK)) {
        return njs_string_new(vm, &array->start[array->length++], start, size,
                              length);
    }

    return ret;
}


njs_array_expand(njs_vm_t *vm, njs_array_t *array, uint32_t prepend,
    uint32_t append)
{
    uint32_t     free_before, free_after;
    uint64_t     size;
    njs_value_t  *start, *old;

    njs_assert(array->object.fast_array);

    free_before = array->start - array->data;
    free_after = array->size - array->length - free_before;

    if (njs_fast_path(free_before >= prepend && free_after >= append)) {
        return NJS_OK;
    }

    size = (uint64_t) prepend + array->length + append;

    if (size < 16) {
        size *= 2;

    } else {
        size += size / 2;
    }

    if (njs_slow_path(size > (UINT32_MAX / sizeof(njs_value_t)))) {
        goto memory_error;
    }

    start = njs_mp_align(vm->mem_pool, sizeof(njs_value_t),
                         size * sizeof(njs_value_t));
    if (njs_slow_path(start == NULL)) {
        goto memory_error;
    }

    array->size = size;

    old = array->data;
    array->data = start;
    start += prepend;

    if (array->length != 0) {
        memcpy(start, array->start, array->length * sizeof(njs_value_t));
    }

    array->start = start;

    njs_mp_free(vm->mem_pool, old);

    return NJS_OK;

memory_error:

    njs_memory_error(vm);

    return NJS_ERROR;
}


njs_array_prototype_slice_copy(njs_vm_t *vm, njs_value_t *this,
    int64_t start, int64_t length)
{
    size_t             size;
    u_char             *dst;
    uint32_t           n;
    njs_int_t          ret;
    njs_array_t        *array, *keys;
    njs_value_t        *value, *last, retval, self;
    const u_char       *src, *end;
    njs_slice_prop_t   string_slice;
    njs_string_prop_t  string;

    keys = NULL;
    array = njs_array_alloc(vm, 0, length, NJS_ARRAY_SPARE);
    if (njs_slow_path(array == NULL)) {
        return NJS_ERROR;
    }

    if (njs_slow_path(length == 0)) {
        ret = NJS_OK;
        goto done;
    }

    n = 0;

    if (njs_fast_path(array->object.fast_array)) {
        if (njs_is_string(this) || njs_is_object_string(this)) {

            if (njs_is_object_string(this)) {
                this = njs_object_value(this);
            }

            string_slice.start = start;
            string_slice.length = length;
            string_slice.string_length = njs_string_prop(&string, this);

            njs_string_slice_string_prop(&string, &string, &string_slice);

            src = string.start;
            end = src + string.size;

            if (string.length == 0) {
                /* Byte string. */
                do {
                    value = &array->start[n++];
                    dst = njs_string_short_start(value);
                    *dst = *src++;
                    njs_string_short_set(value, 1, 0);

                    length--;
                } while (length != 0);

            } else {
                /* UTF-8 or ASCII string. */
                do {
                    value = &array->start[n++];
                    dst = njs_string_short_start(value);
                    dst = njs_utf8_copy(dst, &src, end);
                    size = dst - njs_string_short_start(value);
                    njs_string_short_set(value, size, 1);

                    length--;
                } while (length != 0);
            }

        } else if (njs_is_object(this)) {

            last = &array->start[length];

            for (value = array->start; value < last; value++, start++) {
                ret = njs_value_property_i64(vm, this, start, value);
                if (njs_slow_path(ret != NJS_OK)) {
                    if (ret == NJS_ERROR) {
                        return NJS_ERROR;
                    }

                    njs_set_invalid(value);
                }
            }

        } else {

            /* Primitive types. */

            value = array->start;

            do {
                njs_set_invalid(value++);
                length--;
            } while (length != 0);
        }

        ret = NJS_OK;
        goto done;
    }

    njs_set_array(&self, array);

    if (njs_fast_object(length)) {
        do {
            ret = njs_value_property_i64(vm, this, start++, &retval);
            if (njs_slow_path(ret == NJS_ERROR)) {
                return NJS_ERROR;
            }

            if (ret == NJS_OK) {
                ret = njs_value_property_i64_set(vm, &self, start, &retval);
                if (njs_slow_path(ret == NJS_ERROR)) {
                    return ret;
                }
            }

            length--;
        } while (length != 0);

        ret = NJS_OK;
        goto done;
    }

    keys = njs_array_indices(vm, this);
    if (njs_slow_path(keys == NULL)) {
        return NJS_ERROR;
    }

    for (n = 0; n < keys->length; n++) {
        ret = njs_value_property(vm, this, &keys->start[n], &retval);
        if (njs_slow_path(ret == NJS_ERROR)) {
            goto done;
        }

        ret = njs_value_property_set(vm, &self, &keys->start[n], &retval);
        if (njs_slow_path(ret == NJS_ERROR)) {
            goto done;
        }
    }

    ret = NJS_OK;

done:

    if (keys != NULL) {
        njs_array_destroy(vm, keys);
    }

    njs_set_array(&vm->retval, array);

    return ret;
}


njs_array_prototype_fill(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    int64_t       i, length, start, end;
    njs_int_t     ret;
    njs_array_t   *array;
    njs_value_t   *this, *value;

    this = njs_argument(args, 0);

    ret = njs_value_to_object(vm, this);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    array = NULL;

    if (njs_is_fast_array(this)) {
        array = njs_array(this);
        length = array->length;

    } else {
        ret = njs_object_length(vm, this, &length);
        if (njs_slow_path(ret == NJS_ERROR)) {
            return ret;
        }
    }

    ret = njs_value_to_integer(vm, njs_arg(args, nargs, 2), &start);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    start = (start < 0) ? njs_max(length + start, 0) : njs_min(start, length);

    if (njs_is_undefined(njs_arg(args, nargs, 3))) {
        end = length;

    } else {
        ret = njs_value_to_integer(vm, njs_arg(args, nargs, 3), &end);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    end = (end < 0) ? njs_max(length + end, 0) : njs_min(end, length);

    value = njs_arg(args, nargs, 1);

    if (array != NULL) {
        for (i = start; i < end; i++) {
            array->start[i] = *value;
        }

        vm->retval = *this;

        return NJS_OK;
    }

    value = njs_arg(args, nargs, 1);

    while (start < end) {
        ret = njs_value_property_i64_set(vm, this, start++, value);
        if (njs_slow_path(ret == NJS_ERROR)) {
            return ret;
        }
    }

    vm->retval = *this;

    return NJS_OK;
}


njs_array_handler_includes(njs_vm_t *vm, njs_iterator_args_t *args,
    njs_value_t *entry, int64_t n)
{
    if (!njs_is_valid(entry)) {
        entry = njs_value_arg(&njs_value_undefined);
    }

    if (njs_values_same_zero(args->argument, entry)) {
        njs_set_true(&vm->retval);

        return NJS_DONE;
    }

    return NJS_OK;
}


njs_array_prototype_copy_within(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t unused)
{
    int64_t      length, count, to, from, end;
    njs_int_t    ret;
    njs_value_t  *this, *value;

    this = njs_argument(args, 0);

    ret = njs_value_to_object(vm, this);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

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

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

    to = (to < 0) ? njs_max(length + to, 0) : njs_min(to, length);

    ret = njs_value_to_integer(vm, njs_arg(args, nargs, 2), &from);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    from = (from < 0) ? njs_max(length + from, 0) : njs_min(from, length);

    value = njs_arg(args, nargs, 3);

    if (njs_is_undefined(value)) {
        end = length;

    } else {
        ret = njs_value_to_integer(vm, value, &end);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    end = (end < 0) ? njs_max(length + end, 0) : njs_min(end, length);

    count = njs_min(end - from, length - to);

    njs_vm_retval_set(vm, this);

    return njs_array_copy_within(vm, this, to, from, count,
                                 !(from < to && to < from + count));
}


njs_array_copy_within(njs_vm_t *vm, njs_value_t *array, int64_t to_pos,
    int64_t from_pos, int64_t count, njs_bool_t forward)
{
    int64_t      i, from, to;
    njs_int_t    ret;
    njs_array_t  *arr;
    njs_value_t  value;

    if (njs_fast_path(njs_is_fast_array(array) && count > 0)) {
        arr = njs_array(array);

        memmove(&arr->start[to_pos], &arr->start[from_pos],
                count * sizeof(njs_value_t));

        return NJS_OK;
    }

    if (!forward) {
        from_pos += count - 1;
        to_pos += count - 1;
    }

    for (i = 0; i < count; i++) {
        if (forward) {
            from = from_pos + i;
            to = to_pos + i;

        } else {
            from = from_pos - i;
            to = to_pos - i;
        }

        ret = njs_value_property_i64(vm, array, from, &value);
        if (njs_slow_path(ret == NJS_ERROR)) {
            return NJS_ERROR;
        }

        if (ret == NJS_OK) {
            ret = njs_value_property_i64_set(vm, array, to, &value);

        } else {
            ret = njs_value_property_i64_delete(vm, array, to, NULL);
        }

        if (njs_slow_path(ret == NJS_ERROR)) {
            return NJS_ERROR;
        }
    }

    return NJS_OK;
}


njs_array_length_redefine(njs_vm_t *vm, njs_value_t *value, uint32_t length)
{
    njs_object_prop_t  *prop;

    static const njs_value_t  string_length = njs_string("length");

    if (njs_slow_path(!njs_is_array(value))) {
        njs_internal_error(vm, "njs_array_length_redefine() "
                           "applied to non-array");
        return NJS_ERROR;
    }

    prop = njs_object_property_add(vm, value, njs_value_arg(&string_length), 1);
    if (njs_slow_path(prop == NULL)) {
        njs_internal_error(vm, "njs_array_length_redefine() "
                           "cannot redefine \"length\"");
        return NJS_ERROR;
    }

    prop->enumerable = 0;
    prop->configurable = 0;

    njs_value_number_set(&prop->value, length);

    return NJS_OK;
}


njs_array_add(njs_vm_t *vm, njs_array_t *array, njs_value_t *value)
{
    njs_int_t  ret;

    ret = njs_array_expand(vm, array, 0, 1);

    if (njs_fast_path(ret == NJS_OK)) {
        /* GC: retain value. */
        array->start[array->length++] = *value;
    }

    return ret;
}


njs_array_prototype_concat(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    double       idx;
    int64_t      k, len, length;
    njs_int_t    ret;
    njs_uint_t   i;
    njs_value_t  this, retval, *e;
    njs_array_t  *array, *keys;

    ret = njs_value_to_object(vm, &args[0]);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    /* TODO: ArraySpeciesCreate(). */

    array = njs_array_alloc(vm, 0, 0, NJS_ARRAY_SPARE);
    if (njs_slow_path(array == NULL)) {
        return NJS_ERROR;
    }

    njs_set_array(&this, array);

    len = 0;
    length = 0;

    for (i = 0; i < nargs; i++) {
        e = njs_argument(args, i);

        ret = njs_is_concat_spreadable(vm, e);
        if (njs_slow_path(ret == NJS_ERROR)) {
            return NJS_ERROR;
        }

        if (ret == NJS_OK) {
            ret = njs_object_length(vm, e, &len);
            if (njs_slow_path(ret == NJS_ERROR)) {
                return ret;
            }

            if (njs_slow_path((length + len) > NJS_MAX_LENGTH)) {
                njs_type_error(vm, "Invalid length");
                return NJS_ERROR;
            }

            if (njs_is_fast_array(e) || njs_fast_object(len)) {
                for (k = 0; k < len; k++, length++) {
                    ret = njs_value_property_i64(vm, e, k, &retval);
                    if (njs_slow_path(ret != NJS_OK)) {
                        if (ret == NJS_ERROR) {
                            return NJS_ERROR;
                        }

                        njs_set_invalid(&retval);
                    }

                    ret = njs_value_property_i64_set(vm, &this, length,
                                                     &retval);
                    if (njs_slow_path(ret == NJS_ERROR)) {
                        return ret;
                    }
                }

                continue;
            }

            keys = njs_array_indices(vm, e);
            if (njs_slow_path(keys == NULL)) {
                return NJS_ERROR;
            }

            for (k = 0; k < keys->length; k++) {
                ret = njs_value_property(vm, e, &keys->start[k], &retval);
                if (njs_slow_path(ret == NJS_ERROR)) {
                    return ret;
                }

                if (ret == NJS_OK) {
                    idx = njs_string_to_index(&keys->start[k]) + length;

                    ret = njs_value_property_i64_set(vm, &this, idx, &retval);
                    if (njs_slow_path(ret == NJS_ERROR)) {
                        njs_array_destroy(vm, keys);
                        return ret;
                    }
                }
            }

            njs_array_destroy(vm, keys);

            length += len;

            continue;
        }

        if (njs_slow_path((length + len) >= NJS_MAX_LENGTH)) {
            njs_type_error(vm, "Invalid length");
            return NJS_ERROR;
        }

        ret = njs_value_property_i64_set(vm, &this, length, e);
        if (njs_slow_path(ret == NJS_ERROR)) {
            return ret;
        }

        length++;
    }

    ret = njs_object_length_set(vm, &this, length);
    if (njs_slow_path(ret != NJS_OK)) {
        return NJS_ERROR;
    }

    vm->retval = this;

    return NJS_OK;
}


njs_array_length_set(njs_vm_t *vm, njs_value_t *value,
    njs_object_prop_t *prev, njs_value_t *setval)
{
    double        num, idx;
    int64_t       prev_length;
    uint32_t      i, length;
    njs_int_t     ret;
    njs_array_t   *array, *keys;

    array = njs_object_proto_lookup(njs_object(value), NJS_ARRAY, njs_array_t);
    if (njs_slow_path(array == NULL)) {
        return NJS_DECLINED;
    }

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

    length = (uint32_t) njs_number_to_length(num);
    if ((double) length != num) {
        njs_range_error(vm, "Invalid array length");
        return NJS_ERROR;
    }

    ret = njs_value_to_length(vm, &prev->value, &prev_length);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    keys = NULL;

    if (length < prev_length) {
        keys = njs_array_indices(vm, value);
        if (njs_slow_path(keys == NULL)) {
            return NJS_ERROR;
        }

        if (keys->length != 0) {
            i = keys->length - 1;

            do {
                idx = njs_string_to_index(&keys->start[i]);
                if (idx >= length) {
                    ret = njs_value_property_delete(vm, value, &keys->start[i],
                                                    NULL);
                    if (njs_slow_path(ret == NJS_ERROR)) {
                        goto done;
                    }
                }
            } while (i-- != 0);
        }
    }

    ret = njs_array_length_redefine(vm, value, length);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ret = NJS_OK;

done:

    if (keys != NULL) {
        njs_array_destroy(vm, keys);
    }

    return ret;
}


njs_array_handler_for_each(njs_vm_t *vm, njs_iterator_args_t *args,
    njs_value_t *entry, int64_t n)
{
    if (njs_is_valid(entry)) {
        return njs_array_iterator_call(vm, args, entry, n);
    }

    return NJS_OK;
}


njs_array_prototype_sort(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    int64_t                i, und, len, nlen, length;
    njs_int_t              ret, fast_path;
    njs_array_t            *array;
    njs_value_t            *this, *comparefn, *start, *strings;
    njs_array_sort_ctx_t   ctx;
    njs_array_sort_slot_t  *p, *end, *slots, *nslots;

    comparefn = njs_arg(args, nargs, 1);

    if (njs_is_defined(comparefn)) {
        if (njs_slow_path(!njs_is_function(comparefn))) {
            njs_type_error(vm, "comparefn must be callable or undefined");
            return NJS_ERROR;
        }

        ctx.function = njs_function(comparefn);

    } else {
        ctx.function = NULL;
    }

    this = njs_argument(args, 0);

    ret = njs_value_to_object(vm, this);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

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

    if (njs_slow_path(length < 2)) {
        vm->retval = *this;
        return NJS_OK;
    }

    slots = NULL;
    ctx.vm = vm;
    ctx.strings.separate = 0;
    ctx.strings.pointer = 0;
    ctx.exception = 0;

    fast_path = njs_is_fast_array(this);

    if (njs_fast_path(fast_path)) {
        array = njs_array(this);
        start = array->start;

        slots = njs_mp_alloc(vm->mem_pool,
                             sizeof(njs_array_sort_slot_t) * length);
        if (njs_slow_path(slots == NULL)) {
                return NJS_ERROR;
        }

        und = 0;
        p = slots;

        for (i = 0; i < length; i++) {
            if (njs_slow_path(!njs_is_valid(&start[i]))) {
                fast_path = 0;
                njs_mp_free(vm->mem_pool, slots);
                slots = NULL;
                goto slow_path;
            }

            if (njs_slow_path(njs_is_undefined(&start[i]))) {
                und++;
                continue;
            }

            p->value = start[i];
            p->pos = i;
            p->str = NULL;
            p++;
        }

        len = p - slots;

    } else {

slow_path:

        und = 0;
        p = NULL;
        end = NULL;

        for (i = 0; i < length; i++) {
            if (p >= end) {
                nlen = njs_min(njs_max((p - slots) * 2, 8), length);
                nslots = njs_mp_alloc(vm->mem_pool,
                                      sizeof(njs_array_sort_slot_t) * nlen);
                if (njs_slow_path(nslots == NULL)) {
                    njs_memory_error(vm);
                    return NJS_ERROR;
                }

                if (slots != NULL) {
                    p = (void *) njs_cpymem(nslots, slots,
                                  sizeof(njs_array_sort_slot_t) * (p - slots));
                    njs_mp_free(vm->mem_pool, slots);

                } else {
                    p = nslots;
                }

                slots = nslots;
                end = slots + nlen;
            }

            ret = njs_value_property_i64(vm, this, i, &p->value);
            if (njs_slow_path(ret == NJS_ERROR)) {
                ret = NJS_ERROR;
                goto exception;
            }

            if (ret == NJS_DECLINED) {
                continue;
            }

            if (njs_is_undefined(&p->value)) {
                und++;
                continue;
            }

            p->pos = i;
            p->str = NULL;
            p++;
        }

        len = p - slots;
    }

    strings = njs_arr_init(vm->mem_pool, &ctx.strings, NULL, len + 1,
                           sizeof(njs_value_t));
    if (njs_slow_path(strings == NULL)) {
        ret = NJS_ERROR;
        goto exception;
    }

    njs_qsort(slots, len, sizeof(njs_array_sort_slot_t), njs_array_compare,
              &ctx);

    if (ctx.exception) {
        ret = NJS_ERROR;
        goto exception;
    }

    if (njs_fast_path(fast_path && njs_is_fast_array(this))) {
        array = njs_array(this);
        start = array->start;

        for (i = 0; i < len; i++) {
            start[i] = slots[i].value;
        }

        for (i = len; und-- > 0; i++) {
            start[i] = njs_value_undefined;
        }

    } else {
        for (i = 0; i < len; i++) {
            if (slots[i].pos != i) {
                ret = njs_value_property_i64_set(vm, this, i, &slots[i].value);
                if (njs_slow_path(ret == NJS_ERROR)) {
                    goto exception;
                }
            }
        }

        for (i = len; und-- > 0; i++) {
            ret = njs_value_property_i64_set(vm, this, i,
                                          njs_value_arg(&njs_value_undefined));
            if (njs_slow_path(ret == NJS_ERROR)) {
                goto exception;
            }
        }

        for (; i < length; i++) {
            ret = njs_value_property_i64_delete(vm, this, i, NULL);
            if (njs_slow_path(ret == NJS_ERROR)) {
                goto exception;
            }
        }
    }

    vm->retval = *this;

    ret = NJS_OK;

exception:

    if (slots != NULL) {
        njs_mp_free(vm->mem_pool, slots);
    }

    njs_arr_destroy(&ctx.strings);

    return ret;
}


njs_array_prototype_splice(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    int64_t      i, n, start, length, items, delta, delete;
    njs_int_t    ret;
    njs_value_t  *this, value, del_object;
    njs_array_t  *array, *deleted;

    this = njs_argument(args, 0);

    ret = njs_value_to_object(vm, this);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ret = njs_object_length(vm, this, &length);
    if (njs_slow_path(ret == NJS_ERROR)) {
        return ret;
    }

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

    start = (start < 0) ? njs_max(length + start, 0) : njs_min(start, length);

    items = 0;
    delete = 0;

    if (nargs == 2) {
        delete = length - start;

    } else if (nargs > 2) {
        items = nargs - 3;

        ret = njs_value_to_integer(vm, njs_arg(args, nargs, 2), &delete);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        delete = njs_min(njs_max(delete, 0), length - start);
    }

    delta = items - delete;

    if (njs_slow_path((length + delta) > NJS_MAX_LENGTH)) {
        njs_type_error(vm, "Invalid length");
        return NJS_ERROR;
    }

    /* TODO: ArraySpeciesCreate(). */

    deleted = njs_array_alloc(vm, 0, delete, 0);
    if (njs_slow_path(deleted == NULL)) {
        return NJS_ERROR;
    }

    if (njs_fast_path(njs_is_fast_array(this) && deleted->object.fast_array)) {
        array = njs_array(this);
        for (i = 0, n = start; i < delete; i++, n++) {
            deleted->start[i] = array->start[n];
        }

    } else {
        njs_set_array(&del_object, deleted);

        for (i = 0, n = start; i < delete; i++, n++) {
            ret = njs_value_property_i64(vm, this, n, &value);
            if (njs_slow_path(ret == NJS_ERROR)) {
                return NJS_ERROR;
            }

            if (ret == NJS_OK) {
                /* TODO:  CreateDataPropertyOrThrow(). */
                ret = njs_value_property_i64_set(vm, &del_object, i, &value);
                if (njs_slow_path(ret == NJS_ERROR)) {
                    return ret;
                }

            } else {
                if (deleted->object.fast_array) {
                    njs_set_invalid(&deleted->start[i]);
                }
            }
        }

        ret = njs_object_length_set(vm, &del_object, delete);
        if (njs_slow_path(ret != NJS_OK)) {
            return NJS_ERROR;
        }
    }

    if (njs_fast_path(njs_is_fast_array(this))) {
        array = njs_array(this);

        if (delta != 0) {
            /*
             * Relocate the rest of items.
             * Index of the first item is in "n".
             */
            if (delta > 0) {
                ret = njs_array_expand(vm, array, 0, delta);
                if (njs_slow_path(ret != NJS_OK)) {
                    return ret;
                }
            }

            ret = njs_array_copy_within(vm, this, start + items, start + delete,
                                        array->length - (start + delete), 0);
            if (njs_slow_path(ret != NJS_OK)) {
                return ret;
            }

            array->length += delta;
        }

        /* Copy new items. */

        if (items > 0) {
            memcpy(&array->start[start], &args[3],
                   items * sizeof(njs_value_t));
        }

    } else {

       if (delta != 0) {
           ret = njs_array_copy_within(vm, this, start + items, start + delete,
                                       length - (start + delete), delta < 0);
            if (njs_slow_path(ret != NJS_OK)) {
                return ret;
            }

            for (i = length - 1; i >= length + delta; i--) {
                ret = njs_value_property_i64_delete(vm, this, i, NULL);
                if (njs_slow_path(ret == NJS_ERROR)) {
                    return NJS_ERROR;
                }
            }
       }

        /* Copy new items. */

        for (i = 3, n = start; items-- > 0; i++, n++) {
            ret = njs_value_property_i64_set(vm, this, n, &args[i]);
            if (njs_slow_path(ret == NJS_ERROR)) {
                return NJS_ERROR;
            }
        }

        ret = njs_object_length_set(vm, this, length + delta);
        if (njs_slow_path(ret != NJS_OK)) {
            return NJS_ERROR;
        }
    }

    njs_set_array(&vm->retval, deleted);

    return NJS_OK;
}


njs_array_prototype_reverse_iterator(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t type)
{
    int64_t                 from, length;
    njs_int_t               ret;
    njs_value_t             accumulator;
    njs_iterator_args_t     iargs;
    njs_iterator_handler_t  handler;

    iargs.value = njs_argument(args, 0);

    ret = njs_value_to_object(vm, iargs.value);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    iargs.argument = njs_arg(args, nargs, 1);

    ret = njs_value_length(vm, iargs.value, &length);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    switch (type) {
    case NJS_ARRAY_LAST_INDEX_OF:
        handler = njs_array_handler_index_of;

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

        if (nargs > 2) {
            ret = njs_value_to_integer(vm, njs_arg(args, nargs, 2), &from);
            if (njs_slow_path(ret != NJS_OK)) {
                return ret;
            }

        } else {
            from = length - 1;
        }

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

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

        break;

    case NJS_ARRAY_REDUCE_RIGHT:
    default:
        handler = njs_array_handler_reduce;

        if (njs_slow_path(!njs_is_function(njs_arg(args, nargs, 1)))) {
            njs_type_error(vm, "callback argument is not callable");
            return NJS_ERROR;
        }

        njs_set_invalid(&accumulator);

        iargs.function = njs_function(njs_argument(args, 1));
        iargs.argument = &accumulator;

        if (nargs > 2) {
            accumulator = *njs_argument(args, 2);

        } else if (length == 0) {
            goto done;
        }

        from = length - 1;
        break;
    }

    iargs.from = from;
    iargs.to = 0;

    ret = njs_object_iterate_reverse(vm, &iargs, handler);
    if (njs_fast_path(ret == NJS_ERROR)) {
        return NJS_ERROR;
    }

    if (ret == NJS_DONE) {
        return NJS_OK;
    }

done:

    switch (type) {
    case NJS_ARRAY_LAST_INDEX_OF:
        njs_set_number(&vm->retval, -1);
        break;

    case NJS_ARRAY_REDUCE_RIGHT:
    default:
        if (!njs_is_valid(&accumulator)) {
            njs_type_error(vm, "Reduce of empty object with no initial value");
            return NJS_ERROR;
        }

        vm->retval = accumulator;
        break;
    }

    return NJS_OK;
}


njs_array_prototype_unshift(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    double       idx;
    int64_t      from, to, length;
    njs_int_t    ret;
    njs_uint_t   n;
    njs_array_t  *array, *keys;
    njs_value_t  *this, entry;

    this = njs_argument(args, 0);
    length = 0;
    n = nargs - 1;

    ret = njs_value_to_object(vm, this);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    if (njs_fast_path(njs_is_fast_array(this))) {
        array = njs_array(this);

        if (n != 0) {
            ret = njs_array_expand(vm, array, n, 0);
            if (njs_slow_path(ret != NJS_OK)) {
                return ret;
            }

            array->length += n;
            n = nargs;

            do {
                n--;
                /* GC: njs_retain(&args[n]); */
                array->start--;
                array->start[0] = args[n];
            } while (n > 1);
        }

        njs_set_number(&vm->retval, array->length);

        return NJS_OK;
    }

    ret = njs_object_length(vm, this, &length);
    if (njs_slow_path(ret == NJS_ERROR)) {
        return ret;
    }

    if (n == 0) {
        goto done;
    }

    if (njs_slow_path((length + n) > NJS_MAX_LENGTH)) {
        njs_type_error(vm, "Invalid length");
        return NJS_ERROR;
    }

    if (!njs_fast_object(length)) {
        keys = njs_array_indices(vm, this);
        if (njs_slow_path(keys == NULL)) {
            return NJS_ERROR;
        }

        from = keys->length;

        while (from > 0) {
            ret = njs_value_property_delete(vm, this, &keys->start[--from],
                                            &entry);
            if (njs_slow_path(ret == NJS_ERROR)) {
                njs_array_destroy(vm, keys);
                return ret;
            }

            if (ret == NJS_OK) {
                idx = njs_string_to_index(&keys->start[from]) + n;

                ret = njs_value_property_i64_set(vm, this, idx, &entry);
                if (njs_slow_path(ret == NJS_ERROR)) {
                    njs_array_destroy(vm, keys);
                    return ret;
                }
            }
        }

        njs_array_destroy(vm, keys);

        length += n;

        goto copy;
    }

    from = length;
    length += n;
    to = length;

    while (from > 0) {
        ret = njs_value_property_i64_delete(vm, this, --from, &entry);
        if (njs_slow_path(ret == NJS_ERROR)) {
            return ret;
        }

        to--;

        if (ret == NJS_OK) {
            ret = njs_value_property_i64_set(vm, this, to, &entry);
            if (njs_slow_path(ret == NJS_ERROR)) {
                return ret;
            }
        }
    }

copy:

    for (n = 1; n < nargs; n++) {
        ret = njs_value_property_i64_set(vm, this, n - 1, &args[n]);
        if (njs_slow_path(ret == NJS_ERROR)) {
            return ret;
        }
    }

done:

    ret = njs_object_length_set(vm, this, length);
    if (njs_slow_path(ret == NJS_ERROR)) {
        return ret;
    }

    njs_set_number(&vm->retval, length);

    return NJS_OK;
}


njs_array_prototype_push(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    int64_t      length;
    njs_int_t    ret;
    njs_uint_t   i;
    njs_array_t  *array;
    njs_value_t  *this;

    length = 0;
    this = njs_argument(args, 0);

    ret = njs_value_to_object(vm, this);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

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

        if (nargs != 0) {
            ret = njs_array_expand(vm, array, 0, nargs);
            if (njs_slow_path(ret != NJS_OK)) {
                return ret;
            }

            for (i = 1; i < nargs; i++) {
                /* GC: njs_retain(&args[i]); */
                array->start[array->length++] = args[i];
            }
        }

        njs_set_number(&vm->retval, array->length);

        return NJS_OK;
    }

    ret = njs_object_length(vm, this, &length);
    if (njs_slow_path(ret == NJS_ERROR)) {
        return ret;
    }

    if (njs_slow_path((length + nargs - 1) > NJS_MAX_LENGTH)) {
        njs_type_error(vm, "Invalid length");
        return NJS_ERROR;
    }

    for (i = 1; i < nargs; i++) {
        ret = njs_value_property_i64_set(vm, this, length++, &args[i]);
        if (njs_slow_path(ret == NJS_ERROR)) {
            return ret;
        }
    }

    ret = njs_object_length_set(vm, this, length);
    if (njs_slow_path(ret == NJS_ERROR)) {
        return ret;
    }

    njs_set_number(&vm->retval, length);

    return NJS_OK;
}


njs_array_indices(njs_vm_t *vm, njs_value_t *object)
{
    double       idx;
    uint32_t     i;
    njs_array_t  *keys;

    keys = njs_array_keys(vm, object, 1);
    if (njs_slow_path(keys == NULL)) {
        return NULL;
    }

    for (i = 0; i < keys->length; i++) {
        idx = njs_string_to_index(&keys->start[i]);

        if (isnan(idx)) {
            keys->length = i;
            break;
        }
    }

    return keys;
}


njs_array_handler_find(njs_vm_t *vm, njs_iterator_args_t *args,
    njs_value_t *entry, int64_t n)
{
    njs_int_t    ret;
    njs_value_t  copy;

    if (njs_is_valid(entry)) {
        copy = *entry;

    } else {
        njs_set_undefined(&copy);
    }

    ret = njs_array_iterator_call(vm, args, &copy, n);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    if (njs_is_true(&vm->retval)) {
        vm->retval = copy;

        return NJS_DONE;
    }

    return NJS_OK;
}


njs_array_handler_find_index(njs_vm_t *vm, njs_iterator_args_t *args,
    njs_value_t *entry, int64_t n)
{
    njs_int_t    ret;
    njs_value_t  copy;

    if (njs_is_valid(entry)) {
        copy = *entry;

    } else {
        njs_set_undefined(&copy);
    }

    ret = njs_array_iterator_call(vm, args, &copy, n);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    if (njs_is_true(&vm->retval)) {
        njs_set_number(&vm->retval, n);

        return NJS_DONE;
    }

    return NJS_OK;
}


njs_array_handler_reduce(njs_vm_t *vm, njs_iterator_args_t *args,
    njs_value_t *entry, int64_t n)
{
    njs_int_t    ret;
    njs_value_t  arguments[5];

    if (njs_is_valid(entry)) {
        if (!njs_is_valid(args->argument)) {
            *(args->argument) = *entry;
            return NJS_OK;
        }

        /* GC: array elt, array */

        njs_set_undefined(&arguments[0]);
        arguments[1] = *args->argument;
        arguments[2] = *entry;
        njs_set_number(&arguments[3], n);
        arguments[4] = *args->value;

        ret =  njs_function_apply(vm, args->function, arguments, 5,
                                  args->argument);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    return NJS_OK;
}


njs_array_prototype_iterator_obj(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t kind)
{
    njs_int_t    ret;
    njs_value_t  *this;

    this = njs_argument(args, 0);

    ret = njs_value_to_object(vm, this);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_array_iterator_create(vm, this, &vm->retval, kind);
}


njs_is_concat_spreadable(njs_vm_t *vm, njs_value_t *value)
{
    njs_int_t    ret;
    njs_value_t  retval;

    static const njs_value_t  key =
                         njs_wellknown_symbol(NJS_SYMBOL_IS_CONCAT_SPREADABLE);

    if (njs_slow_path(!njs_is_object(value))) {
        return NJS_DECLINED;
    }

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

    if (njs_is_defined(&retval)) {
        return njs_bool(&retval) ? NJS_OK : NJS_DECLINED;
    }

    return njs_is_array(value) ? NJS_OK : NJS_DECLINED;
}


njs_array_alloc(njs_vm_t *vm, njs_bool_t flat, uint64_t length, uint32_t spare)
{
    uint64_t     size;
    njs_int_t    ret;
    njs_array_t  *array;
    njs_value_t  value;

    if (njs_slow_path(length > UINT32_MAX)) {
        goto overflow;
    }

    array = njs_mp_alloc(vm->mem_pool, sizeof(njs_array_t));
    if (njs_slow_path(array == NULL)) {
        goto memory_error;
    }

    size = length + spare;

    if (flat || size <= NJS_ARRAY_LARGE_OBJECT_LENGTH) {
        array->data = njs_mp_align(vm->mem_pool, sizeof(njs_value_t),
                                   size * sizeof(njs_value_t));
        if (njs_slow_path(array->data == NULL)) {
            goto memory_error;
        }

    } else {
        array->data = NULL;
    }

    array->start = array->data;
    njs_lvlhsh_init(&array->object.hash);
    array->object.shared_hash = vm->shared->array_instance_hash;
    array->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_ARRAY].object;
    array->object.slots = NULL;
    array->object.type = NJS_ARRAY;
    array->object.shared = 0;
    array->object.extensible = 1;
    array->object.error_data = 0;
    array->object.fast_array = (array->data != NULL);

    if (njs_fast_path(array->object.fast_array)) {
        array->size = size;
        array->length = length;

    } else {
        array->size = 0;
        array->length = 0;

        njs_set_array(&value, array);
        ret = njs_array_length_redefine(vm, &value, length);
        if (njs_slow_path(ret != NJS_OK)) {
            return NULL;
        }
    }

    return array;

memory_error:

    njs_memory_error(vm);

    return NULL;

overflow:

    njs_range_error(vm, "Invalid array length");

    return NULL;
}


njs_array_prototype_to_string(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    njs_int_t           ret;
    njs_value_t         value;
    njs_lvlhsh_query_t  lhq;

    static const njs_value_t  join_string = njs_string("join");

    if (njs_is_object(&args[0])) {
        njs_object_property_init(&lhq, &join_string, NJS_JOIN_HASH);

        ret = njs_object_property(vm, &args[0], &lhq, &value);

        if (njs_slow_path(ret == NJS_ERROR)) {
            return ret;
        }

        if (njs_is_function(&value)) {
            return njs_function_apply(vm, njs_function(&value), args, nargs,
                                      &vm->retval);
        }
    }

    return njs_object_prototype_to_string(vm, args, nargs, unused);
}


njs_array_keys(njs_vm_t *vm, njs_value_t *object, njs_bool_t all)
{
    njs_array_t  *keys;

    keys = njs_value_own_enumerate(vm, object, NJS_ENUM_KEYS, NJS_ENUM_STRING,
                                   all);
    if (njs_slow_path(keys == NULL)) {
        return NULL;
    }

    njs_qsort(keys->start, keys->length, sizeof(njs_value_t),
              njs_array_indices_handler, NULL);

    return keys;
}


PJ_DEF(pj_xml_node*) pj_xml_parse( pj_pool_t *pool, char *msg, pj_size_t len)
{
    pj_xml_node *node = NULL;
    pj_scanner scanner;
    PJ_USE_EXCEPTION;

    if (!msg || !len || !pool)
	return NULL;

    pj_scan_init( &scanner, msg, len,
		  PJ_SCAN_AUTOSKIP_WS|PJ_SCAN_AUTOSKIP_NEWLINE,
		  &on_syntax_error);
    PJ_TRY {
	node =  xml_parse_node(pool, &scanner);
    }
    PJ_CATCH_ANY {
	PJ_LOG(4,(THIS_FILE, "Syntax error parsing XML in line %d column %d",
		  scanner.line, pj_scan_get_col(&scanner)));
    }
    PJ_END;
    pj_scan_fini( &scanner );
    return node;
}

Merge pull request from GHSA-5x45-qp78-g4p4

* Prevent infinite loop in scanning xml content

* Simplify scanning method

* Optimization


PJ_DEF(int) pj_xml_print(const pj_xml_node *node, char *buf, pj_size_t len,
			 pj_bool_t include_prolog)
{
    int prolog_len = 0;
    int printed;

    if (!node || !buf || !len)
	return 0;

    if (include_prolog) {
	pj_str_t prolog = {"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", 39};
	if ((int)len < prolog.slen)
	    return -1;
	pj_memcpy(buf, prolog.ptr, prolog.slen);
	prolog_len = (int)prolog.slen;
    }

    printed = xml_print_node(node, 0, buf+prolog_len, len-prolog_len) + prolog_len;
    if (printed > 0 && len-printed >= 1) {
	buf[printed++] = '\n';
    }
    return printed;
}


static pj_xml_attr *alloc_attr( pj_pool_t *pool )
{
    return PJ_POOL_ZALLOC_T(pool, pj_xml_attr);
}


PJ_DEF(pj_xml_node*) pj_xml_clone( pj_pool_t *pool, const pj_xml_node *rhs)
{
    pj_xml_node *node;
    const pj_xml_attr *r_attr;
    const pj_xml_node *child;

    node = alloc_node(pool);

    pj_strdup(pool, &node->name, &rhs->name);
    pj_strdup(pool, &node->content, &rhs->content);

    /* Clone all attributes */
    r_attr = rhs->attr_head.next;
    while (r_attr != &rhs->attr_head) {

	pj_xml_attr *attr;

	attr = alloc_attr(pool);
	pj_strdup(pool, &attr->name, &r_attr->name);
	pj_strdup(pool, &attr->value, &r_attr->value);

	pj_list_push_back(&node->attr_head, attr);

	r_attr = r_attr->next;
    }

    /* Clone all child nodes. */
    child = rhs->node_head.next;
    while (child != (pj_xml_node*) &rhs->node_head) {
	pj_xml_node *new_child;

	new_child = pj_xml_clone(pool, child);
	pj_list_push_back(&node->node_head, new_child);

	child = child->next;
    }

    return node;
}


PJ_DEF(void) pj_xml_add_attr( pj_xml_node *node, pj_xml_attr *attr )
{
    pj_list_push_back(&node->attr_head, attr);
}


PJ_DEF(pj_xml_node*) pj_xml_find_node_rec(const pj_xml_node *parent,
					  const pj_str_t *name)
{
    const pj_xml_node *node = parent->node_head.next;

    PJ_CHECK_STACK();

    while (node != (void*)&parent->node_head) {
	pj_xml_node *found;
	if (pj_stricmp(&node->name, name) == 0)
	    return (pj_xml_node*)node;
	found = pj_xml_find_node_rec(node, name);
	if (found)
	    return (pj_xml_node*)found;
	node = node->next;
    }
    return NULL;
}


PJ_DEF(pj_xml_node*) pj_xml_node_new(pj_pool_t *pool, const pj_str_t *name)
{
    pj_xml_node *node = alloc_node(pool);
    pj_strdup(pool, &node->name, name);
    return node;
}


PJ_DEF(pj_xml_node*) pj_xml_find_next_node( const pj_xml_node *parent,
					    const pj_xml_node *node,
					    const pj_str_t *name)
{
    PJ_CHECK_STACK();

    node = node->next;
    while (node != (void*)&parent->node_head) {
	if (pj_stricmp(&node->name, name) == 0)
	    return (pj_xml_node*)node;
	node = node->next;
    }
    return NULL;
}


static void on_syntax_error(struct pj_scanner *scanner)
{
    PJ_UNUSED_ARG(scanner);
    PJ_THROW(EX_SYNTAX_ERROR);
}


PJ_DEF(pj_xml_node*) pj_xml_find_node(const pj_xml_node *parent,
				      const pj_str_t *name)
{
    const pj_xml_node *node = parent->node_head.next;

    PJ_CHECK_STACK();

    while (node != (void*)&parent->node_head) {
	if (pj_stricmp(&node->name, name) == 0)
	    return (pj_xml_node*)node;
	node = node->next;
    }
    return NULL;
}


PJ_DEF(pj_xml_attr*) pj_xml_find_attr( const pj_xml_node *node,
				       const pj_str_t *name,
				       const pj_str_t *value)
{
    const pj_xml_attr *attr = node->attr_head.next;
    while (attr != (void*)&node->attr_head) {
	if (pj_stricmp(&attr->name, name)==0) {
	    if (value) {
		if (pj_stricmp(&attr->value, value)==0)
		    return (pj_xml_attr*)attr;
	    } else {
		return (pj_xml_attr*)attr;
	    }
	}
	attr = attr->next;
    }
    return NULL;
}


PJ_DEF(void) pj_xml_add_node( pj_xml_node *parent, pj_xml_node *node )
{
    pj_list_push_back(&parent->node_head, node);
}


static int xml_print_node( const pj_xml_node *node, int indent,
			   char *buf, pj_size_t len )
{
    int i;
    char *p = buf;
    pj_xml_attr *attr;
    pj_xml_node *sub_node;

#define SIZE_LEFT()	((int)(len - (p-buf)))

    PJ_CHECK_STACK();

    /* Print name. */
    if (SIZE_LEFT() < node->name.slen + indent + 5)
	return -1;
    for (i=0; i<indent; ++i)
	*p++ = ' ';
    *p++ = '<';
    pj_memcpy(p, node->name.ptr, node->name.slen);
    p += node->name.slen;

    /* Print attributes. */
    attr = node->attr_head.next;
    while (attr != &node->attr_head) {

	if (SIZE_LEFT() < attr->name.slen + attr->value.slen + 4)
	    return -1;

	*p++ = ' ';

	/* Attribute name. */
	pj_memcpy(p, attr->name.ptr, attr->name.slen);
	p += attr->name.slen;

	/* Attribute value. */
	if (attr->value.slen) {
	    *p++ = '=';
	    *p++ = '"';
	    pj_memcpy(p, attr->value.ptr, attr->value.slen);
	    p += attr->value.slen;
	    *p++ = '"';
	}

	attr = attr->next;
    }

    /* Check for empty node. */
    if (node->content.slen==0 &&
	node->node_head.next==(pj_xml_node*)&node->node_head)
    {
        if (SIZE_LEFT() < 3) return -1;
	*p++ = ' ';
	*p++ = '/';
	*p++ = '>';
	return (int)(p-buf);
    }

    /* Enclosing '>' */
    if (SIZE_LEFT() < 1) return -1;
    *p++ = '>';

    /* Print sub nodes. */
    sub_node = node->node_head.next;
    while (sub_node != (pj_xml_node*)&node->node_head) {
	int printed;

	if (SIZE_LEFT() < indent + 3)
	    return -1;
	//*p++ = '\r';
	*p++ = '\n';

	printed = xml_print_node(sub_node, indent + 1, p, SIZE_LEFT());
	if (printed < 0)
	    return -1;

	p += printed;
	sub_node = sub_node->next;
    }

    /* Content. */
    if (node->content.slen) {
	if (SIZE_LEFT() < node->content.slen) return -1;
	pj_memcpy(p, node->content.ptr, node->content.slen);
	p += node->content.slen;
    }

    /* Enclosing node. */
    if (node->node_head.next != (pj_xml_node*)&node->node_head) {
	if (SIZE_LEFT() < node->name.slen + 5 + indent)
	    return -1;
	//*p++ = '\r';
	*p++ = '\n';
	for (i=0; i<indent; ++i)
	    *p++ = ' ';
    } else {
	if (SIZE_LEFT() < node->name.slen + 3)
	    return -1;
    }
    *p++ = '<';
    *p++ = '/';
    pj_memcpy(p, node->name.ptr, node->name.slen);
    p += node->name.slen;
    *p++ = '>';

#undef SIZE_LEFT

    return (int)(p-buf);
}


PJ_DEF(pj_xml_node*) pj_xml_find( const pj_xml_node *parent,
				  const pj_str_t *name,
				  const void *data,
				  pj_bool_t (*match)(const pj_xml_node *,
						     const void*))
{
    const pj_xml_node *node = (const pj_xml_node *)parent->node_head.next;

    if (!name && !match)
	return NULL;

    while (node != (const pj_xml_node*) &parent->node_head) {
	if (name) {
	    if (pj_stricmp(&node->name, name)!=0) {
		node = node->next;
		continue;
	    }
	}
	if (match) {
	    if (match(node, data))
		return (pj_xml_node*)node;
	} else {
	    return (pj_xml_node*)node;
	}

	node = node->next;
    }
    return NULL;
}


static pj_xml_node *alloc_node( pj_pool_t *pool )
{
    pj_xml_node *node;

    node = PJ_POOL_ZALLOC_T(pool, pj_xml_node);
    pj_list_init( &node->attr_head );
    pj_list_init( &node->node_head );

    return node;
}


PJ_DEF(pj_xml_node*) pj_xml_find_rec( const pj_xml_node *parent,
				      const pj_str_t *name,
				      const void *data,
				      pj_bool_t (*match)(const pj_xml_node*,
							 const void*))
{
    const pj_xml_node *node = (const pj_xml_node *)parent->node_head.next;

    if (!name && !match)
	return NULL;

    while (node != (const pj_xml_node*) &parent->node_head) {
	pj_xml_node *found;

	if (name) {
	    if (pj_stricmp(&node->name, name)==0) {
		if (match) {
		    if (match(node, data))
			return (pj_xml_node*)node;
		} else {
		    return (pj_xml_node*)node;
		}
	    }

	} else if (match) {
	    if (match(node, data))
		return (pj_xml_node*)node;
	}

	found = pj_xml_find_rec(node, name, data, match);
	if (found)
	    return found;

	node = node->next;
    }
    return NULL;
}


static pj_xml_node *xml_parse_node( pj_pool_t *pool, pj_scanner *scanner)
{
    pj_xml_node *node;
    pj_str_t end_name;

    PJ_CHECK_STACK();

    if (*scanner->curptr != '<')
	on_syntax_error(scanner);

    /* Handle Processing Instructino (PI) construct (i.e. "<?") */
    if (*scanner->curptr == '<' && *(scanner->curptr+1) == '?') {
	pj_scan_advance_n(scanner, 2, PJ_FALSE);
	for (;;) {
	    pj_str_t dummy;
	    pj_scan_get_until_ch(scanner, '?', &dummy);
	    if (*scanner->curptr=='?' && *(scanner->curptr+1)=='>') {
		pj_scan_advance_n(scanner, 2, PJ_TRUE);
		break;
	    } else {
		pj_scan_advance_n(scanner, 1, PJ_FALSE);
	    }
	}
	return xml_parse_node(pool, scanner);
    }

    /* Handle comments construct (i.e. "<!") */
    if (pj_scan_strcmp(scanner, "<!", 2) == 0) {
	pj_scan_advance_n(scanner, 2, PJ_FALSE);
	for (;;) {
	    pj_str_t dummy;
	    pj_scan_get_until_ch(scanner, '>', &dummy);
	    if (pj_scan_strcmp(scanner, ">", 1) == 0) {
		pj_scan_advance_n(scanner, 1, PJ_TRUE);
		break;
	    } else {
		pj_scan_advance_n(scanner, 1, PJ_FALSE);
	    }
	}
	return xml_parse_node(pool, scanner);
    }

    /* Alloc node. */
    node = alloc_node(pool);

    /* Get '<' */
    pj_scan_get_char(scanner);

    /* Get node name. */
    pj_scan_get_until_chr( scanner, " />\t\r\n", &node->name);

    /* Get attributes. */
    while (*scanner->curptr != '>' && *scanner->curptr != '/') {
	pj_xml_attr *attr = alloc_attr(pool);

	pj_scan_get_until_chr( scanner, "=> \t\r\n", &attr->name);
	if (*scanner->curptr == '=') {
	    pj_scan_get_char( scanner );
            pj_scan_get_quotes(scanner, "\"'", "\"'", 2, &attr->value);
	    /* remove quote characters */
	    ++attr->value.ptr;
	    attr->value.slen -= 2;
	}

	pj_list_push_back( &node->attr_head, attr );
    }

    if (*scanner->curptr == '/') {
	pj_scan_get_char(scanner);
	if (pj_scan_get_char(scanner) != '>')
	    on_syntax_error(scanner);
	return node;
    }

    /* Enclosing bracket. */
    if (pj_scan_get_char(scanner) != '>')
	on_syntax_error(scanner);

    /* Sub nodes. */
    while (*scanner->curptr == '<' && *(scanner->curptr+1) != '/'
				   && *(scanner->curptr+1) != '!')
    {
	pj_xml_node *sub_node = xml_parse_node(pool, scanner);
	pj_list_push_back( &node->node_head, sub_node );
    }

    /* Content. */
    if (!pj_scan_is_eof(scanner) && *scanner->curptr != '<') {
	pj_scan_get_until_ch(scanner, '<', &node->content);
    }

    /* CDATA content. */
    if (*scanner->curptr == '<' && *(scanner->curptr+1) == '!' &&
	pj_scan_strcmp(scanner, "<![CDATA[", 9) == 0)
    {
	pj_scan_advance_n(scanner, 9, PJ_FALSE);
	pj_scan_get_until_ch(scanner, ']', &node->content);
	while (pj_scan_strcmp(scanner, "]]>", 3)) {
	    pj_str_t dummy;

	    pj_scan_advance_n(scanner, 1, PJ_FALSE);
	    pj_scan_get_until_ch(scanner, ']', &dummy);
	}
	node->content.slen = scanner->curptr - node->content.ptr;
	pj_scan_advance_n(scanner, 3, PJ_TRUE);
    }

    /* Enclosing node. */
    if (pj_scan_get_char(scanner) != '<' || pj_scan_get_char(scanner) != '/')
	on_syntax_error(scanner);

    pj_scan_get_until_chr(scanner, " \t>", &end_name);

    /* Compare name. */
    if (pj_stricmp(&node->name, &end_name) != 0)
	on_syntax_error(scanner);

    /* Enclosing '>' */
    if (pj_scan_get_char(scanner) != '>')
	on_syntax_error(scanner);

    return node;
}


PJ_DEF(pj_xml_attr*) pj_xml_attr_new( pj_pool_t *pool, const pj_str_t *name,
				      const pj_str_t *value)
{
    pj_xml_attr *attr = alloc_attr(pool);
    pj_strdup( pool, &attr->name, name);
    pj_strdup( pool, &attr->value, value);
    return attr;
}


rs_input(void)
{

  LOG_INFO("Received RS from ");
  LOG_INFO_6ADDR(&UIP_IP_BUF->srcipaddr);
  LOG_INFO_(" to ");
  LOG_INFO_6ADDR(&UIP_IP_BUF->destipaddr);
  LOG_INFO_("\n");
  UIP_STAT(++uip_stat.nd6.recv);

#if UIP_CONF_IPV6_CHECKS
  /*
   * Check hop limit / icmp code
   * target address must not be multicast
   * if the NA is solicited, dest must not be multicast
   */
  if((UIP_IP_BUF->ttl != UIP_ND6_HOP_LIMIT) || (UIP_ICMP_BUF->icode != 0)) {
    LOG_ERR("RS received is bad\n");
    goto discard;
  }
#endif /*UIP_CONF_IPV6_CHECKS */

  /* Only valid option is Source Link-Layer Address option any thing
     else is discarded */
  nd6_opt_offset = UIP_ND6_RS_LEN;
  nd6_opt_llao = NULL;

  while(uip_l3_icmp_hdr_len + nd6_opt_offset < uip_len) {
#if UIP_CONF_IPV6_CHECKS
    if(ND6_OPT_HDR_BUF(nd6_opt_offset)->len == 0) {
      LOG_ERR("RS received is bad\n");
      goto discard;
    }
#endif /*UIP_CONF_IPV6_CHECKS */
    switch (ND6_OPT_HDR_BUF(nd6_opt_offset)->type) {
    case UIP_ND6_OPT_SLLAO:
      nd6_opt_llao = (uint8_t *)ND6_OPT_HDR_BUF(nd6_opt_offset);
      break;
    default:
      LOG_WARN("ND option not supported in RS\n");
      break;
    }
    nd6_opt_offset += (ND6_OPT_HDR_BUF(nd6_opt_offset)->len << 3);
  }
  /* Options processing: only SLLAO */
  if(nd6_opt_llao != NULL) {
#if UIP_CONF_IPV6_CHECKS
    if(uip_is_addr_unspecified(&UIP_IP_BUF->srcipaddr)) {
      LOG_ERR("RS received is bad\n");
      goto discard;
    } else {
#endif /*UIP_CONF_IPV6_CHECKS */
      uip_lladdr_t lladdr_aligned;
      extract_lladdr_from_llao_aligned(&lladdr_aligned);
      if((nbr = uip_ds6_nbr_lookup(&UIP_IP_BUF->srcipaddr)) == NULL) {
        /* we need to add the neighbor */
        uip_ds6_nbr_add(&UIP_IP_BUF->srcipaddr, &lladdr_aligned,
                        0, NBR_STALE, NBR_TABLE_REASON_IPV6_ND, NULL);
      } else {
        /* If LL address changed, set neighbor state to stale */
        const uip_lladdr_t *lladdr = uip_ds6_nbr_get_ll(nbr);
        if(lladdr == NULL) {
          goto discard;
        }
        if(memcmp(&nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET],
            lladdr, UIP_LLADDR_LEN) != 0) {
          uip_ds6_nbr_t nbr_data;
          nbr_data = *nbr;
          uip_ds6_nbr_rm(nbr);
          nbr = uip_ds6_nbr_add(&UIP_IP_BUF->srcipaddr, &lladdr_aligned,
                                0, NBR_STALE, NBR_TABLE_REASON_IPV6_ND, NULL);
          nbr->reachable = nbr_data.reachable;
          nbr->sendns = nbr_data.sendns;
          nbr->nscount = nbr_data.nscount;
        }
        nbr->isrouter = 0;
      }
#if UIP_CONF_IPV6_CHECKS
    }
#endif /*UIP_CONF_IPV6_CHECKS */
  }

  /* Schedule a sollicited RA */
  uip_ds6_send_ra_sollicited();

discard:
  uipbuf_clear();
  return;
}

Check whether there is enough space for ND6 option headers when processing
incoming packets.


ra_input(void)
{
  uip_lladdr_t lladdr_aligned;

  LOG_INFO("Received RA from ");
  LOG_INFO_6ADDR(&UIP_IP_BUF->srcipaddr);
  LOG_INFO_(" to ");
  LOG_INFO_6ADDR(&UIP_IP_BUF->destipaddr);
  LOG_INFO_("\n");
  UIP_STAT(++uip_stat.nd6.recv);

#if UIP_CONF_IPV6_CHECKS
  if((UIP_IP_BUF->ttl != UIP_ND6_HOP_LIMIT) ||
     (!uip_is_addr_linklocal(&UIP_IP_BUF->srcipaddr)) ||
     (UIP_ICMP_BUF->icode != 0)) {
    LOG_ERR("RA received is bad");
    goto discard;
  }
#endif /*UIP_CONF_IPV6_CHECKS */

  if(UIP_ND6_RA_BUF->cur_ttl != 0) {
    uip_ds6_if.cur_hop_limit = UIP_ND6_RA_BUF->cur_ttl;
    LOG_INFO("uip_ds6_if.cur_hop_limit %u\n", uip_ds6_if.cur_hop_limit);
  }

  if(UIP_ND6_RA_BUF->reachable_time != 0) {
    if(uip_ds6_if.base_reachable_time !=
       uip_ntohl(UIP_ND6_RA_BUF->reachable_time)) {
      uip_ds6_if.base_reachable_time = uip_ntohl(UIP_ND6_RA_BUF->reachable_time);
      uip_ds6_if.reachable_time = uip_ds6_compute_reachable_time();
    }
  }
  if(UIP_ND6_RA_BUF->retrans_timer != 0) {
    uip_ds6_if.retrans_timer = uip_ntohl(UIP_ND6_RA_BUF->retrans_timer);
  }

  /* Options processing */
  nd6_opt_offset = UIP_ND6_RA_LEN;
  while(uip_l3_icmp_hdr_len + nd6_opt_offset < uip_len) {
    if(ND6_OPT_HDR_BUF(nd6_opt_offset)->len == 0) {
      LOG_ERR("RA received is bad");
      goto discard;
    }
    switch (ND6_OPT_HDR_BUF(nd6_opt_offset)->type) {
    case UIP_ND6_OPT_SLLAO:
      LOG_DBG("Processing SLLAO option in RA\n");
      nd6_opt_llao = (uint8_t *) ND6_OPT_HDR_BUF(nd6_opt_offset);
      nbr = uip_ds6_nbr_lookup(&UIP_IP_BUF->srcipaddr);
      if(!extract_lladdr_from_llao_aligned(&lladdr_aligned)) {
        /* failed to extract llao - discard packet */
        goto discard;
      }
      if(nbr == NULL) {
        nbr = uip_ds6_nbr_add(&UIP_IP_BUF->srcipaddr, &lladdr_aligned,
                              1, NBR_STALE, NBR_TABLE_REASON_IPV6_ND, NULL);
      } else {
        const uip_lladdr_t *lladdr = uip_ds6_nbr_get_ll(nbr);
        if(lladdr == NULL) {
          goto discard;
        }
        if(nbr->state == NBR_INCOMPLETE) {
          nbr->state = NBR_STALE;
        }
        if(memcmp(&nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET],
                  lladdr, UIP_LLADDR_LEN) != 0) {
          /* change of link layer address */
          if(uip_ds6_nbr_update_ll(&nbr,
                                   (const uip_lladdr_t *)&lladdr_aligned) < 0) {
            /* failed to update the lladdr */
            goto discard;
          }
          nbr->state = NBR_STALE;
        }
        nbr->isrouter = 1;
      }
      break;
    case UIP_ND6_OPT_MTU:
      LOG_DBG("Processing MTU option in RA\n");
      uip_ds6_if.link_mtu =
        uip_ntohl(((uip_nd6_opt_mtu *) ND6_OPT_HDR_BUF(nd6_opt_offset))->mtu);
      break;
    case UIP_ND6_OPT_PREFIX_INFO:
      LOG_DBG("Processing PREFIX option in RA\n");
      nd6_opt_prefix_info = (uip_nd6_opt_prefix_info *) ND6_OPT_HDR_BUF(nd6_opt_offset);
      if((uip_ntohl(nd6_opt_prefix_info->validlt) >=
          uip_ntohl(nd6_opt_prefix_info->preferredlt))
         && (!uip_is_addr_linklocal(&nd6_opt_prefix_info->prefix))) {
        /* on-link flag related processing */
        if(nd6_opt_prefix_info->flagsreserved1 & UIP_ND6_RA_FLAG_ONLINK) {
          prefix =
            uip_ds6_prefix_lookup(&nd6_opt_prefix_info->prefix,
                                  nd6_opt_prefix_info->preflen);
          if(prefix == NULL) {
            if(nd6_opt_prefix_info->validlt != 0) {
              if(nd6_opt_prefix_info->validlt != UIP_ND6_INFINITE_LIFETIME) {
                prefix = uip_ds6_prefix_add(&nd6_opt_prefix_info->prefix,
                                            nd6_opt_prefix_info->preflen,
                                            uip_ntohl(nd6_opt_prefix_info->
                                                  validlt));
              } else {
                prefix = uip_ds6_prefix_add(&nd6_opt_prefix_info->prefix,
                                            nd6_opt_prefix_info->preflen, 0);
              }
            }
          } else {
            switch (nd6_opt_prefix_info->validlt) {
            case 0:
              uip_ds6_prefix_rm(prefix);
              break;
            case UIP_ND6_INFINITE_LIFETIME:
              prefix->isinfinite = 1;
              break;
            default:
              LOG_DBG("Updating timer of prefix ");
              LOG_DBG_6ADDR(&prefix->ipaddr);
              LOG_DBG_(" new value %"PRIu32"\n", uip_ntohl(nd6_opt_prefix_info->validlt));
              stimer_set(&prefix->vlifetime,
                         uip_ntohl(nd6_opt_prefix_info->validlt));
              prefix->isinfinite = 0;
              break;
            }
          }
        }
        /* End of on-link flag related processing */
        /* autonomous flag related processing */
        if((nd6_opt_prefix_info->flagsreserved1 & UIP_ND6_RA_FLAG_AUTONOMOUS)
           && (nd6_opt_prefix_info->validlt != 0)
           && (nd6_opt_prefix_info->preflen == UIP_DEFAULT_PREFIX_LEN)) {

          uip_ipaddr_copy(&ipaddr, &nd6_opt_prefix_info->prefix);
          uip_ds6_set_addr_iid(&ipaddr, &uip_lladdr);
          addr = uip_ds6_addr_lookup(&ipaddr);
          if((addr != NULL) && (addr->type == ADDR_AUTOCONF)) {
            if(nd6_opt_prefix_info->validlt != UIP_ND6_INFINITE_LIFETIME) {
              /* The processing below is defined in RFC4862 section 5.5.3 e */
              if((uip_ntohl(nd6_opt_prefix_info->validlt) > 2 * 60 * 60) ||
                 (uip_ntohl(nd6_opt_prefix_info->validlt) >
                  stimer_remaining(&addr->vlifetime))) {
                LOG_DBG("Updating timer of address ");
                LOG_DBG_6ADDR(&addr->ipaddr);
                LOG_DBG_(" new value %lu\n",
                       (unsigned long)uip_ntohl(nd6_opt_prefix_info->validlt));
                stimer_set(&addr->vlifetime,
                           uip_ntohl(nd6_opt_prefix_info->validlt));
              } else {
                stimer_set(&addr->vlifetime, 2 * 60 * 60);
                LOG_DBG("Updating timer of address ");
                LOG_DBG_6ADDR(&addr->ipaddr);
                LOG_DBG_(" new value %lu\n", (unsigned long)(2 * 60 * 60));
              }
              addr->isinfinite = 0;
            } else {
              addr->isinfinite = 1;
            }
          } else {
            if(uip_ntohl(nd6_opt_prefix_info->validlt) ==
               UIP_ND6_INFINITE_LIFETIME) {
              uip_ds6_addr_add(&ipaddr, 0, ADDR_AUTOCONF);
            } else {
              uip_ds6_addr_add(&ipaddr, uip_ntohl(nd6_opt_prefix_info->validlt),
                               ADDR_AUTOCONF);
            }
          }
        }
        /* End of autonomous flag related processing */
      }
      break;
#if UIP_ND6_RA_RDNSS
    case UIP_ND6_OPT_RDNSS:
      LOG_DBG("Processing RDNSS option\n");
      uint8_t naddr = (ND6_OPT_RDNSS_BUF(nd6_opt_offset)->len - 1) / 2;
      uip_ipaddr_t *ip = (uip_ipaddr_t *)(&ND6_OPT_RDNSS_BUF(nd6_opt_offset)->ip);
      LOG_DBG("got %d nameservers\n", naddr);
      while(naddr-- > 0) {
        LOG_DBG("nameserver: ");
        LOG_DBG_6ADDR(ip);
        LOG_DBG_(" lifetime: %"PRIx32"\n", uip_ntohl(ND6_OPT_RDNSS_BUF(nd6_opt_offset)->lifetime));
        uip_nameserver_update(ip, uip_ntohl(ND6_OPT_RDNSS_BUF(nd6_opt_offset)->lifetime));
        ip++;
      }
      break;
#endif /* UIP_ND6_RA_RDNSS */
    default:
      LOG_ERR("ND option not supported in RA\n");
      break;
    }
    nd6_opt_offset += (ND6_OPT_HDR_BUF(nd6_opt_offset)->len << 3);
  }

  defrt = uip_ds6_defrt_lookup(&UIP_IP_BUF->srcipaddr);
  if(UIP_ND6_RA_BUF->router_lifetime != 0) {
    if(nbr != NULL) {
      nbr->isrouter = 1;
    }
    if(defrt == NULL) {
      uip_ds6_defrt_add(&UIP_IP_BUF->srcipaddr,
                        (unsigned
                         long)(uip_ntohs(UIP_ND6_RA_BUF->router_lifetime)));
    } else {
      stimer_set(&(defrt->lifetime),
                 (unsigned long)(uip_ntohs(UIP_ND6_RA_BUF->router_lifetime)));
    }
  } else {
    if(defrt != NULL) {
      uip_ds6_defrt_rm(defrt);
    }
  }

#if UIP_CONF_IPV6_QUEUE_PKT
  /* If the nbr just became reachable (e.g. it was in NBR_INCOMPLETE state
   * and we got a SLLAO), check if we had buffered a pkt for it */
  /*  if((nbr != NULL) && (nbr->queue_buf_len != 0)) {
    uip_len = nbr->queue_buf_len;
    memcpy(UIP_IP_BUF, nbr->queue_buf, uip_len);
    nbr->queue_buf_len = 0;
    return;
    }*/
  if(nbr != NULL && uip_packetqueue_buflen(&nbr->packethandle) != 0) {
    uip_len = uip_packetqueue_buflen(&nbr->packethandle);
    memcpy(UIP_IP_BUF, uip_packetqueue_buf(&nbr->packethandle), uip_len);
    uip_packetqueue_free(&nbr->packethandle);
    return;
  }

#endif /*UIP_CONF_IPV6_QUEUE_PKT */

discard:
  uipbuf_clear();
  return;
}


uip_nd6_rs_output(void)
{
  UIP_IP_BUF->vtc = 0x60;
  UIP_IP_BUF->tcflow = 0;
  UIP_IP_BUF->flow = 0;
  UIP_IP_BUF->proto = UIP_PROTO_ICMP6;
  UIP_IP_BUF->ttl = UIP_ND6_HOP_LIMIT;
  uip_create_linklocal_allrouters_mcast(&UIP_IP_BUF->destipaddr);
  uip_ds6_select_src(&UIP_IP_BUF->srcipaddr, &UIP_IP_BUF->destipaddr);
  UIP_ICMP_BUF->type = ICMP6_RS;
  UIP_ICMP_BUF->icode = 0;

  if(uip_is_addr_unspecified(&UIP_IP_BUF->srcipaddr)) {
    UIP_IP_BUF->len[1] = UIP_ICMPH_LEN + UIP_ND6_RS_LEN;
    uip_len = uip_l3_icmp_hdr_len + UIP_ND6_RS_LEN;
  } else {
    uip_len = uip_l3_icmp_hdr_len + UIP_ND6_RS_LEN + UIP_ND6_OPT_LLAO_LEN;
    uipbuf_set_len_field(UIP_IP_BUF, UIP_ICMPH_LEN + UIP_ND6_RS_LEN + UIP_ND6_OPT_LLAO_LEN);

    create_llao(&uip_buf[uip_l3_icmp_hdr_len + UIP_ND6_RS_LEN],
                UIP_ND6_OPT_SLLAO);
  }

  UIP_ICMP_BUF->icmpchksum = 0;
  UIP_ICMP_BUF->icmpchksum = ~uip_icmp6chksum();

  UIP_STAT(++uip_stat.nd6.sent);
  LOG_INFO("Sending RS to ");
  LOG_INFO_6ADDR(&UIP_IP_BUF->destipaddr);
  LOG_INFO_(" from ");
  LOG_INFO_6ADDR(&UIP_IP_BUF->srcipaddr);
  LOG_INFO_("\n");
  return;
}


uip_nd6_ra_output(uip_ipaddr_t * dest)
{

  UIP_IP_BUF->vtc = 0x60;
  UIP_IP_BUF->tcflow = 0;
  UIP_IP_BUF->flow = 0;
  UIP_IP_BUF->proto = UIP_PROTO_ICMP6;
  UIP_IP_BUF->ttl = UIP_ND6_HOP_LIMIT;

  if(dest == NULL) {
    uip_create_linklocal_allnodes_mcast(&UIP_IP_BUF->destipaddr);
  } else {
    /* For sollicited RA */
    uip_ipaddr_copy(&UIP_IP_BUF->destipaddr, dest);
  }
  uip_ds6_select_src(&UIP_IP_BUF->srcipaddr, &UIP_IP_BUF->destipaddr);

  UIP_ICMP_BUF->type = ICMP6_RA;
  UIP_ICMP_BUF->icode = 0;

  UIP_ND6_RA_BUF->cur_ttl = uip_ds6_if.cur_hop_limit;

  UIP_ND6_RA_BUF->flags_reserved =
    (UIP_ND6_M_FLAG << 7) | (UIP_ND6_O_FLAG << 6);

  UIP_ND6_RA_BUF->router_lifetime = uip_htons(UIP_ND6_ROUTER_LIFETIME);
  //UIP_ND6_RA_BUF->reachable_time = uip_htonl(uip_ds6_if.reachable_time);
  //UIP_ND6_RA_BUF->retrans_timer = uip_htonl(uip_ds6_if.retrans_timer);
  UIP_ND6_RA_BUF->reachable_time = 0;
  UIP_ND6_RA_BUF->retrans_timer = 0;

  uip_len = UIP_IPH_LEN + UIP_ICMPH_LEN + UIP_ND6_RA_LEN;
  nd6_opt_offset = UIP_ND6_RA_LEN;

  /* Prefix list */
  for(prefix = uip_ds6_prefix_list;
      prefix < uip_ds6_prefix_list + UIP_DS6_PREFIX_NB; prefix++) {
    if((prefix->isused) && (prefix->advertise)) {
      ND6_OPT_PREFIX_BUF(nd6_opt_offset)->type = UIP_ND6_OPT_PREFIX_INFO;
      ND6_OPT_PREFIX_BUF(nd6_opt_offset)->len = UIP_ND6_OPT_PREFIX_INFO_LEN / 8;
      ND6_OPT_PREFIX_BUF(nd6_opt_offset)->preflen = prefix->length;
      ND6_OPT_PREFIX_BUF(nd6_opt_offset)->flagsreserved1 = prefix->l_a_reserved;
      ND6_OPT_PREFIX_BUF(nd6_opt_offset)->validlt = uip_htonl(prefix->vlifetime);
      ND6_OPT_PREFIX_BUF(nd6_opt_offset)->preferredlt = uip_htonl(prefix->plifetime);
      ND6_OPT_PREFIX_BUF(nd6_opt_offset)->reserved2 = 0;
      uip_ipaddr_copy(&(ND6_OPT_PREFIX_BUF(nd6_opt_offset)->prefix), &(prefix->ipaddr));
      nd6_opt_offset += UIP_ND6_OPT_PREFIX_INFO_LEN;
      uip_len += UIP_ND6_OPT_PREFIX_INFO_LEN;
    }
  }

  /* Source link-layer option */
  create_llao((uint8_t *)ND6_OPT_HDR_BUF(nd6_opt_offset), UIP_ND6_OPT_SLLAO);

  uip_len += UIP_ND6_OPT_LLAO_LEN;
  nd6_opt_offset += UIP_ND6_OPT_LLAO_LEN;

  /* MTU */
  ND6_OPT_MTU_BUF(nd6_opt_offset)->type = UIP_ND6_OPT_MTU;
  ND6_OPT_MTU_BUF(nd6_opt_offset)->len = UIP_ND6_OPT_MTU_LEN >> 3;
  ND6_OPT_MTU_BUF(nd6_opt_offset)->reserved = 0;
  //ND6_OPT_MTU_BUF(nd6_opt_offset)->mtu = uip_htonl(uip_ds6_if.link_mtu);
  ND6_OPT_MTU_BUF(nd6_opt_offset)->mtu = uip_htonl(1500);

  uip_len += UIP_ND6_OPT_MTU_LEN;
  nd6_opt_offset += UIP_ND6_OPT_MTU_LEN;

#if UIP_ND6_RA_RDNSS
  if(uip_nameserver_count() > 0) {
    uint8_t i = 0;
    uip_ipaddr_t *ip = &ND6_OPT_RDNSS_BUF(nd6_opt_offset)->ip;
    uip_ipaddr_t *dns = NULL;
    ND6_OPT_RDNSS_BUF(nd6_opt_offset)->type = UIP_ND6_OPT_RDNSS;
    ND6_OPT_RDNSS_BUF(nd6_opt_offset)->reserved = 0;
    ND6_OPT_RDNSS_BUF(nd6_opt_offset)->lifetime = uip_nameserver_next_expiration();
    if(ND6_OPT_RDNSS_BUF(nd6_opt_offset)->lifetime != UIP_NAMESERVER_INFINITE_LIFETIME) {
      ND6_OPT_RDNSS_BUF(nd6_opt_offset)->lifetime -= clock_seconds();
    }
    while((dns = uip_nameserver_get(i)) != NULL) {
      uip_ipaddr_copy(ip++, dns);
      i++;
    }
    ND6_OPT_RDNSS_BUF(nd6_opt_offset)->len = UIP_ND6_OPT_RDNSS_LEN + (i << 1);
    LOG_INFO("%d nameservers reported\n", i);
    uip_len += ND6_OPT_RDNSS_BUF(nd6_opt_offset)->len << 3;
    nd6_opt_offset += ND6_OPT_RDNSS_BUF(nd6_opt_offset)->len << 3;
  }
#endif /* UIP_ND6_RA_RDNSS */

  uipbuf_set_len_field(UIP_IP_BUF, uip_len - UIP_IPH_LEN);

  /*ICMP checksum */
  UIP_ICMP_BUF->icmpchksum = 0;
  UIP_ICMP_BUF->icmpchksum = ~uip_icmp6chksum();

  UIP_STAT(++uip_stat.nd6.sent);
  LOG_INFO("Sending RA to ");
  LOG_INFO_6ADDR(&UIP_IP_BUF->destipaddr);
  LOG_INFO_(" from ");
  LOG_INFO_6ADDR(&UIP_IP_BUF->srcipaddr);
  LOG_INFO_("\n");
  return;
}


create_llao(uint8_t *llao, uint8_t type)
{
  llao[UIP_ND6_OPT_TYPE_OFFSET] = type;
  llao[UIP_ND6_OPT_LEN_OFFSET] = UIP_ND6_OPT_LLAO_LEN >> 3;
  memcpy(&llao[UIP_ND6_OPT_DATA_OFFSET], &uip_lladdr, UIP_LLADDR_LEN);
  /* padding on some */
  memset(&llao[UIP_ND6_OPT_DATA_OFFSET + UIP_LLADDR_LEN], 0,
         UIP_ND6_OPT_LLAO_LEN - 2 - UIP_LLADDR_LEN);
}


uip_nd6_ns_output(uip_ipaddr_t * src, uip_ipaddr_t * dest, uip_ipaddr_t * tgt)
{
  uipbuf_clear();
  UIP_IP_BUF->vtc = 0x60;
  UIP_IP_BUF->tcflow = 0;
  UIP_IP_BUF->flow = 0;
  UIP_IP_BUF->proto = UIP_PROTO_ICMP6;
  UIP_IP_BUF->ttl = UIP_ND6_HOP_LIMIT;

  if(dest == NULL) {
    uip_create_solicited_node(tgt, &UIP_IP_BUF->destipaddr);
  } else {
    uip_ipaddr_copy(&UIP_IP_BUF->destipaddr, dest);
  }
  UIP_ICMP_BUF->type = ICMP6_NS;
  UIP_ICMP_BUF->icode = 0;
  UIP_ND6_NS_BUF->reserved = 0;
  uip_ipaddr_copy((uip_ipaddr_t *) &UIP_ND6_NS_BUF->tgtipaddr, tgt);
  /*
   * check if we add a SLLAO option: for DAD, MUST NOT, for NUD, MAY
   * (here yes), for Address resolution , MUST
   */
  if(!(uip_ds6_is_my_addr(tgt))) {
    if(src != NULL) {
      uip_ipaddr_copy(&UIP_IP_BUF->srcipaddr, src);
    } else {
      uip_ds6_select_src(&UIP_IP_BUF->srcipaddr, &UIP_IP_BUF->destipaddr);
    }
    if (uip_is_addr_unspecified(&UIP_IP_BUF->srcipaddr)) {
      LOG_ERR("Dropping NS due to no suitable source address\n");
      uipbuf_clear();
      return;
    }
    uipbuf_set_len_field(UIP_IP_BUF, UIP_ICMPH_LEN + UIP_ND6_NS_LEN + UIP_ND6_OPT_LLAO_LEN);

    create_llao(&uip_buf[uip_l3_icmp_hdr_len + UIP_ND6_NS_LEN],
                UIP_ND6_OPT_SLLAO);

    uip_len =
      UIP_IPH_LEN + UIP_ICMPH_LEN + UIP_ND6_NS_LEN + UIP_ND6_OPT_LLAO_LEN;
  } else {
    uip_create_unspecified(&UIP_IP_BUF->srcipaddr);
    UIP_IP_BUF->len[1] = UIP_ICMPH_LEN + UIP_ND6_NS_LEN;
    uip_len = UIP_IPH_LEN + UIP_ICMPH_LEN + UIP_ND6_NS_LEN;
  }

  UIP_ICMP_BUF->icmpchksum = 0;
  UIP_ICMP_BUF->icmpchksum = ~uip_icmp6chksum();

  UIP_STAT(++uip_stat.nd6.sent);
  LOG_INFO("Sending NS to ");
  LOG_INFO_6ADDR(&UIP_IP_BUF->destipaddr);
  LOG_INFO_(" from ");
  LOG_INFO_6ADDR(&UIP_IP_BUF->srcipaddr);
  LOG_INFO_(" with target address ");
  LOG_INFO_6ADDR(tgt);
  LOG_INFO_("\n");
  return;
}


uip_nd6_init()
{
#if UIP_ND6_SEND_NA
  /* Only handle NSs if we are prepared to send out NAs */
  uip_icmp6_register_input_handler(&ns_input_handler);
#endif

#if UIP_ND6_SEND_NS
  /*
   * Only handle NAs if we are prepared to send out NSs. */
  uip_icmp6_register_input_handler(&na_input_handler);
#endif

#if UIP_CONF_ROUTER && UIP_ND6_SEND_RA
  /* Only accept RS if we are a router and happy to send out RAs */
  uip_icmp6_register_input_handler(&rs_input_handler);
#endif

#if !UIP_CONF_ROUTER
  /* Only process RAs if we are not a router */
  uip_icmp6_register_input_handler(&ra_input_handler);
#endif
}


na_input(void)
{
  uint8_t is_llchange;
  uint8_t is_router;
  uint8_t is_solicited;
  uint8_t is_override;
  uip_lladdr_t lladdr_aligned;

  LOG_INFO("Received NA from ");
  LOG_INFO_6ADDR(&UIP_IP_BUF->srcipaddr);
  LOG_INFO_(" to ");
  LOG_INFO_6ADDR(&UIP_IP_BUF->destipaddr);
  LOG_INFO_(" with target address ");
  LOG_INFO_6ADDR((uip_ipaddr_t *) (&UIP_ND6_NA_BUF->tgtipaddr));
  LOG_INFO_("\n");
  UIP_STAT(++uip_stat.nd6.recv);

  /*
   * booleans. the three last one are not 0 or 1 but 0 or 0x80, 0x40, 0x20
   * but it works. Be careful though, do not use tests such as is_router == 1
   */
  is_llchange = 0;
  is_router = ((UIP_ND6_NA_BUF->flagsreserved & UIP_ND6_NA_FLAG_ROUTER));
  is_solicited =
    ((UIP_ND6_NA_BUF->flagsreserved & UIP_ND6_NA_FLAG_SOLICITED));
  is_override =
    ((UIP_ND6_NA_BUF->flagsreserved & UIP_ND6_NA_FLAG_OVERRIDE));

#if UIP_CONF_IPV6_CHECKS
  if((UIP_IP_BUF->ttl != UIP_ND6_HOP_LIMIT) ||
     (UIP_ICMP_BUF->icode != 0) ||
     (uip_is_addr_mcast(&UIP_ND6_NA_BUF->tgtipaddr)) ||
     (is_solicited && uip_is_addr_mcast(&UIP_IP_BUF->destipaddr))) {
    LOG_ERR("NA received is bad\n");
    goto discard;
  }
#endif /*UIP_CONF_IPV6_CHECKS */

  /* Options processing: we handle TLLAO, and must ignore others */
  nd6_opt_offset = UIP_ND6_NA_LEN;
  nd6_opt_llao = NULL;
  while(uip_l3_icmp_hdr_len + nd6_opt_offset < uip_len) {
#if UIP_CONF_IPV6_CHECKS
    if(ND6_OPT_HDR_BUF(nd6_opt_offset)->len == 0) {
      LOG_ERR("NA received is bad\n");
      goto discard;
    }
#endif /*UIP_CONF_IPV6_CHECKS */
    switch (ND6_OPT_HDR_BUF(nd6_opt_offset)->type) {
    case UIP_ND6_OPT_TLLAO:
      nd6_opt_llao = (uint8_t *)ND6_OPT_HDR_BUF(nd6_opt_offset);
      break;
    default:
      LOG_WARN("ND option not supported in NA\n");
      break;
    }
    nd6_opt_offset += (ND6_OPT_HDR_BUF(nd6_opt_offset)->len << 3);
  }
  addr = uip_ds6_addr_lookup(&UIP_ND6_NA_BUF->tgtipaddr);
  /* Message processing, including TLLAO if any */
  if(addr != NULL) {
#if UIP_ND6_DEF_MAXDADNS > 0
    if(addr->state == ADDR_TENTATIVE) {
      uip_ds6_dad_failed(addr);
    }
#endif /*UIP_ND6_DEF_MAXDADNS > 0 */
    LOG_ERR("NA received is bad\n");
    goto discard;
  } else {
    const uip_lladdr_t *lladdr;
    nbr = uip_ds6_nbr_lookup(&UIP_ND6_NA_BUF->tgtipaddr);
    if(nbr == NULL) {
      goto discard;
    }
    lladdr = uip_ds6_nbr_get_ll(nbr);
    if(lladdr == NULL) {
      goto discard;
    }
    if(nd6_opt_llao != NULL) {
      is_llchange =
        memcmp(&nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET], lladdr,
               UIP_LLADDR_LEN) == 0 ? 0 : 1;
    }
    if(nbr->state == NBR_INCOMPLETE) {
      if(nd6_opt_llao == NULL || !extract_lladdr_from_llao_aligned(&lladdr_aligned)) {
        goto discard;
      }
      if(uip_ds6_nbr_update_ll(&nbr,
                               (const uip_lladdr_t *)&lladdr_aligned) < 0) {
        /* failed to update the lladdr */
        goto discard;
      }

      /* Note: No need to refresh the state of the nbr here.
       * It has already been refreshed upon receiving the unicast IPv6 ND packet.
       * See: uip_ds6_nbr_refresh_reachable_state()
       */
      if(!is_solicited) {
        nbr->state = NBR_STALE;
      }
      nbr->isrouter = is_router;
    } else { /* NBR is not INCOMPLETE */
      if(!is_override && is_llchange) {
        if(nbr->state == NBR_REACHABLE) {
          nbr->state = NBR_STALE;
        }
        goto discard;
      } else {
        /**
         *  If this is an cache override, or same lladdr, or no llao -
         *  do updates of nbr states.
         */
        if(is_override || !is_llchange || nd6_opt_llao == NULL) {
          if(nd6_opt_llao != NULL && is_llchange) {
            if(!extract_lladdr_from_llao_aligned(&lladdr_aligned) ||
               uip_ds6_nbr_update_ll(&nbr,
                                     (const uip_lladdr_t *)&lladdr_aligned)
               < 0) {
              /* failed to update the lladdr */
              goto discard;
            }
          }
          /* Note: No need to refresh the state of the nbr here.
           * It has already been refreshed upon receiving the unicast IPv6 ND packet.
           * See: uip_ds6_nbr_refresh_reachable_state()
           */
        }
      }
      if(nbr->isrouter && !is_router) {
        defrt = uip_ds6_defrt_lookup(&UIP_IP_BUF->srcipaddr);
        if(defrt != NULL) {
          uip_ds6_defrt_rm(defrt);
        }
      }
      nbr->isrouter = is_router;
    }
  }
#if UIP_CONF_IPV6_QUEUE_PKT
  /* The nbr is now reachable, check if we had buffered a pkt for it */
  /*if(nbr->queue_buf_len != 0) {
    uip_len = nbr->queue_buf_len;
    memcpy(UIP_IP_BUF, nbr->queue_buf, uip_len);
    nbr->queue_buf_len = 0;
    return;
    }*/
  if(uip_packetqueue_buflen(&nbr->packethandle) != 0) {
    uip_len = uip_packetqueue_buflen(&nbr->packethandle);
    memcpy(UIP_IP_BUF, uip_packetqueue_buf(&nbr->packethandle), uip_len);
    uip_packetqueue_free(&nbr->packethandle);
    return;
  }

#endif /*UIP_CONF_IPV6_QUEUE_PKT */

discard:
  uipbuf_clear();
  return;
}


ns_input(void)
{
  uint8_t flags = 0;

  LOG_INFO("Received NS from ");
  LOG_INFO_6ADDR(&UIP_IP_BUF->srcipaddr);
  LOG_INFO_(" to ");
  LOG_INFO_6ADDR(&UIP_IP_BUF->destipaddr);
  LOG_INFO_(" with target address ");
  LOG_INFO_6ADDR((uip_ipaddr_t *) (&UIP_ND6_NS_BUF->tgtipaddr));
  LOG_INFO_("\n");
  UIP_STAT(++uip_stat.nd6.recv);

#if UIP_CONF_IPV6_CHECKS
  if((UIP_IP_BUF->ttl != UIP_ND6_HOP_LIMIT) ||
     (uip_is_addr_mcast(&UIP_ND6_NS_BUF->tgtipaddr)) ||
     (UIP_ICMP_BUF->icode != 0)) {
    LOG_ERR("NS received is bad\n");
    goto discard;
  }
#endif /* UIP_CONF_IPV6_CHECKS */

  /* Options processing */
  nd6_opt_llao = NULL;
  nd6_opt_offset = UIP_ND6_NS_LEN;
  while(uip_l3_icmp_hdr_len + nd6_opt_offset + UIP_ND6_OPT_HDR_LEN < uip_len) {
#if UIP_CONF_IPV6_CHECKS
    if(ND6_OPT_HDR_BUF(nd6_opt_offset)->len == 0) {
      LOG_ERR("NS received is bad\n");
      goto discard;
    }
#endif /* UIP_CONF_IPV6_CHECKS */
    switch (ND6_OPT_HDR_BUF(nd6_opt_offset)->type) {
    case UIP_ND6_OPT_SLLAO:
      if(uip_l3_icmp_hdr_len + nd6_opt_offset +
         UIP_ND6_OPT_DATA_OFFSET + UIP_LLADDR_LEN > uip_len) {
        LOG_ERR("Insufficient data for NS SLLAO option\n");
        goto discard;
      }
      nd6_opt_llao = &uip_buf[uip_l3_icmp_hdr_len + nd6_opt_offset];
#if UIP_CONF_IPV6_CHECKS
      /* There must be NO option in a DAD NS */
      if(uip_is_addr_unspecified(&UIP_IP_BUF->srcipaddr)) {
        LOG_ERR("NS received is bad\n");
        goto discard;
      } else {
#endif /*UIP_CONF_IPV6_CHECKS */
        uip_lladdr_t lladdr_aligned;
        extract_lladdr_from_llao_aligned(&lladdr_aligned);
        nbr = uip_ds6_nbr_lookup(&UIP_IP_BUF->srcipaddr);
        if(nbr == NULL) {
          uip_ds6_nbr_add(&UIP_IP_BUF->srcipaddr, &lladdr_aligned,
			  0, NBR_STALE, NBR_TABLE_REASON_IPV6_ND, NULL);
        } else {
          const uip_lladdr_t *lladdr = uip_ds6_nbr_get_ll(nbr);
          if(lladdr == NULL) {
            goto discard;
          }
          if(memcmp(&nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET],
              lladdr, UIP_LLADDR_LEN) != 0) {
            if(uip_ds6_nbr_update_ll(&nbr,
                                     (const uip_lladdr_t *)&lladdr_aligned)
               < 0) {
              /* failed to update the lladdr */
              goto discard;
            }
            nbr->state = NBR_STALE;
          } else {
            if(nbr->state == NBR_INCOMPLETE) {
              nbr->state = NBR_STALE;
            }
          }
        }
#if UIP_CONF_IPV6_CHECKS
      }
#endif /*UIP_CONF_IPV6_CHECKS */
      break;
    default:
      LOG_WARN("ND option not supported in NS");
      break;
    }
    nd6_opt_offset += (ND6_OPT_HDR_BUF(nd6_opt_offset)->len << 3);
  }

  addr = uip_ds6_addr_lookup(&UIP_ND6_NS_BUF->tgtipaddr);
  if(addr != NULL) {
    if(uip_is_addr_unspecified(&UIP_IP_BUF->srcipaddr)) {
      /* DAD CASE */
#if UIP_ND6_DEF_MAXDADNS > 0
#if UIP_CONF_IPV6_CHECKS
      if(!uip_is_addr_solicited_node(&UIP_IP_BUF->destipaddr)) {
        LOG_ERR("NS received is bad\n");
        goto discard;
      }
#endif /* UIP_CONF_IPV6_CHECKS */
      if(addr->state != ADDR_TENTATIVE) {
        uip_create_linklocal_allnodes_mcast(&UIP_IP_BUF->destipaddr);
        uip_ds6_select_src(&UIP_IP_BUF->srcipaddr, &UIP_IP_BUF->destipaddr);
        flags = UIP_ND6_NA_FLAG_OVERRIDE;
        goto create_na;
      } else {
          /** \todo if I sent a NS before him, I win */
        uip_ds6_dad_failed(addr);
        goto discard;
      }
#else /* UIP_ND6_DEF_MAXDADNS > 0 */
      goto discard;  /* DAD CASE */
#endif /* UIP_ND6_DEF_MAXDADNS > 0 */
    }
#if UIP_CONF_IPV6_CHECKS
    if(uip_ds6_is_my_addr(&UIP_IP_BUF->srcipaddr)) {
        /**
         * \NOTE do we do something here? we both are using the same address.
         * If we are doing dad, we could cancel it, though we should receive a
         * NA in response of DAD NS we sent, hence DAD will fail anyway. If we
         * were not doing DAD, it means there is a duplicate in the network!
         */
      LOG_ERR("NS received is bad\n");
      goto discard;
    }
#endif /*UIP_CONF_IPV6_CHECKS */

    /* Address resolution case */
    if(uip_is_addr_solicited_node(&UIP_IP_BUF->destipaddr)) {
      uip_ipaddr_copy(&UIP_IP_BUF->destipaddr, &UIP_IP_BUF->srcipaddr);
      uip_ipaddr_copy(&UIP_IP_BUF->srcipaddr, &UIP_ND6_NS_BUF->tgtipaddr);
      flags = UIP_ND6_NA_FLAG_SOLICITED | UIP_ND6_NA_FLAG_OVERRIDE;
      goto create_na;
    }

    /* NUD CASE */
    if(uip_ds6_addr_lookup(&UIP_IP_BUF->destipaddr) == addr) {
      uip_ipaddr_copy(&UIP_IP_BUF->destipaddr, &UIP_IP_BUF->srcipaddr);
      uip_ipaddr_copy(&UIP_IP_BUF->srcipaddr, &UIP_ND6_NS_BUF->tgtipaddr);
      flags = UIP_ND6_NA_FLAG_SOLICITED | UIP_ND6_NA_FLAG_OVERRIDE;
      goto create_na;
    } else {
#if UIP_CONF_IPV6_CHECKS
      LOG_ERR("NS received is bad\n");
      goto discard;
#endif /* UIP_CONF_IPV6_CHECKS */
    }
  } else {
    goto discard;
  }

create_na:
    /* If the node is a router it should set R flag in NAs */
#if UIP_CONF_ROUTER
    flags = flags | UIP_ND6_NA_FLAG_ROUTER;
#endif
  uipbuf_clear();
  UIP_IP_BUF->vtc = 0x60;
  UIP_IP_BUF->tcflow = 0;
  UIP_IP_BUF->flow = 0;
  uipbuf_set_len_field(UIP_IP_BUF, UIP_ICMPH_LEN + UIP_ND6_NA_LEN + UIP_ND6_OPT_LLAO_LEN);
  UIP_IP_BUF->proto = UIP_PROTO_ICMP6;
  UIP_IP_BUF->ttl = UIP_ND6_HOP_LIMIT;

  UIP_ICMP_BUF->type = ICMP6_NA;
  UIP_ICMP_BUF->icode = 0;

  UIP_ND6_NA_BUF->flagsreserved = flags;
  memcpy(&UIP_ND6_NA_BUF->tgtipaddr, &addr->ipaddr, sizeof(uip_ipaddr_t));

  create_llao(&uip_buf[uip_l3_icmp_hdr_len + UIP_ND6_NA_LEN],
              UIP_ND6_OPT_TLLAO);

  UIP_ICMP_BUF->icmpchksum = 0;
  UIP_ICMP_BUF->icmpchksum = ~uip_icmp6chksum();

  uipbuf_set_len(UIP_IPH_LEN + UIP_ICMPH_LEN + UIP_ND6_NA_LEN + UIP_ND6_OPT_LLAO_LEN);

  UIP_STAT(++uip_stat.nd6.sent);
  LOG_INFO("Sending NA to ");
  LOG_INFO_6ADDR(&UIP_IP_BUF->destipaddr);
  LOG_INFO_(" from ");
  LOG_INFO_6ADDR(&UIP_IP_BUF->srcipaddr);
  LOG_INFO_(" with target address ");
  LOG_INFO_6ADDR(&UIP_ND6_NA_BUF->tgtipaddr);
  LOG_INFO_("\n");
  return;

discard:
  uipbuf_clear();
  return;
}


extract_lladdr_from_llao_aligned(uip_lladdr_t *dest)
{
  if(dest != NULL && nd6_opt_llao != NULL) {
    memcpy(dest, &nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET], UIP_LLADDR_LEN);
    return 1;
  }
  return 0;
}


void mc_luma(const base_context* ctx,
             const seq_parameter_set* sps, int mv_x, int mv_y,
             int xP,int yP,
             int16_t* out, int out_stride,
             const pixel_t* ref, int ref_stride,
             int nPbW, int nPbH, int bitDepth_L)
{
  int xFracL = mv_x & 3;
  int yFracL = mv_y & 3;

  int xIntOffsL = xP + (mv_x>>2);
  int yIntOffsL = yP + (mv_y>>2);

  // luma sample interpolation process (8.5.3.2.2.1)

  //const int shift1 = sps->BitDepth_Y-8;
  //const int shift2 = 6;
  const int shift3 = 14 - sps->BitDepth_Y;

  int w = sps->pic_width_in_luma_samples;
  int h = sps->pic_height_in_luma_samples;

  ALIGNED_16(int16_t) mcbuffer[MAX_CU_SIZE * (MAX_CU_SIZE+7)];

  if (xFracL==0 && yFracL==0) {

    if (xIntOffsL >= 0 && yIntOffsL >= 0 &&
        nPbW+xIntOffsL <= w && nPbH+yIntOffsL <= h) {

      ctx->acceleration.put_hevc_qpel(out, out_stride,
                                      &ref[yIntOffsL*ref_stride + xIntOffsL],
                                      ref_stride /* sizeof(pixel_t)*/,
                                      nPbW,nPbH, mcbuffer, 0,0, bitDepth_L);
    }
    else {
      for (int y=0;y<nPbH;y++)
        for (int x=0;x<nPbW;x++) {

          int xA = Clip3(0,w-1,x + xIntOffsL);
          int yA = Clip3(0,h-1,y + yIntOffsL);

          out[y*out_stride+x] = ref[ xA + yA*ref_stride ] << shift3;
        }
    }

#ifdef DE265_LOG_TRACE
    logtrace(LogMotion,"---MC luma %d %d = direct---\n",xFracL,yFracL);

    for (int y=0;y<nPbH;y++) {
      for (int x=0;x<nPbW;x++) {

        int xA = Clip3(0,w-1,x + xIntOffsL);
        int yA = Clip3(0,h-1,y + yIntOffsL);

        logtrace(LogMotion,"%02x ", ref[ xA + yA*ref_stride ]);
      }
      logtrace(LogMotion,"\n");
    }

    logtrace(LogMotion," -> \n");

    for (int y=0;y<nPbH;y++) {
      for (int x=0;x<nPbW;x++) {

        logtrace(LogMotion,"%02x ",out[y*out_stride+x] >> 6); // 6 will be used when summing predictions
      }
      logtrace(LogMotion,"\n");
    }
#endif
  }
  else {
    int extra_left   = extra_before[xFracL];
    int extra_right  = extra_after [xFracL];
    int extra_top    = extra_before[yFracL];
    int extra_bottom = extra_after [yFracL];

    //int nPbW_extra = extra_left + nPbW + extra_right;
    //int nPbH_extra = extra_top  + nPbH + extra_bottom;

    pixel_t padbuf[(MAX_CU_SIZE+16)*(MAX_CU_SIZE+7)];

    const pixel_t* src_ptr;
    int src_stride;

    if (-extra_left + xIntOffsL >= 0 &&
        -extra_top  + yIntOffsL >= 0 &&
        nPbW+extra_right  + xIntOffsL < w &&
        nPbH+extra_bottom + yIntOffsL < h) {
      src_ptr = &ref[xIntOffsL + yIntOffsL*ref_stride];
      src_stride = ref_stride;
    }
    else {
      for (int y=-extra_top;y<nPbH+extra_bottom;y++) {
        for (int x=-extra_left;x<nPbW+extra_right;x++) {

          int xA = Clip3(0,w-1,x + xIntOffsL);
          int yA = Clip3(0,h-1,y + yIntOffsL);

          padbuf[x+extra_left + (y+extra_top)*(MAX_CU_SIZE+16)] = ref[ xA + yA*ref_stride ];
        }
      }

      src_ptr = &padbuf[extra_top*(MAX_CU_SIZE+16) + extra_left];
      src_stride = MAX_CU_SIZE+16;
    }

    ctx->acceleration.put_hevc_qpel(out, out_stride,
                                    src_ptr, src_stride /* sizeof(pixel_t) */,
                                    nPbW,nPbH, mcbuffer, xFracL,yFracL, bitDepth_L);

    logtrace(LogMotion,"---V---\n");
    for (int y=0;y<nPbH;y++) {
      for (int x=0;x<nPbW;x++) {
        logtrace(LogMotion,"%04x ",out[x+y*out_stride]);
      }
      logtrace(LogMotion,"\n");
    }
  }
}

fix MC with HDR chroma, but SDR luma (#301)


void get_merge_candidate_list(base_context* ctx,
                              const slice_segment_header* shdr,
                              de265_image* img,
                              int xC,int yC, int xP,int yP,
                              int nCS, int nPbW,int nPbH, int partIdx,
                              PBMotion* mergeCandList)
{
  int max_merge_idx = 5-shdr->five_minus_max_num_merge_cand -1;

  get_merge_candidate_list_without_step_9(ctx, shdr,
                                          MotionVectorAccess_de265_image(img), img,
                                          xC,yC,xP,yP,nCS,nPbW,nPbH, partIdx,
                                          max_merge_idx, mergeCandList);

  // 9. for encoder: modify all merge candidates

  for (int i=0;i<=max_merge_idx;i++) {
    if (mergeCandList[i].predFlag[0] &&
        mergeCandList[i].predFlag[1] &&
        nPbW+nPbH==12)
      {
        mergeCandList[i].refIdx[1]   = -1;
        mergeCandList[i].predFlag[1] = 0;
      }
  }
}


bool PBMotion::operator==(const PBMotion& b) const
{
  const PBMotion& a = *this;

  // TODO: is this really correct? no check for predFlag? Standard says so... (p.127)

  for (int i=0;i<2;i++) {
    if (a.predFlag[i] != b.predFlag[i]) return false;

    if (a.predFlag[i]) {
      if (a.mv[i].x != b.mv[i].x) return false;
      if (a.mv[i].y != b.mv[i].y) return false;
      if (a.refIdx[i] != b.refIdx[i]) return false;
    }
  }

  return true;
}


void decode_prediction_unit(base_context* ctx,
                            const slice_segment_header* shdr,
                            de265_image* img,
                            const PBMotionCoding& motion,
                            int xC,int yC, int xB,int yB, int nCS, int nPbW,int nPbH, int partIdx)
{
  logtrace(LogMotion,"decode_prediction_unit POC=%d %d;%d %dx%d\n",
           img->PicOrderCntVal, xC+xB,yC+yB, nPbW,nPbH);

  //slice_segment_header* shdr = tctx->shdr;

  // 1.

  PBMotion vi;
  motion_vectors_and_ref_indices(ctx, shdr, img, motion,
                                 xC,yC, xB,yB, nCS, nPbW,nPbH, partIdx, &vi);

  // 2.

  generate_inter_prediction_samples(ctx,shdr, img, xC,yC, xB,yB, nCS, nPbW,nPbH, &vi);

  img->set_mv_info(xC+xB,yC+yB,nPbW,nPbH, vi);
}


void derive_temporal_luma_vector_prediction(base_context* ctx,
                                            de265_image* img,
                                            const slice_segment_header* shdr,
                                            int xP,int yP,
                                            int nPbW,int nPbH,
                                            int refIdxL,
                                            int X, // which MV (L0/L1) to get
                                            MotionVector* out_mvLXCol,
                                            uint8_t*      out_availableFlagLXCol)
{
  // --- no temporal MVP -> exit ---

  if (shdr->slice_temporal_mvp_enabled_flag == 0) {
    out_mvLXCol->x = 0;
    out_mvLXCol->y = 0;
    *out_availableFlagLXCol = 0;
    return;
  }

  // --- find collocated reference image ---

  int Log2CtbSizeY = img->get_sps().Log2CtbSizeY;

  int colPic; // TODO: this is the same for the whole slice. We can precompute it.

  if (shdr->slice_type == SLICE_TYPE_B &&
      shdr->collocated_from_l0_flag == 0)
    {
      logtrace(LogMotion,"collocated L1 ref_idx=%d\n",shdr->collocated_ref_idx);

      colPic = shdr->RefPicList[1][ shdr->collocated_ref_idx ];
    }
  else
    {
      logtrace(LogMotion,"collocated L0 ref_idx=%d\n",shdr->collocated_ref_idx);

      colPic = shdr->RefPicList[0][ shdr->collocated_ref_idx ];
    }

  // check whether collocated reference picture exists

  if (!ctx->has_image(colPic)) {
    out_mvLXCol->x = 0;
    out_mvLXCol->y = 0;
    *out_availableFlagLXCol = 0;

    ctx->add_warning(DE265_WARNING_NONEXISTING_REFERENCE_PICTURE_ACCESSED, false);
    return;
  }

  // --- get collocated MV either at bottom-right corner or from center of PB ---

  int xColPb,yColPb;
  int yColBr = yP + nPbH; // bottom right collocated motion vector position
  int xColBr = xP + nPbW;

  /* If neighboring pixel at bottom-right corner is in the same CTB-row and inside the image,
     use this (reduced down to 16 pixels resolution) as collocated MV position.

     Note: see 2014, Sze, Sect. 5.2.1.2 why candidate C0 is excluded when on another CTB-row.
     This is to reduce the memory bandwidth requirements.
   */
  if ((yP>>Log2CtbSizeY) == (yColBr>>Log2CtbSizeY) &&
      xColBr < img->get_sps().pic_width_in_luma_samples &&
      yColBr < img->get_sps().pic_height_in_luma_samples)
    {
      xColPb = xColBr & ~0x0F; // reduce resolution of collocated motion-vectors to 16 pixels grid
      yColPb = yColBr & ~0x0F;

      derive_collocated_motion_vectors(ctx,img,shdr, xP,yP, colPic, xColPb,yColPb, refIdxL, X,
                                       out_mvLXCol, out_availableFlagLXCol);
    }
  else
    {
      out_mvLXCol->x = 0;
      out_mvLXCol->y = 0;
      *out_availableFlagLXCol = 0;
    }

  if (*out_availableFlagLXCol==0) {

    int xColCtr = xP+(nPbW>>1);
    int yColCtr = yP+(nPbH>>1);

    xColPb = xColCtr & ~0x0F; // reduce resolution of collocated motion-vectors to 16 pixels grid
    yColPb = yColCtr & ~0x0F;

    derive_collocated_motion_vectors(ctx,img,shdr, xP,yP, colPic, xColPb,yColPb, refIdxL, X,
                                     out_mvLXCol, out_availableFlagLXCol);
  }
}