unber and enber

diff --git a/asn1c/Makefile.am b/asn1c/Makefile.am
index a5caec3..1422266 100644
--- a/asn1c/Makefile.am
+++ b/asn1c/Makefile.am
@@ -1,5 +1,5 @@
 
-SUBDIRS = . tests webcgi
+SUBDIRS = . webcgi tests
 
 AM_CFLAGS = @ADD_CFLAGS@
 AM_CPPFLAGS = \
@@ -16,9 +16,9 @@
 	$(top_builddir)/libasn1fix/libasn1fix.la	\
 	$(top_builddir)/libasn1compiler/libasn1compiler.la
 
-bin_PROGRAMS = asn1c unber
+bin_PROGRAMS = asn1c unber enber
 
-dist_man1_MANS = asn1c.1 unber.1
+dist_man1_MANS = asn1c.1 unber.1 enber.1
 
 check_SCRIPTS = check-parsing.sh
 TESTS = check-parsing.sh
diff --git a/asn1c/Makefile.in b/asn1c/Makefile.in
index 73c25f3..dd6652a 100644
--- a/asn1c/Makefile.in
+++ b/asn1c/Makefile.in
@@ -14,7 +14,7 @@
 
 @SET_MAKE@
 
-SOURCES = asn1c.c unber.c
+SOURCES = asn1c.c enber.c unber.c
 
 srcdir = @srcdir@
 top_srcdir = @top_srcdir@
@@ -37,7 +37,7 @@
 PRE_UNINSTALL = :
 POST_UNINSTALL = :
 host_triplet = @host@
-bin_PROGRAMS = asn1c$(EXEEXT) unber$(EXEEXT)
+bin_PROGRAMS = asn1c$(EXEEXT) unber$(EXEEXT) enber$(EXEEXT)
 subdir = asn1c
 DIST_COMMON = README $(dist_man1_MANS) $(srcdir)/Makefile.am \
 	$(srcdir)/Makefile.in
@@ -57,13 +57,17 @@
 	$(top_builddir)/libasn1print/libasn1print.la \
 	$(top_builddir)/libasn1fix/libasn1fix.la \
 	$(top_builddir)/libasn1compiler/libasn1compiler.la
+enber_SOURCES = enber.c
+enber_OBJECTS = enber.$(OBJEXT)
+enber_LDADD = $(LDADD)
 unber_SOURCES = unber.c
 unber_OBJECTS = unber.$(OBJEXT)
 unber_LDADD = $(LDADD)
 DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir)
 depcomp = $(SHELL) $(top_srcdir)/depcomp
 am__depfiles_maybe = depfiles
-@AMDEP_TRUE@DEP_FILES = ./$(DEPDIR)/asn1c.Po ./$(DEPDIR)/unber.Po
+@AMDEP_TRUE@DEP_FILES = ./$(DEPDIR)/asn1c.Po ./$(DEPDIR)/enber.Po \
+@AMDEP_TRUE@	./$(DEPDIR)/unber.Po
 COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
 	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
 LTCOMPILE = $(LIBTOOL) --mode=compile $(CC) $(DEFS) \
@@ -72,8 +76,8 @@
 CCLD = $(CC)
 LINK = $(LIBTOOL) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
 	$(AM_LDFLAGS) $(LDFLAGS) -o $@
-SOURCES = asn1c.c unber.c
-DIST_SOURCES = asn1c.c unber.c
+SOURCES = asn1c.c enber.c unber.c
+DIST_SOURCES = asn1c.c enber.c unber.c
 RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
 	html-recursive info-recursive install-data-recursive \
 	install-exec-recursive install-info-recursive \
@@ -196,7 +200,7 @@
 target_cpu = @target_cpu@
 target_os = @target_os@
 target_vendor = @target_vendor@
-SUBDIRS = . tests webcgi
+SUBDIRS = . webcgi tests
 AM_CFLAGS = @ADD_CFLAGS@
 AM_CPPFLAGS = \
 	-I${top_srcdir}/libasn1compiler	\
