/*
 * Copyright (c) 2004, 2005 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(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, 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);
}


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