/*-
 * Copyright (c) 2004, 2005 Lev Walkin <vlm@lionet.info>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $Id$
 */
#include "sys-common.h"

#include <asn1parser.h>		/* For static string tables */

#include <asn_application.h>
#include <constraints.c>
#include <ber_tlv_tag.c>
#include <ber_tlv_length.c>

#undef  COPYRIGHT
#define COPYRIGHT       \
	"Copyright (c) 2004, 2005 Lev Walkin <vlm@lionet.info>\n"

static void usage(const char *av0, int);/* Print the Usage screen and exit */
static int process(const char *fname);	/* Perform the BER decoding */
static int process_line(const char *fname, char *line, int lineno);

static int no_validation;	/* -n */

int
main(int ac, char **av) {
	int ch;				/* Command line character */
	int i;				/* Index in some loops */

	/*
	 * Process command-line options.
	 */
	while((ch = getopt(ac, av, "nhv")) != -1)
	switch(ch) {
	case 'n':
		no_validation++;
		break;
	case 'v':
		usage(av[0], 1);
		break;
	case 'h':
	default:
		usage(av[0], 0);
	}

	/*
	 * Ensure that there are some input files present.
	 */
	if(ac > optind) {
		ac -= optind;
		av += optind;
	} else {
		fprintf(stderr, "%s: No input files specified\n", av[0]);
		exit(1);
	}

	setvbuf(stdout, 0, _IOLBF, 0);

	/*
	 * Iterate over input files and parse each.
	 * All syntax trees from all files will be bundled together.
	 */
	for(i = 0; i < ac; i++) {
		if(process(av[i]))
			exit(EX_DATAERR);
	}

	return 0;
}

/*
 * Print the usage screen and exit(EX_USAGE).
 */
static void
usage(const char *av0, int copyright_only) {
	fprintf(stderr, 
		"Convert unber(1)'s output back into BER, "
			"v" VERSION "\n" COPYRIGHT);
	if(copyright_only) exit(0);
	fprintf(stderr,
		"Usage: %s [-n] [-] [file ...]\n"
		"Options:\n"
		"  -n      Disable XML input validation\n", av0);
	exit(EX_USAGE);
}

/*
 * Open the file and initiate recursive processing.
 */
static int
process(const char *fname) {
	char buf[8192];
	char *collector = 0;
	size_t collector_size = sizeof(buf);
	size_t collector_offset = 0;
	int lineno = 0;
	FILE *fp;

	if(strcmp(fname, "-")) {
		fp = fopen(fname, "r");
		if(!fp) {
			perror(fname);
			return -1;
		}
	} else {
		fp = stdin;
	}


	while(fgets(buf, sizeof(buf), fp) || !feof(fp)) {
		size_t len = strlen(buf);

		if(!len) continue;
		if(collector_offset || buf[len-1] != '\n') {
			if((collector_size - collector_offset) <= len
			|| !collector) {
				collector_size <<= 1;
				collector = REALLOC(collector, collector_size);
				if(!collector) {
					perror("realloc()");
					exit(EX_OSERR);
				}
			}
			memcpy(collector + collector_offset, buf, len + 1);
			collector_offset += len;
		}
		if(buf[len-1] != '\n') continue;

		if(collector_offset) {
			assert(collector[collector_offset-1] == '\n');
			process_line(fname, collector, ++lineno);
			collector_offset = 0;
		} else {
			process_line(fname, buf, ++lineno);
		}
	}

	if(fp != stdin)
		fclose(fp);

	return 0;
}