@@ -212,7 +216,7 @@
 	$(top_builddir)/libasn1fix/libasn1fix.la	\
 	$(top_builddir)/libasn1compiler/libasn1compiler.la
 
-dist_man1_MANS = asn1c.1 unber.1
+dist_man1_MANS = asn1c.1 unber.1 enber.1
 check_SCRIPTS = check-parsing.sh
 TESTS = check-parsing.sh
 EXTRA_DIST = check-parsing.sh
@@ -281,6 +285,9 @@
 asn1c$(EXEEXT): $(asn1c_OBJECTS) $(asn1c_DEPENDENCIES) 
 	@rm -f asn1c$(EXEEXT)
 	$(LINK) $(asn1c_LDFLAGS) $(asn1c_OBJECTS) $(asn1c_LDADD) $(LIBS)
+enber$(EXEEXT): $(enber_OBJECTS) $(enber_DEPENDENCIES) 
+	@rm -f enber$(EXEEXT)
+	$(LINK) $(enber_LDFLAGS) $(enber_OBJECTS) $(enber_LDADD) $(LIBS)
 unber$(EXEEXT): $(unber_OBJECTS) $(unber_DEPENDENCIES) 
 	@rm -f unber$(EXEEXT)
 	$(LINK) $(unber_LDFLAGS) $(unber_OBJECTS) $(unber_LDADD) $(LIBS)
@@ -292,6 +299,7 @@
 	-rm -f *.tab.c
 
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/asn1c.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/enber.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unber.Po@am__quote@
 
 .c.o:
diff --git a/asn1c/asn1c.1 b/asn1c/asn1c.1
index d6be945..359724b 100644
--- a/asn1c/asn1c.1
+++ b/asn1c/asn1c.1
@@ -1,6 +1,3 @@
-.de Id
-..
-.Id $Id"
 .TH ASN1C 1 "\*(Dt" "ASN.1 Compiler" "ASN.1 Compiler"
 .SH NAME
 asn1c \- ASN.1 Compiler
@@ -100,6 +97,6 @@
 Generate "-- #line" comments in \fB-E\fR output.
 .SH SEE ALSO
 .TP
-\&\fIunber\fR\|(1)
+\&\fIunber\fR\|(1), \&\fIenber\fR\|(1)
 .SH AUTHORS
 Lev Walkin <vlm@lionet.info>
