/*
 * SPDX-FileCopyrightText: (c) 2003, 2004 Lev Walkin <vlm@lionet.info>. All rights reserved.
 * SPDX-License-Identifier: BSD-1-Clause
 */
#include <asn_application.h>
#include <asn_internal.h>
#include <xer_support.h> /* XER/XML parsing support */

/*
 * Decode the XER encoding of a given type.
 */
asn_dec_rval_t xer_decode(asn_codec_ctx_t *opt_codec_ctx,
    asn_TYPE_descriptor_t *td, void **struct_ptr,
    const void *buffer, size_t size)
{
    asn_codec_ctx_t s_codec_ctx;

    /*
     * Stack checker requires that the codec context
     * must be allocated on the stack.
     */
    if (opt_codec_ctx)
        {
            if (opt_codec_ctx->max_stack_size)
                {
                    s_codec_ctx = *opt_codec_ctx;
                    opt_codec_ctx = &s_codec_ctx;
                }
        }
    else
        {
            /* If context is not given, be security-conscious anyway */
            memset(&s_codec_ctx, 0, sizeof(s_codec_ctx));
            s_codec_ctx.max_stack_size = _ASN_DEFAULT_STACK_MAX;
            opt_codec_ctx = &s_codec_ctx;
        }

    /*
     * Invoke type-specific decoder.
     */
    return td->xer_decoder(opt_codec_ctx, td, struct_ptr, 0, buffer, size);
}

struct xer__cb_arg
{
    pxml_chunk_type_e chunk_type;
    size_t chunk_size;
    const void *chunk_buf;
    int callback_not_invoked;
};

static int xer__token_cb(pxml_chunk_type_e type, const void *_chunk_data,
    size_t _chunk_size, void *key)
{
    struct xer__cb_arg *arg = (struct xer__cb_arg *)key;
    arg->chunk_type = type;
    arg->chunk_size = _chunk_size;
    arg->chunk_buf = _chunk_data;
    arg->callback_not_invoked = 0;
    return -1; /* Terminate the XML parsing */
}

/*
 * Fetch the next token from the XER/XML stream.
 */
ssize_t xer_next_token(int *stateContext, const void *buffer, size_t size,
    pxer_chunk_type_e *ch_type)
{
    struct xer__cb_arg arg;
    int new_stateContext = *stateContext;
    ssize_t ret;

    arg.callback_not_invoked = 1;
    ret = pxml_parse(&new_stateContext, buffer, size, xer__token_cb, &arg);
    if (ret < 0)
        {
            return -1;
        }
    if (arg.callback_not_invoked)
        {
            assert(ret == 0); /* No data was consumed */
            return 0;         /* Try again with more data */
        }
    else
        {
            assert(arg.chunk_size);
            assert(arg.chunk_buf == buffer);
        }

    /*
     * Translate the XML chunk types into more convenient ones.
     */
    switch (arg.chunk_type)
        {
        case PXML_TEXT:
            *ch_type = PXER_TEXT;
            break;
        case PXML_TAG:
            return 0; /* Want more */
        case PXML_TAG_END:
            *ch_type = PXER_TAG;
            break;
        case PXML_COMMENT:
        case PXML_COMMENT_END:
            *ch_type = PXER_COMMENT;
            break;
        }

    *stateContext = new_stateContext;
    return arg.chunk_size;
}

#define CSLASH 0x2f /* '/' */
#define LANGLE 0x3c /* '<' */
#define RANGLE 0x3e /* '>' */