static int
process_line(const char *fname, char *line, int lineno) {
	char buf[32];
	char *op;	/* '<' */
	char *cl;	/* '>' */
	char *tcl_pos;	/* tag class (T=") position */
	char *tl_pos;	/* tag length (TL=") position */
	char *v_pos;	/* value length (V=") position */
	int constr;
	ber_tlv_tag_t tag_value;
	ber_tlv_tag_t tag_class;
	ber_tlv_tag_t tlv_tag;
	ber_tlv_len_t tlv_len;
	ber_tlv_len_t opt_tl_len;	/* optional TL length */
	ssize_t ret;
	(void)fname;

	/* Skip the whitespace */
	for(; *line == ' ' || *line == '\t'; line++);

	/* Find a tag opening angle bracket */
	op = line;
	switch(*op) {
	case '<':	/* That's what we want! A tag opening */
		break;
	case '-':	/* This is a comment (dash-dash) */
		if(op[1] == *op)
	case '\r':
	case '\n':
	case '#':	/* This is a comment */
			return 0;
	default:
		fprintf(stderr,
			"%s: Missing '<' after whitespace at line %d\n",
			fname, lineno);
		exit(EX_DATAERR);
	}

	/* Find a tag closing angle bracket */
	for(; *line && *line != '>'; line++) {
		if(*line < ' ') {
			fprintf(stderr, "%s: Invalid charset (%d) at line %d\n",
				fname, *(const unsigned char *)line, lineno);
			exit(EX_DATAERR);
		}
	}
	cl = line;
	if(*cl != '>') {
		fprintf(stderr, "%s: Missing '>' at line %d\n", fname, lineno);
		exit(EX_DATAERR);
	}

	/* Ignore closing tags */
	if(op[1] == '/') {
		if(strchr(cl, '<')) {	/* We are not very robust */
			fprintf(stderr,
				"%s: Multiple tags per line at line %d\n",
				fname, lineno);
			exit(EX_DATAERR);
		}
		/* End-of-content octets */
		if(op[2] == 'I') {
			buf[0] = buf[1] = 0x00;
			fwrite(buf, 1, 2, stdout);
		}
		return 0;
	}

	switch(op[1]) {
	case '!': return 0;	/* A comment */
	case '?': return 0;	/* An XML preamble */
	case 'C': constr = 1; break;
	case 'P': constr = 0; break;
	case 'I': constr = 2; break;
	default:
		fprintf(stderr,
			"%s: Expected \"C\"/\"P\"/\"I\" as the XML tag name (%c) at line %d\n",
				fname, op[1], lineno);
		exit(EX_DATAERR);
	}

	*cl = '\0';
	if(cl[-1] == 'F') {
		fprintf(stderr,
			"%s: Detected pretty-printing of primitive types at line %d. "
			"Re-run `unber` with -p option to disable pretty-printing.\n", fname, lineno);
		exit(EX_DATAERR);
	}

	tcl_pos = strstr(op, "T=\"[");
	tl_pos = strstr(op, "TL=\"");
	v_pos = strstr(op, "V=\"");
	if(!tcl_pos || (!v_pos && constr != 2)) {
		fprintf(stderr,
			"%s: Mandatory attribute %s is not found at line %d\n",
			fname, (!tcl_pos)?"T":"V", lineno);
		exit(EX_DATAERR);
	}
	errno = 0;
	opt_tl_len = tl_pos ? strtoul(tl_pos + 4, 0, 10) : 0;
	if(constr == 2) {
		tlv_len = 0;
	} else {
		tlv_len = strtoul(v_pos + 3, 0, 10);
	}
	if(errno || (opt_tl_len && opt_tl_len < 2) || tlv_len < 0) {
		fprintf(stderr, "%s: Invalid TL or V value at line %d\n",
			fname, lineno);
		exit(EX_DATAERR);
	}

	tcl_pos += 4;
	switch(*tcl_pos) {
	case 'U':	/* UNIVERSAL */
		tag_class = ASN_TAG_CLASS_UNIVERSAL; break;
	case 'P':	/* PRIVATE */
		tag_class = ASN_TAG_CLASS_PRIVATE; break;
	case 'A':	/* APPLICATION */
		tag_class = ASN_TAG_CLASS_APPLICATION; break;
	case '0': case '1': case '2': case '3': case '4':
	case '5': case '6': case '7': case '8': case '9':	/* context */
		tag_class = ASN_TAG_CLASS_CONTEXT; break;
	default:
		fprintf(stderr, "%s: Invalid tag class (%c) at line %d\n",
			fname, tcl_pos[4], lineno);
		exit(EX_DATAERR);
	}
	for(;; tcl_pos++) {
		switch(*tcl_pos) {
		case '"': tcl_pos = "";
		case '\0':
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
			break;
		default: continue;
		}
		break;
	}
	errno = 0;
	if(!*tcl_pos
	|| ((long)(tag_value = strtoul(tcl_pos, 0, 10))) < 0
	|| errno) {
		fprintf(stderr, "%s: Invalid tag value (%c) at line %d\n",
			fname, *tcl_pos, lineno);
		exit(EX_DATAERR);
	}
	tlv_tag = ((tag_value << 2) | tag_class);

	ret = ber_tlv_tag_serialize(tlv_tag, buf, sizeof(buf));
	assert(ret >= 1 && (size_t)ret < sizeof(buf));
	if(constr == 2) {
		buf[ret] = 0x80;
		ret += 1;
	} else {
		ret += der_tlv_length_serialize(tlv_len,
			buf + ret, sizeof(buf) - ret);
		assert(ret >= 2 && (size_t)ret < sizeof(buf));
	}
	if(opt_tl_len && ret != opt_tl_len) {
		fprintf(stderr, "%s: Cannot encode TL at line %d "
			"in the given number of bytes (%ld!=%ld)\n",
			fname, lineno, (long)ret, (long)opt_tl_len);
		exit(EX_DATAERR);
	}
	if(constr) *buf |= 0x20;	/* Enable "constructed" bit */
	fwrite(buf, 1, ret, stdout);

	if(!constr) {
	  ber_tlv_len_t len;
	  for(len = 0, cl++; *cl && *cl != '<'; cl++, len++) {
		unsigned char v;
		int h;
		if(*cl != '&') {
			fputc(*cl, stdout);
			continue;
		}
		cl++;
		if(*cl != '#') {
			fputc(*cl, stdout);
			continue;
		}
		cl++;
		if(*cl != 'x') {
			fprintf(stderr, "%s: Expected \"&#xNN;\" at line %d\n",
				fname, lineno);
			exit(EX_DATAERR);
		}
		for(v = 0, h = 0; h < 2; h++) {
			unsigned char clv = *++cl;
			v <<= 4;
			switch(clv) {
			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
				v |= clv - '0'; break;
			case 'A': case 'B': case 'C':
			case 'D': case 'E': case 'F':
				v |= clv - 'A' + 10; break;
			case 'a': case 'b': case 'c':
			case 'd': case 'e': case 'f':
				v |= clv - 'a' + 10; break;
			default:
				fprintf(stderr,
					"%s: Expected \"&#xNN;\" at line %d (%c)\n",
					fname, lineno, clv);
				exit(EX_DATAERR);
			}
		}
		cl++;
		if(*cl != ';') {
			fprintf(stderr,
				"%s: Expected \"&#xNN;\" at line %d\n",
				fname, lineno);
			exit(EX_DATAERR);
		}
		fputc(v, stdout);
	  }
	  if(len != tlv_len) {
		if(no_validation) fprintf(stderr, "Warning: ");
		fprintf(stderr,
			"%s: Could not encode value of %ld chars "
			"at line %d in %ld bytes\n",
			fname, (long)len, lineno, (long)tlv_len);
		if(!no_validation) exit(EX_DATAERR);
	  }
	}
	
	return 0;
}