diff --git a/asn1c/asn1c.c b/asn1c/asn1c.c
index e3097bd..8c3a7e5 100644
--- a/asn1c/asn1c.c
+++ b/asn1c/asn1c.c
@@ -1,3 +1,30 @@
+/*-
+ * Copyright (c) 2003, 2004 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$
+ */
 /*
  * This is the program that connects the libasn1* libraries together.
  * It uses them in turn to parse, fix and then compile or print the ASN.1 tree.
diff --git a/asn1c/enber.1 b/asn1c/enber.1
new file mode 100644
index 0000000..b398433
--- /dev/null
+++ b/asn1c/enber.1
@@ -0,0 +1,29 @@
+.de Vb
+.sp
+.ft CW
+.nf
+..
+.de Ve
+.ft R
+.fi
+.sp
+..
+.TH ENBER 1 "\*(Dt" "unber counterpart" "unber counterpart"
+.SH NAME
+enber \- Convert the unber XML output back into BER
+.SH SYNOPSIS
+enber [\fB-\fR] \fIinfile\fR...
+.SH DESCRIPTION
+enber takes the XML-encoded files produced by \fIunber\fR\|(1) and converts
+them back into the BER format.
+A single dash represents the standard input.
+.SH EXAMPLES
+Decode the BER sequence and immediately encode it back
+.Vb
+\&    enber \fB-p\fR \fIfilename.ber\fR | unber \fB-\fR > \fIreconstructed.ber\fR
+.Ve
+.SH SEE ALSO
+.TP
+\&\fIunber\fR\|(1), \&\fIasn1c\fR\|(1)
+.SH AUTHORS
+Lev Walkin <vlm@lionet.info>
diff --git a/asn1c/enber.c b/asn1c/enber.c
new file mode 100644
index 0000000..45fda43
--- /dev/null
+++ b/asn1c/enber.c
@@ -0,0 +1,381 @@
+/*-
+ * Copyright (c) 2004 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sysexits.h>		/* for EX_USAGE */
+#include <assert.h>
+#include <errno.h>
+
+#ifdef	HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <asn1parser.h>		/* For static string tables */
+
+#include <asn_application.h>
+#include <constraints.c>
+#include <ber_tlv_tag.c>
+#include <ber_tlv_length.c>
+
+static void usage(const char *av0);	/* 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);
+
+#undef	COPYRIGHT
+#define	COPYRIGHT	\
+	"Copyright (c) 2004 Lev Walkin <vlm@lionet.info>\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, "hv")) != -1)
+	switch(ch) {
+	case 'v':
+		fprintf(stderr, "ASN.1 BER Decoder, v" VERSION "\n" COPYRIGHT);
+		exit(0);
+		break;
+	case 'h':
+	default:
+		usage(av[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) {
+	fprintf(stderr,
+"Convertor of under(1) output back into BER, v" VERSION "\n" COPYRIGHT
+"Usage: %s [-] [file ...]\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 position */
+	char *tl_pos;
+	char *v_pos;
+	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 tl_len;
+	ssize_t ret;
+	(void)fname;
+
+	/* Find a tag opening angle bracket */
+	for(; *line == ' '; line++);
+	op = line;
+	if(*op != '<') {
+		fprintf(stderr, "%s: Missing '<' after whitespace\n", fname);
+		exit(EX_DATAERR);
+	}
+
+	/* Find a tag closing angle bracket */
+	for(; *line && *line != '>'; line++) {
+		if(*line < ' ') {
+			fprintf(stderr, "%s: Invalid charset (%d)\n",
+				fname, *(const unsigned char *)line);
+			exit(EX_DATAERR);
+		}
+	}
+	cl = line;
+	if(*cl != '>') {
+		fprintf(stderr, "%s: Missing '>'\n", fname);
+		exit(EX_DATAERR);
+	}
+
+	/* Ignore closing tags */
+	if(op[1] == '/') {
+		if(strchr(cl, '<')) {	/* We are not very robust */
+			fprintf(stderr, "%s: Multiple tags per line\n", fname);
+			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 '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)\n",
+				fname, op[1]);
+		exit(EX_DATAERR);
+	}
+
+	*cl = '\0';
+	if(cl[-1] == 'F') {
+		fprintf(stderr, "%s: Uses pretty-printing of values. "
+			"Use -p option to unber\n", fname);
+		exit(EX_DATAERR);
+	}
+
+	tcl_pos = strstr(op, "T=\"[");
+	tl_pos = strstr(op, "TL=\"");
+	v_pos = strstr(op, "V=\"");
+	if(!tcl_pos || !tl_pos || !v_pos) {
+		fprintf(stderr,
+			"%s: Mandatory attribute %s is not found at line %d\n",
+			fname, (!tcl_pos)?"T":(v_pos?"V":"TCL"), lineno);
+		exit(EX_DATAERR);
+	}
+	errno = 0;
+	tl_len = strtoul(tl_pos + 4, 0, 10);
+	if(constr == 2) {
+		tlv_len = 0;
+	} else {
+		tlv_len = strtoul(v_pos + 3, 0, 10);
+	}
+	if(errno || 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);
+
+	if(0) {
+		printf("[%s>]\n", op);
+		printf(" <%c T=\"%s\" TL=\"%d\" V=\"%d\">\n",
+			constr?'C':'P',
+			ber_tlv_tag_string(tlv_tag),
+			tl_len, tlv_len);
+	}
+
+	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(ret != tl_len) {
+		fprintf(stderr, "%s: Cannot encode TL at line %d "
+			"in the given number of bytes (%d!=%d)\n",
+			fname, lineno, ret, tl_len);
+		exit(EX_DATAERR);
+	}
+	if(constr) *buf |= 0x20;	/* Enable "constructed" bit */
+	fwrite(buf, 1, tl_len, 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 != '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) {
+		fprintf(stderr,
+			"%s: Could not encode value of %d chars "
+			"at line %d in %d bytes\n",
+			fname, len, lineno, tlv_len);
+		exit(EX_DATAERR);
+	  }
+	}
+	
+	return 0;
+}
+
diff --git a/asn1c/unber.1 b/asn1c/unber.1
index ec731a0..a96b0f5 100644
--- a/asn1c/unber.1
+++ b/asn1c/unber.1
@@ -1,22 +1,95 @@
-.de Id
+.de Vb
+.sp
+.ft CW
+.nf
 ..
