/*#define DEBUG_BER 1 */
/* TODO: change #.REGISTER signature to new_dissector_t and
 * update call_ber_oid_callback() accordingly.
 *
 * Since we dont pass the TAG/LENGTH from the CHOICE/SEQUENCE/SEQUENCE OF/
 * SET OF helpers through the callbacks to the next pabket-ber helper
 * when the tags are IMPLICIT, this causes a problem when we also have
 * indefinite length at the same time as the tags are implicit.
 * While the proper fix is to change the signatures for packet-ber.c helpers
 * as well as the signatures for the callbacks to include the indefinite length
 * indication that would be a major job.
 * For the time being, kludge, set a global variable in the 
 * CHOICE/SEQUENCE [OF]/SET [OF] helpers to indicate to the next helper
 * whether the length is indefinite or not.
 * This has currently only been implemented for {SEQUENCE|SET} [OF] but not yet
 * CHOICE.
 */
/* packet-ber.c
 * Helpers for ASN.1/BER dissection
 * Ronnie Sahlberg (C) 2004
 *
 * $Id: packet-ber.c 15085 2005-07-26 09:52:06Z etxrab $
 *
 * 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/to_str.h>
#include <epan/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 hf_ber_unknown_OID = -1;
static gint hf_ber_unknown_OCTETSTRING = -1;
static gint hf_ber_unknown_GraphicString = -1;
static gint hf_ber_unknown_NumericString = -1;
static gint hf_ber_unknown_PrintableString = -1;
static gint hf_ber_unknown_IA5String = -1;
static gint hf_ber_unknown_INTEGER = -1;
static gint hf_ber_unknown_ENUMERATED = -1;

static gint ett_ber_octet_string = -1;
static gint ett_ber_unknown = -1;
static gint ett_ber_SEQUENCE = -1;

static gboolean show_internal_ber_fields = FALSE;

proto_item *ber_last_created_item=NULL;
/* kludge to pass indefinite length indications from structure helpers
   to the next helper.  Or else implicite tag + indefinite length wont work.
*/
static gboolean length_is_indefinite=FALSE;

static dissector_table_t ber_oid_dissector_table=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_TeletexString	, "TeletexString, 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 GHashTable *oid_table=NULL;

void
dissect_ber_oid_NULL_callback(tvbuff_t *tvb _U_, packet_info *pinfo _U_, proto_tree *tree _U_)
{
	return;
}


void
register_ber_oid_dissector_handle(char *oid, dissector_handle_t dissector, int proto _U_, char *name)
{
	dissector_add_string("ber.oid", oid, dissector);
	g_hash_table_insert(oid_table, oid, name);
}

void
register_ber_oid_dissector(char *oid, dissector_t dissector, int proto, char *name)
{
	dissector_handle_t dissector_handle;

	dissector_handle=create_dissector_handle(dissector, proto);
	dissector_add_string("ber.oid", oid, dissector_handle);
	g_hash_table_insert(oid_table, oid, name);
}

/* Register the oid name to get translation in proto dissection */
void
register_ber_oid_name(char *oid, char *name)
{
	g_hash_table_insert(oid_table, oid, name);
}

/* Get oid name fom has table to get translation in proto dissection(packet-per.c) */
char *
get_ber_oid_name(char *oid)
{
	return g_hash_table_lookup(oid_table, oid);
}


/* This function is only used by dissect_unknown_ber()
 * It is essentially the get_ber_length() function but we dont
 * deliberately generate a [malformed packet] if the length field looks
 * like garbage.
 * For unknown ber encodings we can just do as much as we can but sometimes it
 * is impossible to do it right.
 * For example:
 * If there are implicit tags, then the length will not be valid when we 
 * try to read the tag/len  but we DONT want to generate a malformed packet
 * here, the packet is not malformed   we just can not parse the ber encoding.
 */
static int
get_ber_length_dont_check_len(proto_tree *tree, tvbuff_t *tvb, int offset, guint32 *length, gboolean *ind) {
	guint8 oct, len;
	guint32 tmp_length;
	gboolean tmp_ind;
	int old_offset=offset;

	tmp_length = 0;
	tmp_ind = FALSE;

	/* dont read beyond the end of the tvb */
	if(tvb_length(tvb)<=(unsigned)offset){
		*length=tvb_length(tvb)+99;
		return offset;
	}

	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--) {
				if((unsigned)offset>=tvb_length(tvb)){
					if (length)
						*length = 999999999;
					if (ind)
						*ind = tmp_ind;
					return offset;
				}
				oct = tvb_get_guint8(tvb, offset);
				offset++;
				tmp_length = (tmp_length<<8) + oct;
			}
		} else {
			/* 8.1.3.6 */
			tmp_ind = TRUE;
			/* TO DO */
		}
	}

	/* check that the length is sane */
	if(tmp_length>(guint32)tvb_reported_length_remaining(tvb,offset)){
		proto_tree_add_text(tree, tvb, old_offset, offset-old_offset, "BER: Error length:%d longer than tvb_reported_length_remaining:%d",tmp_length, tvb_reported_length_remaining(tvb, offset));
		/* the ignorant mans way to generate [malformed packet] */
	}

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

	return offset;
}

/* this function tries to dissect an unknown blob as much as possible.
 * everytime this function is called it is a failure to implement a proper
 * dissector in ethereal.
 * something is missing, so dont become too comfy with this one,
 * when it is called it is a BAD thing  not a good thing.
 * It can not handle IMPLICIT tags nor indefinite length.
 */
