/* packet-ber.c
 * Helpers for ASN.1/BER dissection
 * Ronnie Sahlberg (C) 2004
 *
 * $Id: packet-ber.c,v 1.13 2004/06/25 09:24:17 sahlberg Exp $
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@ethereal.com>
 * Copyright 1998 Gerald Combs
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

/* 
 * ITU-T Recommendation X.690 (07/2002),
 *   Information technology ASN.1 encoding rules:
 *     Specification of Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and Distinguished Encoding Rules (DER)
 * 
 */ 

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include <glib.h>

#include <epan/packet.h>

#include <epan/strutil.h>
#include <epan/int-64bit.h>
#include "prefs.h"
#include "packet-ber.h"


static gint proto_ber = -1;
static gint hf_ber_id_class = -1;
static gint hf_ber_id_pc = -1;
static gint hf_ber_id_uni_tag = -1;
static gint hf_ber_id_tag = -1;
static gint hf_ber_length = -1;
static gint hf_ber_bitstring_padding = -1;

static gint ett_ber_octet_string = -1;

static gboolean show_internal_ber_fields = FALSE;

proto_item *ber_last_created_item=NULL;


static const value_string ber_class_codes[] = {
	{ BER_CLASS_UNI,	"Universal" },
	{ BER_CLASS_APP,	"Application" },
	{ BER_CLASS_CON,	"Context Specific" },
	{ BER_CLASS_PRI,	"Private" },
	{ 0, NULL }
};

static const true_false_string ber_pc_codes = {
	"Constructed Encoding",
	"Primitive Encoding"
};

static const value_string ber_uni_tag_codes[] = {
	{ BER_UNI_TAG_EOC				, "'end-of-content'" },
	{ BER_UNI_TAG_BOOLEAN			, "BOOLEAN" },
	{ BER_UNI_TAG_INTEGER			, "INTEGER" },
	{ BER_UNI_TAG_BITSTRING		, "BIT STRING" },
	{ BER_UNI_TAG_OCTETSTRING		, "OCTET STRING" },
	{ BER_UNI_TAG_NULL			, "NULL" },
	{ BER_UNI_TAG_OID			 	, "OBJECT IDENTIFIER" },
	{ BER_UNI_TAG_ObjectDescriptor, "ObjectDescriptor" },
	{ BER_UNI_TAG_REAL			, "REAL" },
	{ BER_UNI_TAG_ENUMERATED		, "ENUMERATED" },
	{ BER_UNI_TAG_EMBEDDED_PDV	, "EMBEDDED PDV" },
	{ BER_UNI_TAG_UTF8String		, "UTF8String" },
	{ BER_UNI_TAG_RELATIVE_OID	, "RELATIVE-OID" },
	{ BER_UNI_TAG_SEQUENCE		, "SEQUENCE, SEQUENCE OF" },
	{ BER_UNI_TAG_SET				, "SET, SET OF" },
	{ BER_UNI_TAG_NumericString	, "NumericString" },
	{ BER_UNI_TAG_PrintableString	, "PrintableString" },
	{ BER_UNI_TAG_TeletextString	, "TeletextString, T61String" },
	{ BER_UNI_TAG_VideotexString	, "VideotexString" },
	{ BER_UNI_TAG_IA5String		, "IA5String" },
	{ BER_UNI_TAG_UTCTime		, "UTCTime" },
	{ BER_UNI_TAG_GeneralizedTime	, "GeneralizedTime" },
	{ BER_UNI_TAG_GraphicString	, "GraphicString" },
	{ BER_UNI_TAG_VisibleString	, "VisibleString, ISO64String" },
	{ BER_UNI_TAG_GeneralString	, "GeneralString" },
	{ BER_UNI_TAG_UniversalString	, "UniversalString" },
	{ BER_UNI_TAG_CHARACTERSTRING	, "CHARACTER STRING" },
	{ BER_UNI_TAG_BMPString		, "BMPString" },
	{ 0, NULL }
};


proto_item *get_ber_last_created_item(void) {
  return ber_last_created_item;
}

static int dissect_ber_sq_of(gboolean implicit_tag, guint32 type, packet_info *pinfo, proto_tree *parent_tree, tvbuff_t *tvb, int offset, ber_sequence *seq, gint hf_id, gint ett_id);

/* 8.1 General rules for encoding */