-.Id $Id"
+.de Ve
+.ft R
+.fi
+.sp
+..
 .TH UNBER 1 "\*(Dt" "ASN.1 BER Decoder" "ASN.1 BER Decoder"
 .SH NAME
 unber \- ASN.1 BER Decoder
 .SH SYNOPSIS
-unber [\fB\-t\fR\fIdata-string\fR] [\fB-\fR] \fIinfile\fR...
+unber [\fB-1\fR] [\fB-i\fRindent] [\fB-p\fR] [\fB\-t\fR\fIdata-string\fR] [\fB-\fR] \fIinfile\fR...
 .SH DESCRIPTION
-unber takes the BER-encoded files and dumps their internal structure to stdout.
-(The DER and CER formats are both subsets of the BER, and are also supported.)
+unber takes the BER-encoded files and dumps their internal structure as human readable text.
 A single dash represents the standard input.
+.sp
+(The DER and CER formats are both subsets of the BER, and are also supported.)
 .SH OPTIONS
 .TP
+\fB\-1\fR
+Do \fInot\fR attempt to read the next BER structure after the first one.
+This may be useful if the input contains garbage past the single BER sequence.
+By default, unber continues decoding until the end of file (input stream).
+.TP
+\fB\-i\fR \fIindent\fR
+Use the specified number of spaces for output indentation. Default is 4 spaces.
+.TP
+\fB\-p\fR
+Do \fInot\fR attempt pretty-printing of known ASN.1 types (strings, INTEGER, BOOLEAN, etc).
+.TP
 \fB\-t\fR \fIdata-string\fR
 Interpret the data-string as a sequence of hexadecimal values representing
 the start of BER TLV encoding. Print the human readable explanation.
+.SH XML FORMAT
+unber dumps the output in the regular XML format which preserves most of the
+information from the underlying binary encoding.
+.P
+The XML opening tag format is as follows:
+.Vb
+\&    <\fItform\fR T="\fItag\fR" TL="\fItl_len\fR" V="{Indefinite|\fIv_len\fR}" [A="\fItype\fR"] [\fIF\fR]>
+.Ve
+Where:
+.TP
+\fItform\fR
+Which form the value is in: primitive ("P") or constructed ("C") or constructed with indefinite length ("I")
+.TP
+\fItag\fR
+The tag class and value
+.TP
+\fItl_len\fR
+The length of the TL (BER Tag and Length) encoding
+.TP
+\fIv_len\fR
+The length of the value (V, encoded by the L), may be "Indefinite"
+.TP
+\fItype\fR
+Likely name of the underlying ASN.1 type (for UNIVERSAL tags)
+.TP
+[\fIF\fR]
+Indicates that the value was reformatted (pretty-printed)
+.P
+Example:
+.Vb
+\&  <I T="[UNIVERSAL 16]" TL="2" V="Indefinite" A="SEQUENCE">
+\&      <P T="[UNIVERSAL 19]" TL="2" V="2" A="PrintableString">US</P>
+\&      <C T="[UNIVERSAL 16]" TL="2" V="11" A="SEQUENCE">
+\&          <P T="[UNIVERSAL 2]" TL="2" V="4" A="INTEGER" F>832970823</P>
+\&      </C T="[UNIVERSAL 16]" A="SEQUENCE">
+\&  </I T="[UNIVERSAL 16]" A="SEQUENCE">
+.Ve
+.SH EXAMPLES
+Decode the given Tag/Length sequence given in hexadecimal form:
+.Vb
+\&    unber  \fB-t\fR "\fIbf 20\fR"
+.Ve
+Decode the given DER file using two-spaces indentation:
+.Vb
+\&    unber  \fB-i\fR \fI2\fR   \fIfilename.der\fR
+.Ve
+Decode the binary stream taken from the standard input:
+.Vb
+\&    cat \fI...\fR | unber \fB-\fR
+.Ve
+Decode the binary stream into the same stream (see \fIenber\fR\|(1)):
+.Vb
+\&    cat \fI...\fR | unber \fB-\fR | enber \fB-\fR > filename.ber\fI\fR
+.Ve
 .SH SEE ALSO
 .TP
