/*
 * Copyright (c) 2004-2017 Lev Walkin <vlm@lionet.info>. All rights reserved.
 * Redistribution and modifications are permitted subject to BSD license.
 */
#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(
    const asn_codec_ctx_t* opt_codec_ctx, const 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->op->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 */
    *ch_type = PXER_WMORE;
    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:
      *ch_type = PXER_WMORE;
      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, 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(
    const 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);
    if (ch_size == -1) {
      RETURN(RC_FAIL);
    } else {
      switch (ch_type) {
        case PXER_WMORE:
          RETURN(RC_WMORE);
        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);
}

size_t xer_whitespace_span(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:
        continue;
      default:
        break;
    }
    break;
  }
  return (p - (const char*) chunk_buf);
}

/*
 * 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;
  }
}