int dissect_unknown_ber(packet_info *pinfo, tvbuff_t *tvb, int offset, proto_tree *tree)
{
	int start_offset;
	gint8 class;
	gboolean pc, ind;
	gint32 tag;
	guint32 len;
	proto_item *item=NULL;
	proto_tree *next_tree=NULL;
	tvbuff_t *next_tvb;

	start_offset=offset;

	offset=get_ber_identifier(tvb, offset, &class, &pc, &tag);
	offset=get_ber_length_dont_check_len(NULL, tvb, offset, &len, &ind);

	if(len>(guint32)tvb_length_remaining(tvb, offset)){
		/* hmm   maybe something bad happened or the frame is short,
		   or the ASN1 definition contains implicit tags making
		   decoding impossible?
		   since these are not vital outputs just return instead of 
		   throwing en exception.
		 */
		proto_tree_add_text(tree, tvb, offset, 0, "BER Failure decoding unknown BER structure. I tried but sometimes decoding unknown BER structures is just impossible");
		return tvb_length(tvb);
	}

	switch(class){
	case BER_CLASS_UNI:
		switch(tag){
		case BER_UNI_TAG_INTEGER:
			offset = dissect_ber_integer(FALSE, pinfo, tree, tvb, start_offset, hf_ber_unknown_INTEGER, NULL);
			break;
		case BER_UNI_TAG_ENUMERATED:
			offset = dissect_ber_integer(FALSE, pinfo, tree, tvb, start_offset, hf_ber_unknown_ENUMERATED, NULL);
			break;
		case BER_UNI_TAG_GraphicString:
			offset = dissect_ber_octet_string(FALSE, pinfo, tree, tvb, start_offset, hf_ber_unknown_GraphicString, NULL);
			break;
		case BER_UNI_TAG_OCTETSTRING:
			offset = dissect_ber_octet_string(FALSE, pinfo, tree, tvb, start_offset, hf_ber_unknown_OCTETSTRING, NULL);
			break;
		case BER_UNI_TAG_OID:
			offset=dissect_ber_object_identifier(FALSE, pinfo, tree, tvb, start_offset, hf_ber_unknown_OID, NULL);
			break;
		case BER_UNI_TAG_SEQUENCE:
			item=proto_tree_add_text(tree, tvb, offset, len, "SEQUENCE  (len:%d bytes)   tvb_remaining:%d", len, tvb_length_remaining(tvb, offset));
			if(item){
				next_tree=proto_item_add_subtree(item, ett_ber_SEQUENCE);
			}
			next_tvb=tvb_new_subset(tvb, offset, len, len);
			offset += dissect_unknown_ber(pinfo, next_tvb, 0, next_tree);
			break;
		case BER_UNI_TAG_NumericString:
			offset = dissect_ber_octet_string(FALSE, pinfo, tree, tvb, start_offset, hf_ber_unknown_NumericString, NULL);
			break;
		case BER_UNI_TAG_PrintableString:
			offset = dissect_ber_octet_string(FALSE, pinfo, tree, tvb, start_offset, hf_ber_unknown_PrintableString, NULL);
			break;
		case BER_UNI_TAG_IA5String:
			offset = dissect_ber_octet_string(FALSE, pinfo, tree, tvb, start_offset, hf_ber_unknown_IA5String, NULL);
			break;
		default:
			proto_tree_add_text(tree, tvb, offset, len, "BER: Error can not handle universal tag:%d",tag);
			offset += len;
		}
		break;
	case BER_CLASS_CON:
		item=proto_tree_add_text(tree, tvb, offset, len, "[%d]  (len:%d bytes) ",tag,len);
		if(item){
			next_tree=proto_item_add_subtree(item, ett_ber_SEQUENCE);
		}
		next_tvb=tvb_new_subset(tvb, offset, len, len);
		offset += dissect_unknown_ber(pinfo, next_tvb, 0, next_tree);
		break;
	default:
		proto_tree_add_text(tree, tvb, offset, len, "BER: Error can not handle class:%d (0x%02x)",class,tvb_get_guint8(tvb, start_offset));
		/* some printout here? aborting dissection */
		return tvb_length(tvb);
	}

	/* were there more data to eat? */
	if(offset<(int)tvb_length(tvb)){
		offset=dissect_unknown_ber(pinfo, tvb, offset, tree);
	}

	return offset;
}


int
call_ber_oid_callback(char *oid, tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree)
{
	tvbuff_t *next_tvb;

	next_tvb = tvb_new_subset(tvb, offset, tvb_length_remaining(tvb, offset), tvb_length_remaining(tvb, offset));
	if(!dissector_try_string(ber_oid_dissector_table, oid, next_tvb, pinfo, tree)){
		proto_item *item=NULL;
		proto_tree *next_tree=NULL;

		item=proto_tree_add_text(tree, next_tvb, 0, tvb_length_remaining(tvb, offset), "BER: Dissector for OID:%s not implemented. Contact Ethereal developers if you want this supported", oid);
		if(item){
			next_tree=proto_item_add_subtree(item, ett_ber_unknown);
		}
		dissect_unknown_ber(pinfo, next_tvb, offset, next_tree);
	}

	/*XXX until we change the #.REGISTER signature for _PDU()s 
	 * into new_dissector_t   we have to do this kludge with
	 * manually step past the content in the ANY type.
	 */
	offset+=tvb_length_remaining(tvb, offset);

	return offset;
}