xer_check_tag_e xer_check_tag(const void *buf_ptr, int size,
    const char *need_tag)
{
    const char *buf = (const char *)buf_ptr;
    const char *end;
    xer_check_tag_e ct = XCT_OPENING;

    if (size < 2 || buf[0] != LANGLE || buf[size - 1] != RANGLE)
        {
            if (size >= 2)
                {
                    ASN_DEBUG("Broken XML tag: \"%c...%c\"", buf[0], buf[size - 1]);
                }
            return XCT_BROKEN;
        }

    /*
     * Determine the tag class.
     */
    if (buf[1] == CSLASH)
        {
            buf += 2;  /* advance past "</" */
            size -= 3; /* strip "</" and ">" */
            ct = XCT_CLOSING;
            if (size > 0 && buf[size - 1] == CSLASH)
                {
                    return XCT_BROKEN; /* </abc/> */
                }
        }
    else
        {
            buf++;     /* advance past "<" */
            size -= 2; /* strip "<" and ">" */
            if (size > 0 && buf[size - 1] == CSLASH)
                {
                    ct = XCT_BOTH;
                    size--; /* One more, for "/" */
                }
        }

    /* Sometimes we don't care about the tag */
    if (!need_tag || !*need_tag)
        {
            return (xer_check_tag_e)(XCT__UNK__MASK | ct);
        }

    /*
     * Determine the tag name.
     */
    for (end = buf + size; buf < end; buf++, need_tag++)
        {
            int b = *buf;
            int n = *need_tag;
            if (b != n)
                {
                    if (n == 0)
                        {
                            switch (b)
                                {
                                case 0x09:
                                case 0x0a:
                                case 0x0c:
                                case 0x0d:
                                case 0x20:
                                    /* "<abc def/>": whitespace is normal */
                                    return ct;
                                }
                        }
                    return (xer_check_tag_e)(XCT__UNK__MASK | ct);
                }
            if (b == 0)
                {
                    return XCT_BROKEN; /* Embedded 0 in buf?! */
                }
        }
    if (*need_tag)
        {
            return (xer_check_tag_e)(XCT__UNK__MASK | ct);
        }

    return ct;
}

#undef ADVANCE
#define ADVANCE(num_bytes)                           \
    do                                               \
        {                                            \
            size_t num = (num_bytes);                \
            buf_ptr = ((const char *)buf_ptr) + num; \
            size -= num;                             \
            consumed_myself += num;                  \
        }                                            \
    while (0)

#undef RETURN
#define RETURN(_code)                                                       \
    do                                                                      \
        {                                                                   \
            rval.code = _code;                                              \
            rval.consumed = consumed_myself;                                \
            if (rval.code != RC_OK) ASN_DEBUG("Failed with %d", rval.code); \
            return rval;                                                    \
        }                                                                   \
    while (0)

#define XER_GOT_BODY(chunk_buf, chunk_size, size)                      \
    do                                                                 \
        {                                                              \
            ssize_t converted_size =                                   \
                body_receiver(struct_key, chunk_buf, chunk_size,       \
                    (size_t)(chunk_size) < (size));                    \
            if (converted_size == -1) RETURN(RC_FAIL);                 \
            if (converted_size == 0 && (size) == (size_t)(chunk_size)) \
                RETURN(RC_WMORE);                                      \
            (chunk_size) = converted_size;                             \
        }                                                              \
    while (0)
#define XER_GOT_EMPTY()                                          \
    do                                                           \
        {                                                        \
            if (body_receiver(struct_key, 0, 0, size > 0) == -1) \
                RETURN(RC_FAIL);                                 \
        }                                                        \
    while (0)

/*
 * Generalized function for decoding the primitive values.
 */
