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

/*
 * Decode an always-primitive type.
 */
asn_dec_rval_t ber_decode_primitive(asn_codec_ctx_t *opt_codec_ctx,
    asn_TYPE_descriptor_t *td, void **sptr,
    const void *buf_ptr, size_t size,
    int tag_mode)
{
    ASN__PRIMITIVE_TYPE_t *st = (ASN__PRIMITIVE_TYPE_t *)*sptr;
    asn_dec_rval_t rval;
    ber_tlv_len_t length;

    /*
     * If the structure is not there, allocate it.
     */
    if (st == NULL)
        {
            st = (ASN__PRIMITIVE_TYPE_t *)CALLOC(1, sizeof(*st));
            if (st == NULL)
                {
                    _ASN_DECODE_FAILED;
                }
            *sptr = (void *)st;
        }

    ASN_DEBUG("Decoding %s as plain primitive (tm=%d)", td->name, tag_mode);

    /*
     * Check tags and extract value length.
     */
    rval = ber_check_tags(opt_codec_ctx, td, 0, buf_ptr, size, tag_mode, 0,
        &length, 0);
    if (rval.code != RC_OK)
        {
            return rval;
        }

    ASN_DEBUG("%s length is %d bytes", td->name, (int)length);

    /*
     * Make sure we have this length.
     */
    buf_ptr = ((const char *)buf_ptr) + rval.consumed;
    size -= rval.consumed;
    if (length > (ber_tlv_len_t)size)
        {
            rval.code = RC_WMORE;
            rval.consumed = 0;
            return rval;
        }

    st->size = (int)length;
    /* The following better be optimized away. */
    if (sizeof(st->size) != sizeof(length) && (ber_tlv_len_t)st->size != length)
        {
            st->size = 0;
            _ASN_DECODE_FAILED;
        }

    st->buf = (uint8_t *)MALLOC(length + 1);
    if (!st->buf)
        {
            st->size = 0;
            _ASN_DECODE_FAILED;
        }

    memcpy(st->buf, buf_ptr, length);
    st->buf[length] = '\0'; /* Just in case */

    rval.code = RC_OK;
    rval.consumed += length;

    ASN_DEBUG("Took %ld/%ld bytes to encode %s", (long)rval.consumed,
        (long)length, td->name);

    return rval;
}

/*
 * Encode an always-primitive type using DER.
 */
asn_enc_rval_t der_encode_primitive(asn_TYPE_descriptor_t *td, void *sptr,
    int tag_mode, ber_tlv_tag_t tag,
    asn_app_consume_bytes_f *cb, void *app_key)
{
    asn_enc_rval_t erval;
    ASN__PRIMITIVE_TYPE_t *st = (ASN__PRIMITIVE_TYPE_t *)sptr;

    ASN_DEBUG("%s %s as a primitive type (tm=%d)",
        cb ? "Encoding" : "Estimating", td->name, tag_mode);

    erval.encoded = der_write_tags(td, st->size, tag_mode, 0, tag, cb, app_key);
    ASN_DEBUG("%s wrote tags %d", td->name, (int)erval.encoded);
    if (erval.encoded == -1)
        {
            erval.failed_type = td;
            erval.structure_ptr = sptr;
            return erval;
        }

    if (cb && st->buf)
        {
            if (cb(st->buf, st->size, app_key) < 0)
                {
                    erval.encoded = -1;
                    erval.failed_type = td;
                    erval.structure_ptr = sptr;
                    return erval;
                }
        }
    else
        {
            assert(st->buf || st->size == 0);
        }

    erval.encoded += st->size;
    _ASN_ENCODED_OK(erval);
}

void ASN__PRIMITIVE_TYPE_free(asn_TYPE_descriptor_t *td, void *sptr,
    int contents_only)
{
    ASN__PRIMITIVE_TYPE_t *st = (ASN__PRIMITIVE_TYPE_t *)sptr;

    if (!td || !sptr)
        {
            return;
        }

    ASN_DEBUG("Freeing %s as a primitive type", td->name);

    if (st->buf)
        {
            FREEMEM(st->buf);
        }

    if (!contents_only)
        {
            FREEMEM(st);
        }
}

/*
 * Local internal type passed around as an argument.
 */
struct xdp_arg_s
{
    asn_TYPE_descriptor_t *type_descriptor;
    void *struct_key;
    xer_primitive_body_decoder_f *prim_body_decoder;
    int decoded_something;
    int want_more;
};