/*  8.1.2 Identifier octets */
int get_ber_identifier(tvbuff_t *tvb, int offset, guint8 *class, gboolean *pc, guint32 *tag) {
	guint8 id, t;
	guint8 tmp_class;
	gboolean tmp_pc;
	guint32 tmp_tag;

	id = tvb_get_guint8(tvb, offset);
	offset += 1;
	
	/* 8.1.2.2 */
	tmp_class = (id>>6) & 0x03;
	tmp_pc = (id>>5) & 0x01;
	tmp_tag = id&0x1F;
	/* 8.1.2.4 */
	if (tmp_tag == 0x1F) {
		tmp_tag = 0;
		while (tvb_length_remaining(tvb, offset) > 0) {
			t = tvb_get_guint8(tvb, offset);
			offset += 1;
			tmp_tag <<= 7;       
			tmp_tag |= t & 0x7F;
			if (t & 0x80) break;
		}
	}

	if (class)
		*class = tmp_class;
	if (pc)
		*pc = tmp_pc;
	if (tag)
		*tag = tmp_tag;

	return offset;
}

int dissect_ber_identifier(packet_info *pinfo _U_, proto_tree *tree, tvbuff_t *tvb, int offset, guint8 *class, gboolean *pc, guint32 *tag) 
{
	int old_offset = offset;
	guint8 tmp_class;
	gboolean tmp_pc;
	guint32 tmp_tag;

	offset = get_ber_identifier(tvb, offset, &tmp_class, &tmp_pc, &tmp_tag);
	
	if(show_internal_ber_fields){
		proto_tree_add_uint(tree, hf_ber_id_class, tvb, old_offset, 1, tmp_class<<6);
		proto_tree_add_boolean(tree, hf_ber_id_pc, tvb, old_offset, 1, (tmp_pc)?0x20:0x00);
		if(tmp_class==BER_CLASS_UNI){
			proto_tree_add_uint(tree, hf_ber_id_uni_tag, tvb, old_offset, offset - old_offset, tmp_tag);
		} else {
			proto_tree_add_uint(tree, hf_ber_id_tag, tvb, old_offset, offset - old_offset, tmp_tag);
		}
	}

	if (class)
		*class = tmp_class;
	if (pc)
		*pc = tmp_pc;
	if (tag)
		*tag = tmp_tag;

	return offset;
}

/* this function gets the length octets of the BER TLV.
 * We only handle (TAGs and) LENGTHs that fit inside 32 bit integers.
 */
/* 8.1.3 Length octets */
int
get_ber_length(tvbuff_t *tvb, int offset, guint32 *length, gboolean *ind) {
	guint8 oct, len;
	guint32 tmp_length;
	gboolean tmp_ind;

	tmp_length = 0;
	tmp_ind = FALSE;

	oct = tvb_get_guint8(tvb, offset);
	offset += 1;
	
	if (!(oct&0x80)) {
		/* 8.1.3.4 */
		tmp_length = oct;
	} else {
		len = oct & 0x7F;
		if (len) {
			/* 8.1.3.5 */
			while (len--) {
				oct = tvb_get_guint8(tvb, offset);
				offset++;
				tmp_length = (tmp_length<<8) + oct;
			}
		} else {
			/* 8.1.3.6 */
			tmp_ind = TRUE;
			/* TO DO */
		}
	}

	if (length)
		*length = tmp_length;
	if (ind)
		*ind = tmp_ind;

	return offset;
}

/* this function dissects the length octets of the BER TLV.
 * We only handle (TAGs and) LENGTHs that fit inside 32 bit integers.
 */
int
dissect_ber_length(packet_info *pinfo _U_, proto_tree *tree, tvbuff_t *tvb, int offset, guint32 *length, gboolean *ind)
{
	int old_offset = offset;
	guint32 tmp_length;
	gboolean tmp_ind;

	offset = get_ber_length(tvb, offset, &tmp_length, &tmp_ind);
	
	if(show_internal_ber_fields){
		proto_tree_add_uint(tree, hf_ber_length, tvb, old_offset, offset - old_offset, tmp_length);
	}
	if (length)
		*length = tmp_length;
	if (ind)
		*ind = tmp_ind;
	return offset;
}

/* 8.7 Encoding of an octetstring value */
int 
dissect_ber_octet_string(gboolean implicit_tag, packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, int offset, gint hf_id, tvbuff_t **out_tvb) {
	guint8 class;
	gboolean pc, ind;
	guint32 tag;
	guint32 len;
	int end_offset;
	proto_item *it;

	/* read header and len for the octet string */
	offset=dissect_ber_identifier(pinfo, tree, tvb, offset, &class, &pc, &tag);
	offset=dissect_ber_length(pinfo, tree, tvb, offset, &len, &ind);
	end_offset=offset+len;

	/* sanity check: we only handle Constructed Universal Sequences */
	if (!implicit_tag) {
		if( (class!=BER_CLASS_UNI)
		  ||(tag!=BER_UNI_TAG_OCTETSTRING) ){
	    	    proto_tree_add_text(tree, tvb, offset-2, 2, "BER Error: OctetString expected but Class:%d PC:%d Tag:%d was unexpected", class, pc, tag);
			return end_offset;
		}
	}

	ber_last_created_item = NULL;
	if (pc) {
		/* constructed */
		/* TO DO */
	} else {
		/* primitive */
		if (hf_id != -1) {
			it = proto_tree_add_item(tree, hf_id, tvb, offset, len, FALSE);
			ber_last_created_item = it;
		}
		if (out_tvb) {
			*out_tvb = tvb_new_subset(tvb, offset, len, len);
		}
	}
	return end_offset;
}