asn_dec_rval_t xer_decode_general(
    asn_codec_ctx_t *opt_codec_ctx,
    asn_struct_ctx_t *ctx,                 /* Type decoder context */
    void *struct_key, const char *xml_tag, /* Expected XML tag */
    const void *buf_ptr, size_t size,
    int (*opt_unexpected_tag_decoder)(void *struct_key, const void *chunk_buf,
        size_t chunk_size),
    ssize_t (*body_receiver)(void *struct_key, const void *chunk_buf,
        size_t chunk_size, int have_more))
{
    asn_dec_rval_t rval;
    ssize_t consumed_myself = 0;

    (void)opt_codec_ctx;

    /*
     * Phases of XER/XML processing:
     * Phase 0: Check that the opening tag matches our expectations.
     * Phase 1: Processing body and reacting on closing tag.
     */
    if (ctx->phase > 1)
        {
            RETURN(RC_FAIL);
        }
    for (;;)
        {
            pxer_chunk_type_e ch_type; /* XER chunk type */
            ssize_t ch_size;           /* Chunk size */
            xer_check_tag_e tcv;       /* Tag check value */

            /*
             * Get the next part of the XML stream.
             */
            ch_size = xer_next_token(&ctx->context, buf_ptr, size, &ch_type);
            switch (ch_size)
                {
                case -1:
                    RETURN(RC_FAIL);
                case 0:
                    RETURN(RC_WMORE);
                default:
                    switch (ch_type)
                        {
                        case PXER_COMMENT:    /* Got XML comment */
                            ADVANCE(ch_size); /* Skip silently */
                            continue;
                        case PXER_TEXT:
                            if (ctx->phase == 0)
                                {
                                    /*
                                     * We have to ignore whitespace
                                     * here, but in order to be forward
                                     * compatible with EXTENDED-XER
                                     * (EMBED-VALUES, #25) any text is
                                     * just ignored here.
                                     */
                                }
                            else
                                {
                                    XER_GOT_BODY(buf_ptr, ch_size,
                                        size);
                                }
                            ADVANCE(ch_size);
                            continue;
                        case PXER_TAG:
                            break; /* Check the rest down there */
                        }
                }

            assert(ch_type == PXER_TAG && size);

            tcv = xer_check_tag(buf_ptr, ch_size, xml_tag);
            /*
             * Phase 0:
             *     Expecting the opening tag
             *     for the type being processed.
             * Phase 1:
             *     Waiting for the closing XML tag.
             */
            switch (tcv)
                {
                case XCT_BOTH:
                    if (ctx->phase)
                        {
                            break;
                        }
                    /* Finished decoding of an empty element */
                    XER_GOT_EMPTY();
                    ADVANCE(ch_size);
                    ctx->phase = 2; /* Phase out */
                    RETURN(RC_OK);
                case XCT_OPENING:
                    if (ctx->phase)
                        {
                            break;
                        }
                    ADVANCE(ch_size);
                    ctx->phase = 1; /* Processing body phase */
                    continue;
                case XCT_CLOSING:
                    if (!ctx->phase)
                        {
                            break;
                        }
                    ADVANCE(ch_size);
                    ctx->phase = 2; /* Phase out */
                    RETURN(RC_OK);
                case XCT_UNKNOWN_BO:
                    /*
                     * Certain tags in the body may be expected.
                     */
                    if (opt_unexpected_tag_decoder &&
                        opt_unexpected_tag_decoder(struct_key, buf_ptr,
                            ch_size) >= 0)
                        {
                            /* Tag's processed fine */
                            ADVANCE(ch_size);
                            if (!ctx->phase)
                                {
                                    /* We are not expecting
                                     * the closing tag anymore. */
                                    ctx->phase = 2; /* Phase out */
                                    RETURN(RC_OK);
                                }
                            continue;
                        }
                    /* Fall through */
                default:
                    break; /* Unexpected tag */
                }

            ASN_DEBUG("Unexpected XML tag (expected \"%s\")", xml_tag);
            break; /* Dark and mysterious things have just happened */
        }

    RETURN(RC_FAIL);
}

int xer_is_whitespace(const void *chunk_buf, size_t chunk_size)
{
    const char *p = (const char *)chunk_buf;
    const char *pend = p + chunk_size;

    for (; p < pend; p++)
        {
            switch (*p)
                {
                /* X.693, #8.1.4
                 * HORISONTAL TAB (9)
                 * LINE FEED (10)
                 * CARRIAGE RETURN (13)
                 * SPACE (32)
                 */
                case 0x09:
                case 0x0a:
                case 0x0d:
                case 0x20:
                    break;
                default:
                    return 0;
                }
        }
    return 1; /* All whitespace */
}

/*
 * This is a vastly simplified, non-validating XML tree skipper.
 */
int xer_skip_unknown(xer_check_tag_e tcv, ber_tlv_len_t *depth)
{
    assert(*depth > 0);
    switch (tcv)
        {
        case XCT_BOTH:
        case XCT_UNKNOWN_BO:
            /* These negate each other. */
            return 0;
        case XCT_OPENING:
        case XCT_UNKNOWN_OP:
            ++(*depth);
            return 0;
        case XCT_CLOSING:
        case XCT_UNKNOWN_CL:
            if (--(*depth) == 0)
                {
                    return (tcv == XCT_CLOSING) ? 2 : 1;
                }
            return 0;
        default:
            return -1;
        }
}