static int xer_decode__unexpected_tag(void *key, const void *chunk_buf,
    size_t chunk_size)
{
    struct xdp_arg_s *arg = (struct xdp_arg_s *)key;
    enum xer_pbd_rval bret;

    if (arg->decoded_something)
        {
            if (xer_is_whitespace(chunk_buf, chunk_size))
                {
                    return 0; /* Skip it. */
                }
            /*
             * Decoding was done once already. Prohibit doing it again.
             */
            return -1;
        }

    bret = arg->prim_body_decoder(arg->type_descriptor, arg->struct_key,
        chunk_buf, chunk_size);
    switch (bret)
        {
        case XPBD_SYSTEM_FAILURE:
        case XPBD_DECODER_LIMIT:
        case XPBD_BROKEN_ENCODING:
            break;
        case XPBD_BODY_CONSUMED:
            /* Tag decoded successfully */
            arg->decoded_something = 1;
            /* Fall through */
        case XPBD_NOT_BODY_IGNORE: /* Safe to proceed further */
            return 0;
        }

    return -1;
}

static ssize_t xer_decode__body(void *key, const void *chunk_buf,
    size_t chunk_size, int have_more)
{
    struct xdp_arg_s *arg = (struct xdp_arg_s *)key;
    enum xer_pbd_rval bret;

    if (arg->decoded_something)
        {
            if (xer_is_whitespace(chunk_buf, chunk_size))
                {
                    return chunk_size;
                }
            /*
             * Decoding was done once already. Prohibit doing it again.
             */
            return -1;
        }

    if (!have_more)
        {
            /*
             * If we've received something like "1", we can't really
             * tell whether it is really `1` or `123`, until we know
             * that there is no more data coming.
             * The have_more argument will be set to 1 once something
             * like this is available to the caller of this callback:
             * "1<tag_start..."
             */
            arg->want_more = 1;
            return -1;
        }

    bret = arg->prim_body_decoder(arg->type_descriptor, arg->struct_key,
        chunk_buf, chunk_size);
    switch (bret)
        {
        case XPBD_SYSTEM_FAILURE:
        case XPBD_DECODER_LIMIT:
        case XPBD_BROKEN_ENCODING:
            break;
        case XPBD_BODY_CONSUMED:
            /* Tag decoded successfully */
            arg->decoded_something = 1;
            /* Fall through */
        case XPBD_NOT_BODY_IGNORE: /* Safe to proceed further */
            return chunk_size;
        }

    return -1;
}

asn_dec_rval_t xer_decode_primitive(
    asn_codec_ctx_t *opt_codec_ctx, asn_TYPE_descriptor_t *td, void **sptr,
    size_t struct_size, const char *opt_mname, const void *buf_ptr, size_t size,
    xer_primitive_body_decoder_f *prim_body_decoder)
{
    const char *xml_tag = opt_mname ? opt_mname : td->xml_tag;
    asn_struct_ctx_t s_ctx;
    struct xdp_arg_s s_arg;
    asn_dec_rval_t rc;

    /*
     * Create the structure if does not exist.
     */
    if (!*sptr)
        {
            *sptr = CALLOC(1, struct_size);
            if (!*sptr)
                {
                    _ASN_DECODE_FAILED;
                }
        }

    memset(&s_ctx, 0, sizeof(s_ctx));
    s_arg.type_descriptor = td;
    s_arg.struct_key = *sptr;
    s_arg.prim_body_decoder = prim_body_decoder;
    s_arg.decoded_something = 0;
    s_arg.want_more = 0;

    rc = xer_decode_general(opt_codec_ctx, &s_ctx, &s_arg, xml_tag, buf_ptr,
        size, xer_decode__unexpected_tag, xer_decode__body);
    switch (rc.code)
        {
        case RC_OK:
            if (!s_arg.decoded_something)
                {
                    char ch;
                    ASN_DEBUG(
                        "Primitive body is not recognized, "
                        "supplying empty one");
                    /*
                     * Decoding opportunity has come and gone.
                     * Where's the result?
                     * Try to feed with empty body, see if it eats it.
                     */
                    if (prim_body_decoder(s_arg.type_descriptor,
                            s_arg.struct_key, &ch,
                            0) != XPBD_BODY_CONSUMED)
                        {
                            /*
                             * This decoder does not like empty stuff.
                             */
                            _ASN_DECODE_FAILED;
                        }
                }
            break;
        case RC_WMORE:
            /*
             * Redo the whole thing later.
             * We don't have a context to save intermediate parsing state.
             */
            rc.consumed = 0;
            break;
        case RC_FAIL:
            rc.consumed = 0;
            if (s_arg.want_more)
                {
                    rc.code = RC_WMORE;
                }
            else
                {
                    _ASN_DECODE_FAILED;
                }
            break;
        }
    return rc;
}