int dissect_ber_octet_string_wcb(gboolean implicit_tag, packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, int offset, gint hf_id, ber_callback func)
{
	tvbuff_t *out_tvb;

	offset = dissect_ber_octet_string(implicit_tag, pinfo, tree, tvb, offset, hf_id, (func)?&out_tvb:NULL);
	if (func && (tvb_length(out_tvb)>0)) {
		if (hf_id != -1)
			tree = proto_item_add_subtree(ber_last_created_item, ett_ber_octet_string);
		func(pinfo, tree, out_tvb, 0);
	}
	return offset;
}


int
dissect_ber_integer(packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, int offset, gint hf_id, guint32 *value)
{
	guint8 class;
	gboolean pc;
	guint32 tag;
	guint32 len;
	gint32 val;
	guint32 i;

	offset=dissect_ber_identifier(pinfo, tree, tvb, offset, &class, &pc, &tag);
	offset=dissect_ber_length(pinfo, tree, tvb, offset, &len, NULL);

/*	if(class!=BER_CLASS_UNI)*/

	/* ok,  we cant handle >4 byte integers so lets fake them */
	if(len>8){
		proto_tree_add_text(tree, tvb, offset, len, "BER: Can not parse %d byte integers", len);
		return 0xdeadbeef;
	}
	if(len>4){
		header_field_info *hfinfo;
		char buf[8]={0,0,0,0,0,0,0,0};

		tvb_memcpy(tvb, buf+8-len, offset, len);
		hfinfo = proto_registrar_get_nth(hf_id);
		proto_tree_add_text(tree, tvb, offset, len, "%s: %s", hfinfo->name, u64toa(buf));
		return 0xdeadbeef;
	}
	
	val=0;
	if (len > 0) {
		/* extend sign bit */
		val = (gint8)tvb_get_guint8(tvb, offset);
		offset++;
	}
	for(i=1;i<len;i++){
		val=(val<<8)|tvb_get_guint8(tvb, offset);
		offset++;
	}

	ber_last_created_item=NULL;

	if(hf_id!=-1){	
		/* XXX - what if "len" is not 1, 2, 3, or 4? */
		ber_last_created_item=proto_tree_add_item(tree, hf_id, tvb, offset-len, len, FALSE);
	}
	if(value){
		*value=val;
	}

	return offset;
}





int
dissect_ber_boolean(packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, int offset, gint hf_id)
{
	guint8 class;
	gboolean pc;
	guint32 tag;
	guint32 len;
	guint8 val;
	header_field_info *hfi;

	offset=dissect_ber_identifier(pinfo, tree, tvb, offset, &class, &pc, &tag);
	offset=dissect_ber_length(pinfo, tree, tvb, offset, &len, NULL);

/*	if(class!=BER_CLASS_UNI)*/
	
	val=tvb_get_guint8(tvb, offset);
	offset+=1;

	ber_last_created_item=NULL;

	if(hf_id!=-1){
		hfi = proto_registrar_get_nth(hf_id);
		if (hfi->type == FT_BOOLEAN)
			ber_last_created_item=proto_tree_add_boolean(tree, hf_id, tvb, offset-1, 1, val);
		else
			ber_last_created_item=proto_tree_add_uint(tree, hf_id, tvb, offset-1, 1, val?1:0);
	}

	return offset;
}





/* this function dissects a BER sequence 
 */