static int dissect_ber_sq_of(gboolean implicit_tag, gint32 type, packet_info *pinfo, proto_tree *parent_tree, tvbuff_t *tvb, int offset, const ber_sequence_t *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, gint8 *class, gboolean *pc, gint32 *tag) {
	guint8 id, t;
	gint8 tmp_class;
	gboolean tmp_pc;
	gint32 tmp_tag;

	id = tvb_get_guint8(tvb, offset);
	offset += 1;
#ifdef DEBUG_BER
printf ("BER ID=%02x", id);
#endif
	/* 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);
#ifdef DEBUG_BER
printf (" %02x", t);
#endif
			offset += 1;
			tmp_tag <<= 7;       
			tmp_tag |= t & 0x7F;
			if (!(t & 0x80)) break;
		}
	}

#ifdef DEBUG_BER
printf ("\n");
#endif
	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, gint8 *class, gboolean *pc, gint32 *tag) 
{
	int old_offset = offset;
	gint8 tmp_class;
	gboolean tmp_pc;
	gint32 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(proto_tree *tree, tvbuff_t *tvb, int offset, guint32 *length, gboolean *ind) {
	guint8 oct, len;
	guint32 tmp_length;
	gboolean tmp_ind;
	int old_offset=offset;

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

	/* check that the length is sane */
	if(tmp_length>(guint32)tvb_reported_length_remaining(tvb,offset)){
		proto_tree_add_text(tree, tvb, old_offset, offset-old_offset, "BER: Error length:%d longer than tvb_reported_length_remaining:%d",tmp_length, tvb_reported_length_remaining(tvb, offset));
		/* the ignorant mans way to generate [malformed packet] */
		tvb_get_guint8(tvb, 99999999);
	}

	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(tree, tvb, offset, &tmp_length, &tmp_ind);
	
	if(show_internal_ber_fields){
		if(tmp_ind){
			proto_tree_add_text(tree, tvb, old_offset, 1, "Length: Indefinite length");
		} else {
			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) {
	gint8 class;
	gboolean pc, ind;
	gint32 tag;
	guint32 len;
	int end_offset;
	proto_item *it;
  guint32 i;

#ifdef DEBUG_BER
{
char *name;
header_field_info *hfinfo;
if(hf_id>0){
hfinfo = proto_registrar_get_nth(hf_id);
name=hfinfo->name;
} else {
name="unnamed";
}
if(tvb_length_remaining(tvb,offset)>3){
printf("OCTET STRING dissect_ber_octet string(%s) entered implicit_tag:%d offset:%d len:%d %02x:%02x:%02x\n",name,implicit_tag,offset,tvb_length_remaining(tvb,offset),tvb_get_guint8(tvb,offset),tvb_get_guint8(tvb,offset+1),tvb_get_guint8(tvb,offset+2));
}else{
printf("OCTET STRING dissect_ber_octet_string(%s) entered\n",name);
}
}
#endif

	if (!implicit_tag) {
		/* 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 ((class!=BER_CLASS_APP)&&(class!=BER_CLASS_PRI))

		if( (class!=BER_CLASS_UNI) 
		  ||((tag<BER_UNI_TAG_NumericString)&&(tag!=BER_UNI_TAG_OCTETSTRING)&&(tag!=BER_UNI_TAG_UTF8String)) ){
		    tvb_ensure_bytes_exist(tvb, offset-2, 2);
	    	    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);
			if(out_tvb)
				*out_tvb=NULL;
			return end_offset;
		}
	} else {
		/* implicit tag so just trust the length of the tvb */
		pc=FALSE;
		len=tvb_length_remaining(tvb,offset);
		end_offset=offset+len;
	}		

	ber_last_created_item = NULL;
	if (pc) {
		/* constructed */
		/* TO DO */
		if(out_tvb)
			*out_tvb=NULL;
    printf("TODO: handle constructed type in packet-ber.c, dissect_ber_octet_string\n");
	} else {
		/* primitive */
		if (hf_id != -1) {
			tvb_ensure_bytes_exist(tvb, offset, len);
			it = proto_tree_add_item(tree, hf_id, tvb, offset, len, FALSE);
			ber_last_created_item = it;
		} else {
      proto_item *pi;
      
      pi=proto_tree_add_text(tree, tvb, offset, len, "Unknown OctetString: Length: 0x%02x, Value: 0x", len);
      if(pi){
        for(i=0;i<len;i++){
          proto_item_append_text(pi,"%02x",tvb_get_guint8(tvb, offset));
          offset++;
        }
      }
    }
		if (out_tvb) {
			if(len<=(guint32)tvb_length_remaining(tvb, offset)){
				*out_tvb = tvb_new_subset(tvb, offset, len, len);
			} else {
				*out_tvb = tvb_new_subset(tvb, offset, tvb_length_remaining(tvb, offset), tvb_length_remaining(tvb, offset));
			}
		}
	}
	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 = NULL;

	offset = dissect_ber_octet_string(implicit_tag, pinfo, tree, tvb, offset, hf_id, (func)?&out_tvb:NULL);
	if (func && out_tvb && (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;
}

/* 8.8 Encoding of a null value */
int 
dissect_ber_null(gboolean implicit_tag, packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, int offset, gint hf_id) {
  gint8 class;
  gboolean pc;
  gint32 tag;
  guint32 len;
  int offset_old;

  if(!implicit_tag){
    offset_old = offset;  
    offset = dissect_ber_identifier(pinfo, tree, tvb, offset, &class, &pc, &tag);
    if ((pc) ||
	(!implicit_tag && ((class != BER_CLASS_UNI) || (tag != BER_UNI_TAG_NULL)))) {
      proto_tree_add_text(tree, tvb, offset_old, offset - offset_old, "BER Error: NULL expected but Class:%d(%s) PC:%d Tag:%d was unexpected", class,val_to_str(class,ber_class_codes,"Unknown"), pc, tag);
    }

    offset_old = offset;  
    offset = dissect_ber_length(pinfo, tree, tvb, offset, &len, NULL);
    if (len) {
      proto_tree_add_text(tree, tvb, offset_old, offset - offset_old, "BER Error: NULL expect zero length but Length=%d", len);
      proto_tree_add_text(tree, tvb, offset, len, "BER Error: unexpected data in NULL type");
      offset += len;
    }
  }

  proto_tree_add_item(tree, hf_id, tvb, offset, 0, FALSE);

  return offset;
}

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

#ifdef DEBUG_BER
{
char *name;
header_field_info *hfinfo;
if(hf_id>0){
hfinfo = proto_registrar_get_nth(hf_id);
name=hfinfo->name;
} else {
name="unnamed";
}
if(tvb_length_remaining(tvb,offset)>3){
printf("INTEGERnew dissect_ber_integer(%s) entered implicit_tag:%d offset:%d len:%d %02x:%02x:%02x\n",name,implicit_tag,offset,tvb_length_remaining(tvb,offset),tvb_get_guint8(tvb,offset),tvb_get_guint8(tvb,offset+1),tvb_get_guint8(tvb,offset+2));
}else{
printf("INTEGERnew dissect_ber_integer(%s) entered implicit_tag:%d \n",name,implicit_tag);
}
}
#endif


	if(!implicit_tag){
	  offset=dissect_ber_identifier(pinfo, tree, tvb, offset, &class, &pc, &tag);
	  offset=dissect_ber_length(pinfo, tree, tvb, offset, &len, NULL);
	} else {
	  len=tvb_length_remaining(tvb, offset);
	}

	/* ok,  we cant handle >4 byte integers so lets fake them */
	if(len>8){
		header_field_info *hfinfo;
		proto_item *pi;

		hfinfo = proto_registrar_get_nth(hf_id);
		pi=proto_tree_add_text(tree, tvb, offset, len, "%s : 0x", hfinfo->name);
		if(pi){
			for(i=0;i<len;i++){
				proto_item_append_text(pi,"%02x",tvb_get_guint8(tvb, offset));
				offset++;
			}
		}
		return offset;
	}
	if(len>4){
		header_field_info *hfinfo;

		val64=0;
		if (len > 0) {
			/* extend sign bit */
			val64 = (gint8)tvb_get_guint8(tvb, offset);
			offset++;
		}
		for(i=1;i<len;i++){
			val64=(val64<<8)|tvb_get_guint8(tvb, offset);
			offset++;
		}
		hfinfo = proto_registrar_get_nth(hf_id);
		proto_tree_add_text(tree, tvb, offset-len, len, "%s: %" PRIu64, hfinfo->name, val64);
		return offset;
	}
	
	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){	
		/*  */
		if (len < 1 || len > 4) {
			proto_tree_add_text(tree, tvb, offset-len, len, "Can't handle integer length: %u", len);
		} else {
			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(gboolean implicit_tag, packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, int offset, gint hf_id)
{
	gint8 class;
	gboolean pc;
	gint32 tag;
	guint32 len;
	guint8 val;
	header_field_info *hfi;

	if(!implicit_tag){
		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)*/
	} else {
		/* nothing to do here, yet */
	}
	
	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, const ber_sequence_t *seq, gint hf_id, gint ett_id) {
	gint8 class;
	gboolean pc, ind = 0, ind_field;
	gint32 tag;
	guint32 len;
	proto_tree *tree = parent_tree;
	proto_item *item = NULL;
	int end_offset, tag_start_offset;
	tvbuff_t *next_tvb;

#ifdef DEBUG_BER
{
char *name;
header_field_info *hfinfo;
if(hf_id>0){
hfinfo = proto_registrar_get_nth(hf_id);
name=hfinfo->name;
} else {
name="unnamed";
}
if(tvb_length_remaining(tvb,offset)>3){
printf("SEQUENCE dissect_ber_sequence(%s) entered offset:%d len:%d %02x:%02x:%02x implicit_tag:%d length_is_indefinite:%d\n",name,offset,tvb_length_remaining(tvb,offset),tvb_get_guint8(tvb,offset),tvb_get_guint8(tvb,offset+1),tvb_get_guint8(tvb,offset+2),implicit_tag,length_is_indefinite);
}else{
printf("SEQUENCE dissect_ber_sequence(%s) entered length_is_indefinite:%d\n",name,length_is_indefinite);
}
}
#endif
	if(!implicit_tag){
		/* 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);
		if(ind){
		  /* if the length is indefinite we dont really know (yet) where the
		   * object ends so assume it spans the rest of the tvb for now.
        	   */
		  end_offset = tvb_length(tvb);
		} else {
		  end_offset = offset + len;
		}

		/* sanity check: we only handle Constructed Universal Sequences */
		if ((class!=BER_CLASS_APP)&&(class!=BER_CLASS_PRI))
		if ((!pc)
		||(!implicit_tag&&((class!=BER_CLASS_UNI)
							||(tag!=BER_UNI_TAG_SEQUENCE)))) {
			tvb_ensure_bytes_exist(tvb, offset-2, 2);
			proto_tree_add_text(tree, tvb, offset-2, 2, "BER Error: Sequence expected but Class:%d(%s) PC:%d Tag:%d was unexpected", class,val_to_str(class,ber_class_codes,"Unknown"), pc, tag);
			return end_offset;
		}
	} else {
		ind=length_is_indefinite;
		/* was implicit tag so just use the length of the tvb */
		len=tvb_length_remaining(tvb,offset);
		end_offset=offset+len;
	}

	/* 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){
		gint8 class;
		gboolean pc;
		gint32 tag;
		guint32 len;
		int hoffset, eoffset, count;

		if(ind){ /* this sequence was of indefinite length, so check for EOC */
#ifdef DEBUG_BER
{
char *name;
header_field_info *hfinfo;
if(hf_id>0){
hfinfo = proto_registrar_get_nth(hf_id);
name=hfinfo->name;
} else {
name="unnamed";
}
if(tvb_length_remaining(tvb,offset)>3){
printf("SEQUENCE dissect_ber_sequence(%s) TESTING FOR EOC implicit_tag:%d offset:%d len:%d %02x:%02x:%02x length_is_indefinite:%d\n",name,implicit_tag,offset,tvb_length_remaining(tvb,offset),tvb_get_guint8(tvb,offset),tvb_get_guint8(tvb,offset+1),tvb_get_guint8(tvb,offset+2),length_is_indefinite);
}else{
printf("SEQUENCE dissect_ber_sequence(%s) TESTING FOR EOC length_is_indefinite:%d\n",name,length_is_indefinite);
}
}
#endif
			if((tvb_get_guint8(tvb, offset)==0)&&(tvb_get_guint8(tvb, offset+1)==0)){
				if(show_internal_ber_fields){
					proto_tree_add_text(tree, tvb, offset, 2, "EOC");
				}
#ifdef DEBUG_BER
{
char *name;
header_field_info *hfinfo;
if(hf_id>0){
hfinfo = proto_registrar_get_nth(hf_id);
name=hfinfo->name;
} else {
name="unnamed";
}
if(tvb_length_remaining(tvb,offset)>3){
printf("SEQUENCE dissect_ber_sq_of(%s) EOC FOUND implicit_tag:%d offset:%d len:%d %02x:%02x:%02x length_is_indefinite:%d\n",name,implicit_tag,offset,tvb_length_remaining(tvb,offset),tvb_get_guint8(tvb,offset),tvb_get_guint8(tvb,offset+1),tvb_get_guint8(tvb,offset+2),length_is_indefinite);
}else{
printf("SEQUENCE dissect_ber_sq_of(%s) EOC FOUND length_is_indefinite:%d\n",name,length_is_indefinite);
}
}
#endif
				return offset+2;
			}
		}
		hoffset = offset;
		/* read header and len for next field */
		tag_start_offset=offset;
		offset = get_ber_identifier(tvb, offset, &class, &pc, &tag);
		offset = get_ber_length(tree, tvb, offset, &len, &ind_field);
		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, tag_start_offset, offset-tag_start_offset, "BER Error: This field lies beyond the end of the known sequence definition. class:%d pc:%d tag:%d len:%d",class,pc,tag,len);
			offset = eoffset;
			continue;
		}

		/* Verify that this one is the one we want.
		 * Skip check completely if class==ANY
		 * of if NOCHKTAG is set
		 */
/* XXX Bug in asn2eth,
 * for   scope            [7]  Scope OPTIONAL,
 * it generates 	
 *   { BER_CLASS_CON, 7, BER_FLAGS_OPTIONAL|BER_FLAGS_NOTCHKTAG, dissect_scope },
 * and there should not be a NOTCHKTAG here
 */
		if( ((seq->class==BER_CLASS_CON)||(seq->class==BER_CLASS_APP)||(seq->class==BER_CLASS_PRI)) && (!(seq->flags&BER_FLAGS_NOOWNTAG)) ){
		  if( (seq->class!=BER_CLASS_ANY) 
		  &&  (seq->tag!=-1)  
		  &&( (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->class == BER_CLASS_UNI){
				proto_tree_add_text(tree, tvb, offset, len,
				    "BER Error: Wrong field in SEQUENCE  expected class:%d (%s) tag:%d (%s) but found class:%d tag:%d",
				    seq->class,val_to_str(seq->class,ber_class_codes,"Unknown"),
				    seq->tag,val_to_str(seq->tag,ber_uni_tag_codes,"Unknown"),
				    class,tag);
			}else{
				proto_tree_add_text(tree, tvb, offset, len,
				    "BER Error: Wrong field in SEQUENCE  expected class:%d (%s) tag:%d but found class:%d tag:%d",
				    seq->class,val_to_str(seq->class,ber_class_codes,"Unknown"),
				    seq->tag,class,tag);
			}
			seq++;
			offset=eoffset;
			continue;
		  }
	        } else if (!(seq->flags & BER_FLAGS_NOTCHKTAG)) {
		  if( (seq->class!=BER_CLASS_ANY) 
		  &&  (seq->tag!=-1)  
		  &&( (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->class == BER_CLASS_UNI){
				proto_tree_add_text(tree, tvb, offset, len, "BER Error: Wrong field in sequence  expected class:%d (%s) tag:%d(%s) but found class:%d(%s) tag:%d",seq->class,val_to_str(seq->class,ber_class_codes,"Unknown"),seq->tag,val_to_str(seq->tag,ber_uni_tag_codes,"Unknown"),class,val_to_str(class,ber_class_codes,"Unknown"),tag);
			}else{
				proto_tree_add_text(tree, tvb, offset, len, "BER Error: Wrong field in sequence  expected class:%d (%s) tag:%d but found class:%d(%s) tag:%d",seq->class,val_to_str(seq->class,ber_class_codes,"Unknown"),seq->tag,class,val_to_str(class,ber_class_codes,"Unknown"),tag);
			}
			seq++;
			offset=eoffset;
			continue;
		  }
		}

		if (!(seq->flags & BER_FLAGS_NOOWNTAG) ) {
			/* 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 */
		if(ind_field 
		|| ((eoffset-hoffset)>tvb_length_remaining(tvb, hoffset)) ){
			/* If the field is indefinite (i.e. we dont know the
			 * length) of if the tvb is short, then just
			 * give it all of the tvb and hope for the best.
			 */
			next_tvb = tvb_new_subset(tvb, hoffset, tvb_length_remaining(tvb,hoffset), tvb_length_remaining(tvb,hoffset));
		} else {
			next_tvb = tvb_new_subset(tvb, hoffset, eoffset-hoffset, eoffset-hoffset);
		}

#ifdef DEBUG_BER
{
char *name;
header_field_info *hfinfo;
if(hf_id>0){
hfinfo = proto_registrar_get_nth(hf_id);
name=hfinfo->name;
} else {
name="unnamed";
}
if(tvb_length_remaining(next_tvb,0)>3){
printf("SEQUENCE dissect_ber_sequence(%s) calling subdissector offset:%d len:%d %02x:%02x:%02x indefinite_len:%d no_own_tag:%d\n",name,offset,tvb_length_remaining(next_tvb,0),tvb_get_guint8(next_tvb,0),tvb_get_guint8(next_tvb,1),tvb_get_guint8(next_tvb,2),ind_field,!!(seq->flags&BER_FLAGS_NOOWNTAG));
}else{
printf("SEQUENCE dissect_ber_sequence(%s) calling subdissector indefinite_len:%d no_own_tag:%d\n",name,ind_field,!!(seq->flags&BER_FLAGS_NOOWNTAG));
}
}
#endif
		length_is_indefinite=ind_field;
		count=seq->func(pinfo, tree, next_tvb, 0);
		length_is_indefinite=FALSE;
#ifdef DEBUG_BER
{
char *name;
header_field_info *hfinfo;
if(hf_id>0){
hfinfo = proto_registrar_get_nth(hf_id);
name=hfinfo->name;
} else {
name="unnamed";
}
printf("SEQUENCE dissect_ber_sequence(%s) subdissector ate %d bytes\n",name,count);
}
#endif
		seq++;
		if( (len==0)&&(!ind_field) ) {
			offset = eoffset;
		} else {
			offset = hoffset+count;
		}
		/* if it was optional and no bytes were eaten and it was */
		/* supposed to (len<>0), just try again. */
		if((len!=0)&&(count==0)&&(seq->flags&BER_FLAGS_OPTIONAL)){
			goto ber_sequence_try_again;
		}
	}

	/* if we didnt end up at exactly offset, then we ate too many bytes */
	if (offset != end_offset) {
		tvb_ensure_bytes_exist(tvb, offset-2, 2);
		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 
 * If we did not find a matching choice,  just return offset unchanged
 * in case it was a CHOICE { } OPTIONAL
 */
int
dissect_ber_choice(packet_info *pinfo, proto_tree *parent_tree, tvbuff_t *tvb, int offset, const ber_choice_t *choice, gint hf_id, gint ett_id, gint *branch_taken)
{
	gint8 class;
	gboolean pc, ind;
	gint32 tag;
	guint32 len;
	const ber_choice_t *ch;
	proto_tree *tree=parent_tree;
	proto_item *item=NULL;
	int end_offset, start_offset, count;
	int hoffset = offset;
	header_field_info	*hfinfo;
	gint reported_length, length;
	tvbuff_t *next_tvb;

#ifdef DEBUG_BER
{
char *name;
header_field_info *hfinfo;
if(hf_id>0){
hfinfo = proto_registrar_get_nth(hf_id);
name=hfinfo->name;
} else {
name="unnamed";
}
if(tvb_length_remaining(tvb,offset)>3){
printf("CHOICE dissect_ber_choice(%s) entered offset:%d len:%d %02x:%02x:%02x\n",name,offset,tvb_length_remaining(tvb,offset),tvb_get_guint8(tvb,offset),tvb_get_guint8(tvb,offset+1),tvb_get_guint8(tvb,offset+2));
}else{
printf("CHOICE dissect_ber_choice(%s) entered len:%d\n",name,tvb_length_remaining(tvb,offset));
}
}
#endif
	start_offset=offset;

	/* read header and len for choice field */
	offset=get_ber_identifier(tvb, offset, &class, &pc, &tag);
	offset=get_ber_length(parent_tree, tvb, offset, &len, &ind);
	if(ind){
	  /* if the length is indefinite we dont really know (yet) where the
	   * object ends so assume it spans the rest of the tvb for now.
	   * XXX - what if it runs past the end of the tvb because we
	   * need to do reassembly?
           */
 	  end_offset = tvb_length(tvb);
	} else {
	  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;
	if(branch_taken){
		*branch_taken=-1;
	}
	while(ch->func){
choice_try_again:
		if(branch_taken){
			(*branch_taken)++;
		}
#ifdef DEBUG_BER
printf("CHOICE testing potential subdissector class:%d:(expected)%d  tag:%d:(expected)%d flags:%d\n",class,ch->class,tag,ch->tag,ch->flags);
#endif
		if( ((ch->class==class)&&(ch->tag==tag))
		||  ((ch->class==class)&&(ch->tag==-1)&&(ch->flags&BER_FLAGS_NOOWNTAG))
		){
			if (!(ch->flags & BER_FLAGS_NOOWNTAG)){
				/* dissect header and len for field */
				hoffset = dissect_ber_identifier(pinfo, tree, tvb, start_offset, NULL, NULL, NULL);
				hoffset = dissect_ber_length(pinfo, tree, tvb, hoffset, NULL, NULL);
				start_offset=hoffset;
			}
			/* 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);
				}
			}

			reported_length = end_offset-start_offset;
			length = tvb_length_remaining(tvb, hoffset);
			if (length > reported_length)
				length = reported_length;
			next_tvb=tvb_new_subset(tvb, hoffset, length, reported_length);

#ifdef DEBUG_BER
{
char *name;
header_field_info *hfinfo;
if(hf_id>0){
hfinfo = proto_registrar_get_nth(hf_id);
name=hfinfo->name;
} else {
name="unnamed";
}
if(tvb_length_remaining(next_tvb,0)>3){
printf("CHOICE dissect_ber_choice(%s) calling subdissector start_offset:%d offset:%d len:%d %02x:%02x:%02x\n",name,start_offset,offset,tvb_length_remaining(next_tvb,0),tvb_get_guint8(next_tvb,0),tvb_get_guint8(next_tvb,1),tvb_get_guint8(next_tvb,2));
}else{
printf("CHOICE dissect_ber_choice(%s) calling subdissector len:%d\n",name,tvb_length(next_tvb));
}
}
#endif
			count=ch->func(pinfo, tree, next_tvb, 0);
#ifdef DEBUG_BER
{
char *name;
header_field_info *hfinfo;
if(hf_id>0){
hfinfo = proto_registrar_get_nth(hf_id);
name=hfinfo->name;
} else {
name="unnamed";
}
printf("CHOICE dissect_ber_choice(%s) subdissector ate %d bytes\n",name,count);
}
#endif
			if((count==0)&&(ch->class==class)&&(ch->tag==-1)&&(ch->flags&BER_FLAGS_NOOWNTAG)){
				/* wrong one, break and try again */
				ch++;
				goto choice_try_again;
			}

			offset=hoffset+count;
			if(ind){
				/* this choice was of indefinite length so we 
				 * just have to trust what the subdissector 
				 * told us about the length consumed.
				 */
				return offset;
			} else {
				return end_offset;
			}
			break;
		}
		ch++;
	}

	if(branch_taken){
		/* none of the branches were taken so set the param 
		   back to -1 */
		*branch_taken=-1;
	}
#ifdef REMOVED
	/*XXX here we should have another flag to the CHOICE to distinguish
	 * between teh case when we know it is a mandatory   or if the CHOICE is optional == no arm matched */

	/* 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;
#endif

	return start_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)
{
	gint8 class;
	gboolean pc;
	gint32 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) ){
		tvb_ensure_bytes_exist(tvb, offset-2, 2);
	        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, gint32 type, packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, int offset, gint hf_id, tvbuff_t **out_tvb) {
	gint8 class;
	gboolean pc;
	gint32 tag;
	guint32 len;
	int eoffset;
	int hoffset = offset;

#ifdef DEBUG_BER
{
char *name;
header_field_info *hfinfo;
if(hf_id>0){
hfinfo = proto_registrar_get_nth(hf_id);
name=hfinfo->name;
} else {
name="unnamed";
}
if(tvb_length_remaining(tvb,offset)>3){
printf("RESTRICTED STRING dissect_ber_octet string(%s) entered implicit_tag:%d offset:%d len:%d %02x:%02x:%02x\n",name,implicit_tag,offset,tvb_length_remaining(tvb,offset),tvb_get_guint8(tvb,offset),tvb_get_guint8(tvb,offset+1),tvb_get_guint8(tvb,offset+2));
}else{
printf("RESTRICTED STRING dissect_ber_octet_string(%s) entered\n",name);
}
}
#endif

	if (!implicit_tag) {
		offset = get_ber_identifier(tvb, offset, &class, &pc, &tag);
		offset = get_ber_length(tree, tvb, offset, &len, NULL);
		eoffset = offset + len;

		/* sanity check */
		if( (class!=BER_CLASS_UNI)
		  ||(tag != type) ){
	            tvb_ensure_bytes_exist(tvb, offset-2, 2);
	    	    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(implicit_tag, 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 = NULL;

	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 (out_tvb && tvb_length(out_tvb) >= name_len) {
			tvb_memcpy(out_tvb, name_string, 0, name_len-1);
			name_string[name_len-1] = '\0';
		} else if (out_tvb) {
			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) {
	gint8 class;
	gboolean pc;
	gint32 tag;
	guint32 len;
	int eoffset;
	char str[MAX_OID_STR_LEN], *name;
	proto_item *item;

#ifdef DEBUG_BER
{
char *name;
header_field_info *hfinfo;
if(hf_id>0){
hfinfo = proto_registrar_get_nth(hf_id);
name=hfinfo->name;
} else {
name="unnamed";
}
if(tvb_length_remaining(tvb,offset)>3){
printf("OBJECT IDENTIFIER dissect_ber_object_identifier(%s) entered implicit_tag:%d offset:%d len:%d %02x:%02x:%02x\n",name,implicit_tag,offset,tvb_length_remaining(tvb,offset),tvb_get_guint8(tvb,offset),tvb_get_guint8(tvb,offset+1),tvb_get_guint8(tvb,offset+2));
}else{
printf("OBJECT IDENTIFIER dissect_ber_object_identifier(%s) entered\n",name);
}
}
#endif

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

	if (!implicit_tag) {
		/* sanity check */
		offset = get_ber_identifier(tvb, offset, &class, &pc, &tag);
		offset = dissect_ber_length(pinfo, tree, tvb, offset, &len, NULL);
		eoffset = offset + len;
		if( (class!=BER_CLASS_UNI)
		  ||(tag != BER_UNI_TAG_OID) ){
	            tvb_ensure_bytes_exist(tvb, offset-2, 2);
	    	    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;
		}
	} else {
		len=tvb_length_remaining(tvb,offset);
		eoffset=offset+len;
	}

	oid_to_str_buf(tvb_get_ptr(tvb, offset, len), len, str);
	offset += len;

	if (hf_id != -1) {
		item=proto_tree_add_string(tree, hf_id, tvb, offset - len, len, str);
		/* see if we know the name of this oid */
		if(item){
			name=g_hash_table_lookup(oid_table, str);
			if(name){
				proto_item_append_text(item, " (%s)", name);
			}
		}
	}

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

	return eoffset;
}

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

#ifdef DEBUG_BER
{
char *name;
header_field_info *hfinfo;
if(hf_id>0){
hfinfo = proto_registrar_get_nth(hf_id);
name=hfinfo->name;
} else {
name="unnamed";
}
if(tvb_length_remaining(tvb,offset)>3){
printf("SQ OF dissect_ber_sq_of(%s) entered implicit_tag:%d offset:%d len:%d %02x:%02x:%02x length_is_indefinite:%d\n",name,implicit_tag,offset,tvb_length_remaining(tvb,offset),tvb_get_guint8(tvb,offset),tvb_get_guint8(tvb,offset+1),tvb_get_guint8(tvb,offset+2),length_is_indefinite);
}else{
printf("SQ OF dissect_ber_sq_of(%s) entered length_is_indefinite:%d\n",name,length_is_indefinite);
}
}
#endif

	if(!implicit_tag){
		/* first we must read the sequence of header */
		offset = dissect_ber_identifier(pinfo, tree, tvb, offset, &class, &pc, &tag);
		offset = dissect_ber_length(pinfo, tree, tvb, offset, &len, &ind);
		if(ind){
		  /* if the length is indefinite we dont really know (yet) where the
		   * object ends so assume it spans the rest of the tvb for now.
        	   */
		  end_offset = tvb_length(tvb);
		} else {
		  end_offset = offset + len;
		}

		/* sanity check: we only handle Constructed Universal Sequences */
		if ((class!=BER_CLASS_APP)&&(class!=BER_CLASS_PRI))
		if (!pc
			||(!implicit_tag&&((class!=BER_CLASS_UNI)
							||(tag!=type)))) {
			tvb_ensure_bytes_exist(tvb, offset-2, 2);
			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;
		}
	} else {
		ind=length_is_indefinite;
		len=tvb_length_remaining(tvb,offset);
		end_offset = offset + len;
	}

	/* count number of items */
	cnt = 0;
	hoffset = offset;
	while (offset < end_offset){
		guint32 len;

		if(ind){ /* this sequence of was of indefinite length, so check for EOC */

			if((tvb_get_guint8(tvb, offset)==0)&&(tvb_get_guint8(tvb, offset+1)==0)){
				break;
			}
		}

		/* read header and len for next field */
		offset = get_ber_identifier(tvb, offset, NULL, NULL, NULL);
		offset = get_ber_length(tree, 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){
		gint8 class;
		gboolean pc;
		gint32 tag;
		guint32 len;
		int eoffset;
		int hoffset, count;

		if(ind){ /* this sequence of was of indefinite length, so check for EOC */
#ifdef DEBUG_BER
{
char *name;
header_field_info *hfinfo;
if(hf_id>0){
hfinfo = proto_registrar_get_nth(hf_id);
name=hfinfo->name;
} else {
name="unnamed";
}
if(tvb_length_remaining(tvb,offset)>3){
printf("SQ OF dissect_ber_sq_of(%s) TESTING FOR EOC implicit_tag:%d offset:%d len:%d %02x:%02x:%02x length_is_indefinite:%d\n",name,implicit_tag,offset,tvb_length_remaining(tvb,offset),tvb_get_guint8(tvb,offset),tvb_get_guint8(tvb,offset+1),tvb_get_guint8(tvb,offset+2),length_is_indefinite);
}else{
printf("SQ OF dissect_ber_sq_of(%s) TESTING FOR EOC length_is_indefinite:%d\n",name,length_is_indefinite);
}
}
#endif
			if((tvb_get_guint8(tvb, offset)==0)&&(tvb_get_guint8(tvb, offset+1)==0)){
				if(show_internal_ber_fields){
					proto_tree_add_text(tree, tvb, offset, 2, "EOC");
				}
#ifdef DEBUG_BER
{
char *name;
header_field_info *hfinfo;
if(hf_id>0){
hfinfo = proto_registrar_get_nth(hf_id);
name=hfinfo->name;
} else {
name="unnamed";
}
if(tvb_length_remaining(tvb,offset)>3){
printf("SQ OF dissect_ber_sq_of(%s) EOC FOUND implicit_tag:%d offset:%d len:%d %02x:%02x:%02x length_is_indefinite:%d\n",name,implicit_tag,offset,tvb_length_remaining(tvb,offset),tvb_get_guint8(tvb,offset),tvb_get_guint8(tvb,offset+1),tvb_get_guint8(tvb,offset+2),length_is_indefinite);
}else{
printf("SQ OF dissect_ber_sq_of(%s) EOC FOUND length_is_indefinite:%d\n",name,length_is_indefinite);
}
}
#endif
				return offset+2;
			}
		}
		hoffset = offset;
		/* read header and len for next field */
		offset = get_ber_identifier(tvb, offset, &class, &pc, &tag);
		offset = get_ber_length(tree, tvb, offset, &len, &ind_field);
		if(ind_field){
			/* if the length is indefinite we dont really know (yet) where the
			 * object ends so assume it spans the rest of the tvb for now.
        		 */
	  		eoffset = tvb_length(tvb);
		} else {
			eoffset = offset + len;
		}

		/* verify that this one is the one we want */
		if(seq->class!=BER_CLASS_ANY){
		  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 in SQ OF");
				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);
		}
		
#ifdef DEBUG_BER
{
char *name;
header_field_info *hfinfo;
if(hf_id>0){
hfinfo = proto_registrar_get_nth(hf_id);
name=hfinfo->name;
} else {
name="unnamed";
}
if(tvb_length_remaining(tvb,0)>3){
printf("SQ OF dissect_ber_sq_of(%s) calling subdissector offset:%d len:%d %02x:%02x:%02x indefinite_len:%d no_own_tag:%d\n",name,offset,tvb_length_remaining(tvb,0),tvb_get_guint8(tvb,0),tvb_get_guint8(tvb,1),tvb_get_guint8(tvb,2),ind_field,!!(seq->flags&BER_FLAGS_NOOWNTAG));
}else{
printf("SQ OF dissect_ber_sq_of(%s) calling subdissector indefinite_len:%d no_own_tag:%d\n",name,ind_field,!!(seq->flags&BER_FLAGS_NOOWNTAG));
}
}
#endif
		/* call the dissector for this field */
		length_is_indefinite=ind_field;
		count=seq->func(pinfo, tree, tvb, hoffset)-hoffset;
		length_is_indefinite=FALSE;
#ifdef DEBUG_BER
{
char *name;
header_field_info *hfinfo;
if(hf_id>0){
hfinfo = proto_registrar_get_nth(hf_id);
name=hfinfo->name;
} else {
name="unnamed";
}
printf("SQ OF dissect_ber_sq_of(%s) subdissector ate %d bytes\n",name,count);
}
#endif
		if(ind_field){
			/* previous field was of indefinite length so we have
			 * no choice but use whatever the subdissector told us
			 * as size for the field.
			 */
			cnt++;
			offset = hoffset+count;
		} else {
			cnt++;
			offset = eoffset;
		}
	}

	/* if we didnt end up at exactly offset, then we ate too many bytes */
	if (offset != end_offset) {
		tvb_ensure_bytes_exist(tvb, offset-2, 2);
		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, const ber_sequence_t *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, const ber_sequence_t *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_GeneralizedTime(gboolean implicit_tag, packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, int offset, gint hf_id)
{
	char str[32];
	const guint8 *tmpstr;
	gint8 class;
	gboolean pc;
	gint32 tag;
	guint32 len;
	int end_offset;

	if(!implicit_tag){
	  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)){
		tvb_ensure_bytes_exist(tvb, offset-2, 2);
	        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;
	  }
        } else {
	  len=tvb_length_remaining(tvb,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, const asn_namedbit *named_bits, gint hf_id, gint ett_id, tvbuff_t **out_tvb) 
{
	gint8 class;
	gboolean pc, ind;
	gint32 tag;
	guint32 len;
	guint8 pad=0, b0, b1, val;
	int end_offset;
	proto_item *item = NULL;
	proto_tree *tree = NULL;
	const asn_namedbit *nb;
	char *sep;
	gboolean term;

	if(!implicit_tag){
	  /* 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) ){
		    tvb_ensure_bytes_exist(tvb, offset-2, 2);
	    	    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;
		}
	  }
	} else {
	  pc=0;
	  len=tvb_length_remaining(tvb,offset);
	  end_offset=offset+len;
	}

	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) {
			if(len<=(guint32)tvb_length_remaining(tvb, offset)){
				*out_tvb = tvb_new_subset(tvb, offset, len, len);
			} else {
				*out_tvb = tvb_new_subset(tvb, offset, tvb_length_remaining(tvb, offset), tvb_length_remaining(tvb, offset));
			}
		}
	}

	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);
					term = TRUE;
				}
			} else {
				if (item && nb->fstr) {
					proto_item_append_text(item, "%s%s", sep, nb->fstr);
					term = TRUE;
				}
			}
			nb++;
			if (term) sep = ", ";
		}
		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 = NULL;
	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) {
		/* 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 }},
	{ &hf_ber_unknown_OCTETSTRING, {
	    "OCTETSTRING", "ber.unknown.OCTETSTRING", FT_BYTES, BASE_HEX,
	    NULL, 0, "This is an unknown OCTETSTRING", HFILL }},
	{ &hf_ber_unknown_GraphicString, {
	    "GRAPHICSTRING", "ber.unknown.GRAPHICSTRING", FT_STRING, BASE_HEX,
	    NULL, 0, "This is an unknown GRAPHICSTRING", HFILL }},
	{ &hf_ber_unknown_OID, {
	    "OID", "ber.unknown.OID", FT_STRING, BASE_NONE,
	    NULL, 0, "This is an unknown Object Identifier", HFILL }},
	{ &hf_ber_unknown_NumericString, {
	    "NumericString", "ber.unknown.NumericString", FT_STRING, BASE_NONE,
	    NULL, 0, "This is an unknown NumericString", HFILL }},
	{ &hf_ber_unknown_PrintableString, {
	    "PrintableString", "ber.unknown.PrintableString", FT_STRING, BASE_NONE,
	    NULL, 0, "This is an unknown PrintableString", HFILL }},
	{ &hf_ber_unknown_IA5String, {
	    "IA5String", "ber.unknown.IA5String", FT_STRING, BASE_NONE,
	    NULL, 0, "This is an unknown IA5String", HFILL }},
	{ &hf_ber_unknown_INTEGER, {
	    "INTEGER", "ber.unknown.INTEGER", FT_UINT32, BASE_DEC,
	    NULL, 0, "This is an unknown INTEGER", HFILL }},
	{ &hf_ber_unknown_ENUMERATED, {
	    "ENUMERATED", "ber.unknown.ENUMERATED", FT_UINT32, BASE_DEC,
	    NULL, 0, "This is an unknown ENUMERATED", HFILL }},

    };

    static gint *ett[] = {
	&ett_ber_octet_string,
	&ett_ber_unknown,
	&ett_ber_SEQUENCE,
    };
    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);

    ber_oid_dissector_table = register_dissector_table("ber.oid", "BER OID Dissectors", FT_STRING, BASE_NONE);
    oid_table=g_hash_table_new(g_str_hash, g_str_equal);
}

void
proto_reg_handoff_ber(void)
{
}