-\&\fIasn1c\fR\|(1)
+\&\fIenber\fR\|(1), \&\fIasn1c\fR\|(1)
 .SH AUTHORS
 Lev Walkin <vlm@lionet.info>
diff --git a/asn1c/unber.c b/asn1c/unber.c
index a954ece..60f3a03 100644
--- a/asn1c/unber.c
+++ b/asn1c/unber.c
@@ -1,3 +1,30 @@
+/*-
+ * Copyright (c) 2004 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 <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -13,11 +40,13 @@
 
 #include <asn1parser.h>		/* For static string tables */
 
-#include <asn_types.h>
-#include <ber_tlv_tag.h>
-#include <ber_tlv_length.h>
+#include <asn_application.h>
+#include <constraints.c>
 #include <ber_tlv_tag.c>
 #include <ber_tlv_length.c>
+#include <OBJECT_IDENTIFIER.c>
+#include <RELATIVE-OID.c>
+#include <INTEGER.c>
 
 static void usage(const char *av0);	/* Print the Usage screen and exit */
 static int process(const char *fname);	/* Perform the BER decoding */
@@ -27,7 +56,9 @@
 #define	COPYRIGHT	\
 	"Copyright (c) 2004 Lev Walkin <vlm@lionet.info>\n"
 
-static char *indent_buffer = "    ";
+static int single_type_decoding = 0;	/* -1 enables that */
+static int pretty_printing = 1;		/* -p disables that */
+static char *indent_buffer = "    ";	/* -i controls that */
 
 int
 main(int ac, char **av) {
@@ -37,12 +68,18 @@
 	/*
 	 * Process command-line options.
 	 */
-	while((ch = getopt(ac, av, "i:t:v")) != -1)
+	while((ch = getopt(ac, av, "1hi:pt:v")) != -1)
 	switch(ch) {
+	case '1':
+		single_type_decoding = 1;
+		break;
 	case 't':
 		if(decode_tlv_from_string(optarg))
 			exit(EX_DATAERR);
 		exit(0);
+	case 'p':
+		pretty_printing = 0;
+		break;
 	case 'i':
 		i = atoi(optarg);
 		if(i >= 0 && i < 16) {
@@ -58,6 +95,7 @@
 		fprintf(stderr, "ASN.1 BER Decoder, v" VERSION "\n" COPYRIGHT);
 		exit(0);
 		break;
+	case 'h':
 	default:
 		usage(av[0]);
 	}
@@ -96,9 +134,21 @@
 "ASN.1 BER Decoder, v" VERSION "\n" COPYRIGHT
 "Usage: %s [options] [-] [file ...]\n"
 "Options:\n"
-"  -i <indent>           Amount of spaces for indentation (default is 4)\n"
-"  -t <data-string>      Decode the given tag[/length] sequence\n"
-"                        (e.g. -t \"bf 20\")\n"
+"  -1                 Decode only the first BER structure (otherwise, until EOF)\n"
+"  -i <indent>        Amount of spaces for output indentation (default is 4)\n"
+"  -p                 Do not attempt pretty-printing of known ASN.1 types\n"
+"  -t <data-string>   Decode the given tag[/length] sequence (e.g. -t \"bf20\")\n"
+"\n"
+"The XML opening tag format is as follows:\n"
+"  <tform T=\"tag\" TL=\"tl_len\" V=\"{Indefinite|v_len}\" [A=\"type\"] [F]>\n"
+"Where:\n"
+"  tform    Which form the value is in: constructed (\"C\", \"I\") or primitive (\"P\")\n"
+"  tag      The tag class and value\n"
+"  tl_len   The length of the TL (BER Tag and Length) encoding\n"
+"  v_len    The length of the value (V, encoded by the L), may be \"Indefinite\"\n"
+"  type     Likely name of the underlying ASN.1 type (for [UNIVERSAL n] tags)\n"
+"  [F]      Indicates that the value was reformatted (pretty-printed)\n"
+"See the manual page for details\n"
 	, av0);
 	exit(EX_USAGE);
 }