int dissect_ber_sequence(gboolean implicit_tag, packet_info *pinfo, proto_tree *parent_tree, tvbuff_t *tvb, int offset, ber_sequence *seq, gint hf_id, gint ett_id) {
	guint8 class;
	gboolean pc, ind;
	guint32 tag;
	guint32 len;
	proto_tree *tree = parent_tree;
	proto_item *item = NULL;
	int end_offset;

	/* first we must read the sequence header */
	offset = dissect_ber_identifier(pinfo, tree, tvb, offset, &class, &pc, &tag);
	offset = dissect_ber_length(pinfo, tree, tvb, offset, &len, &ind);
	end_offset = offset + len;

	/* sanity check: we only handle Constructed Universal Sequences */
	if ((!pc)
		||(!implicit_tag&&((class!=BER_CLASS_UNI)
							||(tag!=BER_UNI_TAG_SEQUENCE)))) {
		proto_tree_add_text(tree, tvb, offset-2, 2, "BER Error: Sequence expected but Class:%d PC:%d Tag:%d was unexpected", class, pc, tag);
		return end_offset;
	}

	/* create subtree */
	if (hf_id != -1) {
		if(parent_tree){
			item = proto_tree_add_item(parent_tree, hf_id, tvb, offset, len, FALSE);
			tree = proto_item_add_subtree(item, ett_id);
		}
	}

	/* loop over all entries until we reach the end of the sequence */
	while (offset < end_offset){
		guint8 class;
		gboolean pc;
		guint32 tag;
		guint32 len;
		int hoffset, eoffset;

		hoffset = offset;
		/* read header and len for next field */
		offset = get_ber_identifier(tvb, offset, &class, &pc, &tag);
		offset = get_ber_length(tvb, offset, &len, NULL);
		eoffset = offset + len;

ber_sequence_try_again:
		/* have we run out of known entries in the sequence ?*/
		if (!seq->func) {
			/* it was not,  move to the enxt one and try again */
			proto_tree_add_text(tree, tvb, offset, len, "BER Error: This field lies beyond the end of the known sequence definition.");
			offset = eoffset;
			continue;
		}

		/* verify that this one is the one we want */
		if( (seq->class!=class)
		  ||(seq->tag!=tag) ){
			/* it was not,  move to the enxt one and try again */
			if(seq->flags&BER_FLAGS_OPTIONAL){
				/* well this one was optional so just skip to the next one and try again. */
				seq++;
				goto ber_sequence_try_again;
			}
			if (!(seq->flags & BER_FLAGS_NOTCHKTAG)) {
				proto_tree_add_text(tree, tvb, offset, len, "BER Error: Wrong field");
				seq++;
				offset=eoffset;
				continue;
			}
		}

		if (!(seq->flags & BER_FLAGS_NOOWNTAG) && !(seq->flags & BER_FLAGS_IMPLTAG)) {
			/* dissect header and len for field */
			hoffset = dissect_ber_identifier(pinfo, tree, tvb, hoffset, NULL, NULL, NULL);
			hoffset = dissect_ber_length(pinfo, tree, tvb, hoffset, NULL, NULL);
		}
		
		/* call the dissector for this field */
		seq->func(pinfo, tree, tvb, hoffset);

		seq++;
		offset = eoffset;
	}

	/* if we didnt end up at exactly offset, then we ate too many bytes */
	if (offset != end_offset) {
		proto_tree_add_text(tree, tvb, offset-2, 2, "BER Error: Sequence ate %d too many bytes", offset-end_offset);
	}

	return end_offset;
}



/* this function dissects a BER choice 
 */
int
dissect_ber_choice(packet_info *pinfo, proto_tree *parent_tree, tvbuff_t *tvb, int offset, const ber_choice *choice, gint hf_id, gint ett_id)
{
	guint8 class;
	gboolean pc;
	guint32 tag;
	guint32 len;
	const ber_choice *ch;
	proto_tree *tree=parent_tree;
	proto_item *item=NULL;
	int end_offset;
	int hoffset = offset;
	header_field_info	*hfinfo;


	/* read header and len for choice field */
	offset=get_ber_identifier(tvb, offset, &class, &pc, &tag);
	offset=get_ber_length(tvb, offset, &len, NULL);
	end_offset=offset+len;

	/* Some sanity checks.  
	 * The hf field passed to us MUST be an integer type 
	 */
	if(hf_id!=-1){
		hfinfo=proto_registrar_get_nth(hf_id);
		switch(hfinfo->type) {
			case FT_UINT8:
			case FT_UINT16:
			case FT_UINT24:
			case FT_UINT32:
				break;
		default:
			proto_tree_add_text(tree, tvb, offset, len,"dissect_ber_choice(): Was passed a HF field that was not integer type : %s",hfinfo->abbrev);
			fprintf(stderr,"dissect_ber_choice(): frame:%d offset:%d Was passed a HF field that was not integer type : %s\n",pinfo->fd->num,offset,hfinfo->abbrev);
			return end_offset;
		}
	}

	

	/* loop over all entries until we find the right choice or 
	   run out of entries */
	ch = choice;
	while(ch->func){
		if( (ch->class==class)
		  &&(ch->tag==tag) ){
			if (!(ch->flags & BER_FLAGS_NOOWNTAG) && !(ch->flags & BER_FLAGS_IMPLTAG)) {
				/* dissect header and len for field */
				hoffset = dissect_ber_identifier(pinfo, tree, tvb, hoffset, NULL, NULL, NULL);
				hoffset = dissect_ber_length(pinfo, tree, tvb, hoffset, NULL, NULL);
			}
			/* create subtree */
			if(hf_id!=-1){
				if(parent_tree){
					item = proto_tree_add_uint(parent_tree, hf_id, tvb, hoffset, end_offset - hoffset, ch->value);
					tree = proto_item_add_subtree(item, ett_id);
				}
			}
			offset=ch->func(pinfo, tree, tvb, hoffset);
			return end_offset;
			break;
		}
		ch++;
	}
	/* oops no more entries and we still havent found
	 * our guy :-(
	 */
	proto_tree_add_text(tree, tvb, offset, len, "BER Error: This choice field was not found.");

	return end_offset;
}

#if 0
/* this function dissects a BER GeneralString
 */
int
dissect_ber_GeneralString(packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, int offset, gint hf_id, char *name_string, int name_len)
{
	guint8 class;
	gboolean pc;
	guint32 tag;
	guint32 len;
	int end_offset;
	char str_arr[256];
	guint32 max_len;
	char *str;

	str=str_arr;
	max_len=255;
	if(name_string){
		str=name_string;
		max_len=name_len;
	}

	/* first we must read the GeneralString header */
	offset=dissect_ber_identifier(pinfo, tree, tvb, offset, &class, &pc, &tag);
	offset=dissect_ber_length(pinfo, tree, tvb, offset, &len, NULL);
	end_offset=offset+len;

	/* sanity check: we only handle Universal GeneralString*/
	if( (class!=BER_CLASS_UNI)
	  ||(tag!=BER_UNI_TAG_GENSTR) ){
	        proto_tree_add_text(tree, tvb, offset-2, 2, "BER Error: GeneralString expected but Class:%d PC:%d Tag:%d was unexpected", class, pc, tag);
		return end_offset;
	}

	if(len>=(max_len-1)){
		len=max_len-1;
	}
	
	tvb_memcpy(tvb, str, offset, len);
	str[len]=0;

	if(hf_id!=-1){
		proto_tree_add_string(tree, hf_id, tvb, offset, len, str);
	}

	return end_offset;
}
#endif
int
dissect_ber_restricted_string(gboolean implicit_tag, guint32 type, packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, int offset, gint hf_id, tvbuff_t **out_tvb) {
	guint8 class;
	gboolean pc;
	guint32 tag;
	guint32 len;
	int eoffset;
	int hoffset = offset;

	offset = get_ber_identifier(tvb, offset, &class, &pc, &tag);
	offset = get_ber_length(tvb, offset, &len, NULL);
	eoffset = offset + len;

	/* sanity check */
	if (!implicit_tag) {
		if( (class!=BER_CLASS_UNI)
		  ||(tag != type) ){
	    	    proto_tree_add_text(tree, tvb, offset-2, 2, "BER Error: String with tag=%d expected but Class:%d PC:%d Tag:%d was unexpected", type, class, pc, tag);
			return eoffset;
		}
	}

	/* 8.21.3 */
	return dissect_ber_octet_string(TRUE, pinfo, tree, tvb, hoffset, hf_id, out_tvb);
}

int
dissect_ber_GeneralString(packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, int offset, gint hf_id, char *name_string, guint name_len)
{
	tvbuff_t *out_tvb;

	offset = dissect_ber_restricted_string(FALSE, BER_UNI_TAG_GeneralString, pinfo, tree, tvb, offset, hf_id, (name_string)?&out_tvb:NULL);

	if (name_string) {
		if (tvb_length(out_tvb) >= name_len) {
			tvb_memcpy(out_tvb, name_string, 0, name_len-1);
			name_string[name_len-1] = '\0';
		} else {
			tvb_memcpy(out_tvb, name_string, 0, -1);
			name_string[tvb_length(out_tvb)] = '\0';
		}
	}

	return offset;
}

/* 8.19 Encoding of an object identifier value */
int dissect_ber_object_identifier(gboolean implicit_tag, packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, int offset, gint hf_id, char *value_string) {
	guint8 class;
	gboolean pc;
	guint32 tag;
	guint32 i, len;
	int eoffset;
	guint8 byte;
	guint32 value;
	char str[256],*strp;

	offset = get_ber_identifier(tvb, offset, &class, &pc, &tag);
	offset = dissect_ber_length(pinfo, tree, tvb, offset, &len, NULL);
	eoffset = offset + len;

	if (value_string) {
		value_string[0] = '\0';
	}

	/* sanity check */
	if (!implicit_tag) {
		if( (class!=BER_CLASS_UNI)
		  ||(tag != BER_UNI_TAG_OID) ){
	    	    proto_tree_add_text(tree, tvb, offset-2, 2, "BER Error: Object Identifier expected but Class:%d PC:%d Tag:%d was unexpected", class, pc, tag);
			return eoffset;
		}
	}

	value=0;
	for (i=0,strp=str; i<len; i++){
		byte = tvb_get_guint8(tvb, offset);
		offset++;

		if((strp-str)>200){
    	    proto_tree_add_text(tree, tvb, offset, eoffset - offset, "BER Error: too long Object Identifier");
			return offset;
		}

		/* 8.19.4 */
		if (i == 0) {
			strp += sprintf(strp, "%d.%d", byte/40, byte%40);
			continue;
		}

		value = (value << 7) | (byte & 0x7F);
		if (byte & 0x80) {
			continue;
		}

		strp += sprintf(strp, ".%d", value);
		value = 0;
	}
	*strp = '\0';

	if (hf_id != -1) {
		proto_tree_add_string(tree, hf_id, tvb, offset - len, len, str);
	}

	if (value_string) {
		strcpy(value_string, str);
	}

	return eoffset;
}