@@ -135,7 +185,7 @@
 	 */
 	do {
 		pdc = process_deeper(fname, fp, 0, -1);
-	} while(pdc == PD_FINISHED);	/* Wait until PD_EOF */
+	} while(pdc == PD_FINISHED && !single_type_decoding);
 
 	if(fp != stdin)
 		fclose(fp);
@@ -216,6 +266,11 @@
 			continue;
 		}
 
+		if(tagbuf[0] == '\0' && tagbuf[1] == '\0') {
+			/* End of content octets */
+			return PD_FINISHED;
+		}
+
 		/* Make sure the T & L decoders took exactly the whole buffer */
 		assert((t_len + l_len) == tblen);
 
@@ -249,6 +304,7 @@
 			if(tlv_len == -1)
 				tlv_len = limit;
 
+			printf(">\n");	/* Close the opening tag */
 			pdc = process_deeper(fname, fp, level + 1, tlv_len);
 			if(pdc == PD_FAILED) return pdc;
 		} else {
@@ -269,11 +325,6 @@
 static void
 print_TL(int fin, int level, int constr, ssize_t tlen, ber_tlv_tag_t tlv_tag, ber_tlv_len_t tlv_len) {
 
-	if(fin && tlen == 2 && !constr && !tlv_tag && !tlv_len) {
-		/* end of content octets */
-		return;
-	}
-
 	if(fin && !constr) {
 		printf("</P>\n");
 		return;
@@ -282,19 +333,19 @@
 	while(level-- > 0) printf(indent_buffer);  /* Print indent */
 	printf(fin ? "</" : "<");
 
-	printf(constr ? "C" : "P");
+	printf(constr ? ((tlv_len == -1) ? "I" : "C") : "P");
 
 	printf(" T=\"");
 	ber_tlv_tag_fwrite(tlv_tag, stdout);
 	printf("\"");
 
 	if(!fin) {
-		printf(" tL=\"%ld\"", (long)tlen);
+		printf(" TL=\"%ld\"", (long)tlen);
 
 		if(tlv_len == -1)
-			printf(" L=\"Indefinite\"");
+			printf(" V=\"Indefinite\"");
 		else
-			printf(" L=\"%ld\"", (long)tlv_len);
+			printf(" V=\"%ld\"", (long)tlv_len);
 	}
 
 	if(BER_TAG_CLASS(tlv_tag) == ASN_TAG_CLASS_UNIVERSAL) {
@@ -304,25 +355,86 @@
 		if(str) printf(" A=\"%s\"", str);
 	}
 
-	if(fin || constr)
-		printf(">\n");
-	else
-		printf(">");
+	if(fin) printf(">\n");
 }
 
+/*
+ * Print the value in binary form, or reformat for pretty-printing.
+ */
 static int
 print_V(const char *fname, FILE *fp, ber_tlv_tag_t tlv_tag, ber_tlv_len_t tlv_len) {
+	asn1_integer_t *arcs = 0;	/* Object identifier arcs */
+	unsigned char *vbuf = 0;
 	asn1p_expr_type_e etype = 0;
-	long collector = 0;
+	asn1_integer_t collector = 0;
+	int special_format = 0;
 	ssize_t i;
 
 	/* Figure out what type is it */
-	if(BER_TAG_CLASS(tlv_tag) == ASN_TAG_CLASS_UNIVERSAL) {
+	if(BER_TAG_CLASS(tlv_tag) == ASN_TAG_CLASS_UNIVERSAL
+	&& pretty_printing) {
 		ber_tlv_tag_t tvalue = BER_TAG_VALUE(tlv_tag);
 		etype = ASN_UNIVERSAL_TAG2TYPE(tvalue);
 	}
 
 	/*
+	 * Determine how to print the value, either in its native binary form,
+	 * encoded with &xNN characters, or using pretty-printing.
+	 * The basic string types (including "useful types", like UTCTime)
+	 * are excempt from this determination logic, because their alphabets
+	 * are subsets of the XML's native UTF-8 encoding.
+	 */
+	switch(etype) {
+	case ASN_BASIC_BOOLEAN:
+		if(tlv_len == 1)
+			special_format = 1;
+		else
+			etype = 0;
+		break;
+	case ASN_BASIC_INTEGER:
+	case ASN_BASIC_ENUMERATED:
+		if((size_t)tlv_len <= sizeof(collector))
+			special_format = 1;
+		else
+			etype = 0;
+		break;
+	case ASN_BASIC_OBJECT_IDENTIFIER:
+	case ASN_BASIC_RELATIVE_OID:
+		if(tlv_len > 0 && tlv_len < 128*1024 /* VERY long OID! */) {
+			arcs = malloc(sizeof(*arcs) * (tlv_len + 1));
+			if(arcs) {
+				vbuf = malloc(tlv_len + 1);
+				/* Not checking is intentional */
+			}
+		}
+	case ASN_BASIC_UTCTime:
+	case ASN_BASIC_GeneralizedTime:
+	case ASN_STRING_NumericString:
+	case ASN_STRING_PrintableString:
+	case ASN_STRING_VisibleString:
+	case ASN_STRING_IA5String:
+	case ASN_STRING_UTF8String:
+		break;	/* Directly compatible with UTF-8 */
+	case ASN_STRING_BMPString:
+	case ASN_STRING_UniversalString:
+		break;	/* Not directly compatible with UTF-8 */
+	default:
+		/* Conditionally compatible with UTF-8 */
+		if((
+			(etype & ASN_STRING_MASK)
+			||
+			(etype == ASN_BASIC_OCTET_STRING)
+		) && (tlv_len > 0 && tlv_len < 128 * 1024)) {
+			vbuf = malloc(tlv_len + 1);
+			/* Not checking is intentional */
+		}
+		break;
+	}
+
+	/* If collection vbuf is present, defer printing the F flag. */
+	if(!vbuf) printf(special_format ? " F>" : ">");
+
+	/*
 	 * Print the value in binary or text form.
 	 */
 	for(i = 0; i < tlv_len; i++) {
@@ -330,6 +442,8 @@
 		if(ch == -1) {
 			fprintf(stderr,
 			"%s: Unexpected end of file (V)\n", fname);
+			if(vbuf) free(vbuf);
+			if(arcs) free(arcs);
 			return -1;
 		}
 		switch(etype) {
@@ -338,6 +452,7 @@
 		case ASN_STRING_NumericString:
 		case ASN_STRING_PrintableString:
 		case ASN_STRING_VisibleString:
+		case ASN_STRING_IA5String:
 		case ASN_STRING_UTF8String:
 			switch(ch) {
 			default:
@@ -354,33 +469,112 @@
 			}
 			break;
 		case ASN_BASIC_BOOLEAN:
-			if(tlv_len == 1) {
-				switch(ch) {
-				case 0: printf("<false/>"); break;
-				case 0xff: printf("<true/>"); break;
-				default: printf("<true value=\"&x%02x\"/>", ch);
-				}
-				break;
+			switch(ch) {
+			case 0: printf("<false/>"); break;
+			case 0xff: printf("<true/>"); break;
+			default: printf("<true value=\"&x%02x\"/>", ch);
 			}
-			/* Fall through */
+			break;
 		case ASN_BASIC_INTEGER:
 		case ASN_BASIC_ENUMERATED:
-			if((size_t)tlv_len <= sizeof(collector)) {
-				if(i) {
-					collector = collector * 256 + ch;
-				} else {
-					collector = (int)(signed char)ch;
-				}
-				if((i+1) == tlv_len)
-					printf("%ld", collector);
-				break;
-			}
-			/* Fall through */
+			if(i)	collector = collector * 256 + ch;
+			else	collector = (int)(signed char)ch;
+			break;
 		default:
-			printf("&x%02x;", ch);
+			if(vbuf) {
+				vbuf[i] = ch;
+			} else {
+				printf("&x%02x;", ch);
+			}
 		}
 	}
 
+	/* Do post-processing */
+	switch(etype) {
+	case ASN_BASIC_INTEGER:
+	case ASN_BASIC_ENUMERATED:
+		printf("%" PRIdMAX, collector);
+		break;
+	case ASN_BASIC_OBJECT_IDENTIFIER:
+		if(vbuf) {
+			OBJECT_IDENTIFIER_t oid;
+			int arcno;
+
+			oid.buf = vbuf;
+			oid.size = tlv_len;
+
+			arcno = OBJECT_IDENTIFIER_get_arcs(&oid, arcs,
+				sizeof(*arcs), tlv_len + 1);
+			if(arcno >= 0) {
+				assert(arcno <= (tlv_len + 1));
+				printf(" F>");
+				for(i = 0; i < arcno; i++) {
+					if(i) printf(".");
+					printf("%" PRIuASN, arcs[i]);
+				}
+				free(vbuf);
+				vbuf = 0;
+			}
+		}
+		break;
+	case ASN_BASIC_RELATIVE_OID:
+		if(vbuf) {
+			RELATIVE_OID_t oid;
+			int arcno;
+
+			oid.buf = vbuf;
+			oid.size = tlv_len;
+	
+			arcno = RELATIVE_OID_get_arcs(&oid, arcs,
+				sizeof(*arcs), tlv_len);
+			if(arcno >= 0) {
+				assert(arcno <= (tlv_len + 1));
+				printf(" F>");
+				for(i = 0; i < arcno; i++) {
+					if(i) printf(".");
+					printf("%" PRIuASN, arcs[i]);
+				}
+				free(vbuf);
+				vbuf = 0;
+			}
+		}
+		break;
+	default: break;
+	}
+
+	/*
+	 * If the buffer was not consumed, print it out.
+	 */
+	if(vbuf) {
+		int binary;
+
+		/*
+		 * Check whether the data could be represented as text
+		 */
+		binary = -1 * (tlv_len >> 2); /* Threshold is 25% binary */
+		for(i = 0; i < tlv_len; i++) {
+			switch(vbuf[i]) {
+			case 0x1b: binary = 1; break;
+			case 0x09: case 0x0a: case 0x0d: continue;
+			default:
+				if(vbuf[i] < 0x20 || (vbuf[i] & 0x80))
+					if(++binary > 0)  /* Way too many */
+						break;
+				continue;
+			}
+			break;
+		}
+		printf(">");
+		for(i = 0; i < tlv_len; i++) {
+			if(binary > 0 || vbuf[i] < 0x20 || (vbuf[i] & 0x80))
+				printf("&x%02x;", vbuf[i]);
+			else
+				printf("%c", vbuf[i]);
+		}
+		free(vbuf);
+	}
+
+	if(arcs) free(arcs);
 	return 0;
 }
 
@@ -479,3 +673,9 @@
 
 	return 0;
 }
+
+/*
+ * Dummy functions.
+ */
+ber_dec_rval_t ber_check_tags(asn1_TYPE_descriptor_t *td, ber_dec_ctx_t *opt_ctx, void *ptr, size_t size, int tag_mode, ber_tlv_len_t *last_length, int *opt_tlv_form) { ber_dec_rval_t rv; (void)td; (void)opt_ctx; (void)ptr; (void)size; (void)tag_mode; (void)last_length; (void)opt_tlv_form; return rv; }
+ssize_t der_write_tags(asn1_TYPE_descriptor_t *td, size_t slen, int tag_mode, ber_tlv_tag_t tag, asn_app_consume_bytes_f *cb, void *app_key) { (void)td; (void)slen; (void)tag_mode; (void)tag; (void)cb; (void)app_key; return -1; }