static int dissect_ber_sq_of(gboolean implicit_tag, guint32 type, packet_info *pinfo, proto_tree *parent_tree, tvbuff_t *tvb, int offset, ber_sequence *seq, gint hf_id, gint ett_id) {
	guint8 class;
	gboolean pc, ind;
	guint32 tag;
	guint32 len;
	proto_tree *tree = parent_tree;
	proto_item *item = NULL;
	int cnt, hoffset, end_offset;
	header_field_info *hfi;

	/* first we must read the sequence header */
	offset = dissect_ber_identifier(pinfo, tree, tvb, offset, &class, &pc, &tag);
	offset = dissect_ber_length(pinfo, tree, tvb, offset, &len, &ind);
	end_offset = offset + len;

	/* sanity check: we only handle Constructed Universal Sequences */
	if (!pc
		||(!implicit_tag&&((class!=BER_CLASS_UNI)
							||(tag!=type)))) {
		proto_tree_add_text(tree, tvb, offset-2, 2, "BER Error: %s Of expected but Class:%d PC:%d Tag:%d was unexpected", 
							(type==BER_UNI_TAG_SEQUENCE)?"Set":"Sequence", class, pc, tag);
		return end_offset;
	}

	/* count number of items */
	cnt = 0;
	hoffset = offset;
	while (offset < end_offset){
		guint32 len;
		/* read header and len for next field */
		offset = get_ber_identifier(tvb, offset, NULL, NULL, NULL);
		offset = get_ber_length(tvb, offset, &len, NULL);
		offset += len;
		cnt++;
	}
	offset = hoffset;

	/* create subtree */
	if (hf_id != -1) {
		hfi = proto_registrar_get_nth(hf_id);
		if(parent_tree){
			if (hfi->type == FT_NONE) {
				item = proto_tree_add_item(parent_tree, hf_id, tvb, offset, len, FALSE);
				proto_item_append_text(item, ":");
			} else {
				item = proto_tree_add_uint(parent_tree, hf_id, tvb, offset, len, cnt);
				proto_item_append_text(item, (cnt==1)?" item":" items");
			}
			tree = proto_item_add_subtree(item, ett_id);
		}
	}

	/* loop over all entries until we reach the end of the sequence */
	while (offset < end_offset){
		guint8 class;
		gboolean pc;
		guint32 tag;
		guint32 len;
		int eoffset;
		int hoffset;

		hoffset = offset;
		/* read header and len for next field */
		offset = get_ber_identifier(tvb, offset, &class, &pc, &tag);
		offset = get_ber_length(tvb, offset, &len, NULL);
		eoffset = offset + len;

		/* verify that this one is the one we want */
		if ((seq->class!=class)
			||(seq->tag!=tag) ){
			if (!(seq->flags & BER_FLAGS_NOTCHKTAG)) {
				proto_tree_add_text(tree, tvb, offset, len, "BER Error: Wrong field");
				offset = eoffset;
				continue;
			}
		}

		if (!(seq->flags & BER_FLAGS_NOOWNTAG) && !(seq->flags & BER_FLAGS_IMPLTAG)) {
			/* dissect header and len for field */
			hoffset = dissect_ber_identifier(pinfo, tree, tvb, hoffset, NULL, NULL, NULL);
			hoffset = dissect_ber_length(pinfo, tree, tvb, hoffset, NULL, NULL);
		}
		
		/* call the dissector for this field */
		seq->func(pinfo, tree, tvb, hoffset);
		cnt++;
		offset = eoffset;
	}

	/* if we didnt end up at exactly offset, then we ate too many bytes */
	if (offset != end_offset) {
		proto_tree_add_text(tree, tvb, offset-2, 2, "BER Error: %s Of ate %d too many bytes", 
							(type==BER_UNI_TAG_SEQUENCE)?"Set":"Sequence", offset-end_offset);
	}

	return end_offset;
}

int dissect_ber_sequence_of(gboolean implicit_tag, packet_info *pinfo, proto_tree *parent_tree, tvbuff_t *tvb, int offset, ber_sequence *seq, gint hf_id, gint ett_id) {
	return dissect_ber_sq_of(implicit_tag, BER_UNI_TAG_SEQUENCE, pinfo, parent_tree, tvb, offset, seq, hf_id, ett_id);
}

int dissect_ber_set_of(gboolean implicit_tag, packet_info *pinfo, proto_tree *parent_tree, tvbuff_t *tvb, int offset, ber_sequence *seq, gint hf_id, gint ett_id) {
	return dissect_ber_sq_of(implicit_tag, BER_UNI_TAG_SET, pinfo, parent_tree, tvb, offset, seq, hf_id, ett_id);
}

int 
dissect_ber_generalized_time(packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, int offset, gint hf_id)
{
	char str[32];
	const guint8 *tmpstr;
	guint8 class;
	gboolean pc;
	guint32 tag;
	guint32 len;
	int end_offset;

	offset=dissect_ber_identifier(pinfo, tree, tvb, offset, &class, &pc, &tag);
	offset=dissect_ber_length(pinfo, tree, tvb, offset, &len, NULL);
	end_offset=offset+len;

	/* sanity check. we only handle universal/generalized time */
	if( (class!=BER_CLASS_UNI)
	  ||(tag!=BER_UNI_TAG_GeneralizedTime)){
	        proto_tree_add_text(tree, tvb, offset-2, 2, "BER Error: GeneralizedTime expected but Class:%d PC:%d Tag:%d was unexpected", class, pc, tag);
		return end_offset;
		end_offset=offset+len;
	}

	tmpstr=tvb_get_ptr(tvb, offset, len);
	snprintf(str, 31, "%.4s-%.2s-%.2s %.2s:%.2s:%.2s (%.1s)",
		tmpstr, tmpstr+4, tmpstr+6, tmpstr+8,
		tmpstr+10, tmpstr+12, tmpstr+14);
	str[31]=0; /* just in case ... */
		
	if(hf_id!=-1){
		proto_tree_add_string(tree, hf_id, tvb, offset, len, str);
	}

	offset+=len;
	return offset;
}

/* 8.6 Encoding of a bitstring value */
int dissect_ber_bitstring(gboolean implicit_tag, packet_info *pinfo, proto_tree *parent_tree, tvbuff_t *tvb, int offset, asn_namedbit *named_bits, gint hf_id, gint ett_id, tvbuff_t **out_tvb) 
{
	guint8 class;
	gboolean pc, ind;
	guint32 tag;
	guint32 len;
	guint8 pad=0, b0, b1, val;
	int end_offset;
	proto_item *item = NULL;
	proto_tree *tree = NULL;
	asn_namedbit *nb;
	char *sep;
	gboolean term;

	/* read header and len for the octet string */
	offset = dissect_ber_identifier(pinfo, parent_tree, tvb, offset, &class, &pc, &tag);
	offset = dissect_ber_length(pinfo, parent_tree, tvb, offset, &len, &ind);
	end_offset = offset + len;

	/* sanity check: we only handle Universal BitSrings */
	if (!implicit_tag) {
		if( (class!=BER_CLASS_UNI)
		  ||(tag!=BER_UNI_TAG_BITSTRING) ){
	    	    proto_tree_add_text(parent_tree, tvb, offset-2, 2, "BER Error: BitString expected but Class:%d PC:%d Tag:%d was unexpected", class, pc, tag);
			return end_offset;
		}
	}

	ber_last_created_item = NULL;

	if (pc) {
		/* constructed */
		/* TO DO */
	} else {
		/* primitive */
		/* padding */
		pad = tvb_get_guint8(tvb, offset);
		proto_tree_add_item(parent_tree, hf_ber_bitstring_padding, tvb, offset, 1, FALSE);
		offset++;
		len--;
		if ( hf_id != -1) {
			item = proto_tree_add_item(parent_tree, hf_id, tvb, offset, len, FALSE);
			ber_last_created_item = item;
			if (ett_id != -1) {
				tree = proto_item_add_subtree(item, ett_id);
			}
		}
		if (out_tvb) {
			*out_tvb = tvb_new_subset(tvb, offset, len, 8*len-pad);
		}
	}

	if (named_bits) {
		sep = " (";
		term = FALSE;
		nb = named_bits;
		while (nb->p_id) {
			if (nb->bit < (8*len-pad)) {
				val = tvb_get_guint8(tvb, offset + nb->bit/8);
				val &= 0x80 >> (nb->bit%8);
				b0 = (nb->gb0 == -1) ? nb->bit/8 :
						       ((guint32)nb->gb0)/8;
				b1 = (nb->gb1 == -1) ? nb->bit/8 :
						       ((guint32)nb->gb1)/8;
				proto_tree_add_item(tree, *(nb->p_id), tvb, offset + b0, b1 - b0 + 1, FALSE);
			} else {  /* 8.6.2.4 */
				val = 0;
				proto_tree_add_boolean(tree, *(nb->p_id), tvb, offset + len, 0, 0x00);
			}
			if (val) {
				if (item && nb->tstr)
					proto_item_append_text(item, "%s%s", sep, nb->tstr);
			} else {
				if (item && nb->fstr)
					proto_item_append_text(item, "%s%s", sep, nb->fstr);
			}
			nb++;
			sep = ", ";
			term = TRUE;
		}
		if (term)
			proto_item_append_text(item, ")");
	}

	return end_offset;
}

int dissect_ber_bitstring32(gboolean implicit_tag, packet_info *pinfo, proto_tree *parent_tree, tvbuff_t *tvb, int offset, int **bit_fields, gint hf_id, gint ett_id, tvbuff_t **out_tvb) 
{
	tvbuff_t *tmp_tvb;
	proto_tree *tree;
	guint32 val;
	int **bf;
	header_field_info *hfi;
	char *sep;
	gboolean term;
	unsigned int i, tvb_len;

	offset = dissect_ber_bitstring(implicit_tag, pinfo, parent_tree, tvb, offset, NULL, hf_id, ett_id, &tmp_tvb);
	
	tree = proto_item_get_subtree(ber_last_created_item);
	if (bit_fields && tree) {
		/* tmp_tvb points to the actual bitstring (including any pad bits at the end.
		 * note that this bitstring is not neccessarily always encoded as 4 bytes
		 * so we have to read it byte by byte.
		 */
		val=0;
		tvb_len=tvb_length(tmp_tvb);
		for(i=0;i<4;i++){
			val<<=8;
			if(i<tvb_len){
				val|=tvb_get_guint8(tmp_tvb,i);
			}
		}
		bf = bit_fields;
		sep = " (";
		term = FALSE;
		while (*bf) {
			proto_tree_add_boolean(tree, **bf, tmp_tvb, 0, tvb_len, val);
			hfi = proto_registrar_get_nth(**bf);
			if (val & hfi->bitmask) {
				proto_item_append_text(ber_last_created_item, "%s%s", sep, hfi->name);
				sep = ", ";
				term = TRUE;
			}
			bf++;
		}
		if (term)
			proto_item_append_text(ber_last_created_item, ")");
	}

	if (out_tvb)
		*out_tvb = tmp_tvb;

	return offset;
}

void
proto_register_ber(void)
{
    static hf_register_info hf[] = {
	{ &hf_ber_id_class, {
	    "Class", "ber.id.class", FT_UINT8, BASE_DEC,
	    VALS(ber_class_codes), 0xc0, "Class of BER TLV Identifier", HFILL }},
	{ &hf_ber_bitstring_padding, {
	    "Padding", "ber.bitstring.padding", FT_UINT8, BASE_DEC,
	    NULL, 0x0, "Number of unsused bits in the last octet of the bitstring", HFILL }},
	{ &hf_ber_id_pc, {
	    "P/C", "ber.id.pc", FT_BOOLEAN, 8,
	    TFS(&ber_pc_codes), 0x20, "Primitive or Constructed BER encoding", HFILL }},
	{ &hf_ber_id_uni_tag, {
	    "Tag", "ber.id.uni_tag", FT_UINT8, BASE_DEC,
	    VALS(ber_uni_tag_codes), 0x1f, "Universal tag type", HFILL }},
	{ &hf_ber_id_tag, {
	    "Tag", "ber.id.tag", FT_UINT32, BASE_DEC,
	    NULL, 0, "Tag value for non-Universal classes", HFILL }},
	{ &hf_ber_length, {
	    "Length", "ber.length", FT_UINT32, BASE_DEC,
	    NULL, 0, "Length of contents", HFILL }},

    };

    static gint *ett[] = {
	&ett_ber_octet_string,
    };
    module_t *ber_module;

    proto_ber = proto_register_protocol("Basic Encoding Rules (ASN.1 X.690)", "BER", "ber");
    proto_register_field_array(proto_ber, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));

	proto_set_cant_toggle(proto_ber);

    /* Register preferences */
    ber_module = prefs_register_protocol(proto_ber, NULL);
    prefs_register_bool_preference(ber_module, "show_internals",
	"Show internal BER encapsulation tokens",
	"Whether the dissector should also display internal"
	" ASN.1 BER details such as Identifier and Length fields", &show_internal_ber_fields);
}

void
proto_reg_handoff_ber(void)
{
}
