/* packet-diameter.c
 * Routines for DIAMETER packet disassembly
 *
 * $Id: packet-diameter.c,v 1.26 2001/08/04 19:50:33 guy Exp $
 *
 * Copyright (c) 2001 by David Frascone <dave@frascone.com>
 *
 * 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.
 */

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

#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif

#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <glib.h>
#include "packet.h"
#include "resolv.h"
#include "prefs.h"

/* This must be defined before we include packet-diameter-defs.h s*/

/* Valid data types */
typedef enum {
  /* Base Types */
  DIAMETER_OCTET_STRING = 1,
  DIAMETER_INTEGER32,
  DIAMETER_INTEGER64,
  DIAMETER_UNSIGNED32,
  DIAMETER_UNSIGNED64,
  DIAMETER_FLOAT32,
  DIAMETER_FLOAT64,
  DIAMETER_FLOAT128,
  DIAMETER_GROUPED,

  /* Derived Types */
  DIAMETER_IP_ADDRESS,         /* OctetString */
  DIAMETER_TIME,               /* Integer 32 */
  DIAMETER_UTF8STRING,         /* OctetString */
  DIAMETER_IDENTITY,           /* OctetString */
  DIAMETER_ENUMERATED,         /* Integer 32 */
  DIAMETER_IP_FILTER_RULE,     /* OctetString */
  DIAMETER_QOS_FILTER_RULE     /* OctetString */
  
} diameterDataType;

typedef struct avp_info {
  guint32           code;
  gchar            *name;
  diameterDataType  type;
  value_string     *values;
} avpInfo;

#include "packet-diameter-defs.h"

#define  NTP_TIME_DIFF                   (2208988800UL)

#define TCP_PORT_DIAMETER	1812
#define SCTP_PORT_DIAMETER	1812

static const true_false_string flags_set_truth = {
  "Set",
  "Not set"
};

static const true_false_string reserved_set = {
  "*** Error! Reserved Bit is Set",
  "Ok"
};
static int proto_diameter = -1;
static int hf_diameter_length = -1;
static int hf_diameter_code = -1;
static int hf_diameter_hopbyhopid =-1;
static int hf_diameter_endtoendid =-1;
static int hf_diameter_reserved = -1;
static int hf_diameter_version = -1;
static int hf_diameter_vendor_id = -1;
static int hf_diameter_flags = -1;
static int hf_diameter_flags_request = -1;
static int hf_diameter_flags_proxyable = -1;
static int hf_diameter_flags_error = -1;
static int hf_diameter_flags_reserved3 = -1;
static int hf_diameter_flags_reserved4 = -1;
static int hf_diameter_flags_reserved5 = -1;
static int hf_diameter_flags_reserved6 = -1;
static int hf_diameter_flags_reserved7 = -1;

static int hf_diameter_avp_code = -1;
static int hf_diameter_avp_length = -1;
static int hf_diameter_avp_reserved = -1;
static int hf_diameter_avp_flags = -1;
static int hf_diameter_avp_flags_vendor_specific = -1;
static int hf_diameter_avp_flags_mandatory = -1;
static int hf_diameter_avp_flags_protected = -1;
static int hf_diameter_avp_flags_reserved3 = -1;
static int hf_diameter_avp_flags_reserved4 = -1;
static int hf_diameter_avp_flags_reserved5 = -1;
static int hf_diameter_avp_flags_reserved6 = -1;
static int hf_diameter_avp_flags_reserved7 = -1;
static int hf_diameter_avp_vendor_id = -1;


static int hf_diameter_avp_data_uint32 = -1;
static int hf_diameter_avp_data_int32 = -1;
static int hf_diameter_avp_data_uint64 = -1;
static int hf_diameter_avp_data_int64 = -1;
static int hf_diameter_avp_data_bytes = -1;
static int hf_diameter_avp_data_string = -1;
static int hf_diameter_avp_data_v4addr = -1;
static int hf_diameter_avp_data_v6addr = -1;
static int hf_diameter_avp_data_time = -1;

static gint ett_diameter = -1;
static gint ett_diameter_flags = -1;
static gint ett_diameter_avp = -1;
static gint ett_diameter_avp_flags = -1;
static gint ett_diameter_avpinfo = -1;

static char gbl_diameterString[200];
static int gbl_diameterTcpPort=TCP_PORT_DIAMETER;
static int gbl_diameterSctpPort=SCTP_PORT_DIAMETER;

typedef struct _e_diameterhdr {
  guint32  versionLength;
  guint32  flagsCmdCode;
  guint32  vendorId;
  guint32  hopByHopId;
  guint32  endToEndId;
} e_diameterhdr;

typedef struct _e_avphdr {
  guint32 avp_code;
  guint32 avp_flagsLength;
  guint32 avp_vendorId;           /* optional */
} e_avphdr;

#define AUTHENTICATOR_LENGTH 12

/* Diameter Header Flags */
/*                                      RPrrrrrrCCCCCCCCCCCCCCCCCCCCCCCC  */
#define DIAM_FLAGS_R 0x80
#define DIAM_FLAGS_P 0x40
#define DIAM_FLAGS_E 0x20
#define DIAM_FLAGS_RESERVED3 0x10
#define DIAM_FLAGS_RESERVED4 0x08
#define DIAM_FLAGS_RESERVED5 0x04
#define DIAM_FLAGS_RESERVED6 0x02
#define DIAM_FLAGS_RESERVED7 0x01
#define DIAM_FLAGS_RESERVED  0x1f

#define DIAM_LENGTH_MASK  0x00ffffffl
#define DIAM_COMMAND_MASK DIAM_LENGTH_MASK
#define DIAM_GET_FLAGS(dh)                ((dh.flagsCmdCode & ~DIAM_COMMAND_MASK) >> 24)
#define DIAM_GET_VERSION(dh)              ((dh.versionLength & (~DIAM_LENGTH_MASK)) >> 24)
#define DIAM_GET_COMMAND(dh)              (dh.flagsCmdCode & DIAM_COMMAND_MASK)
#define DIAM_GET_LENGTH(dh)               (dh.versionLength & DIAM_LENGTH_MASK)

/* Diameter AVP Flags */
#define AVP_FLAGS_P 0x20
#define AVP_FLAGS_V 0x80
#define AVP_FLAGS_M 0x40
#define AVP_FLAGS_RESERVED3 0x10
#define AVP_FLAGS_RESERVED4 0x08
#define AVP_FLAGS_RESERVED5 0x04
#define AVP_FLAGS_RESERVED6 0x02
#define AVP_FLAGS_RESERVED7 0x01
#define AVP_FLAGS_RESERVED 0x1f          /* 00011111  -- V M P X X X X X */

#define MIN_AVP_SIZE (sizeof(e_avphdr) - sizeof(guint32))
#define MIN_DIAMETER_SIZE (sizeof(e_diameterhdr) + MIN_AVP_SIZE)

static void dissect_avps(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree);




/* Diameter Manipulation Routines (mess with our strucutres) */

diameterDataType
diameter_avp_get_type(guint32 avpCode){
  int i;
  for (i=0; diameter_avps[i].name; i++) {
	if (avpCode == diameter_avps[i].code) {
	  /* We found it! */
	  return diameter_avps[i].type;
	}
  }
  /* If we don't find it, assume it's data */
  g_warning("DIAMETER: Unable to find type for avpCode %d!", avpCode);
  return DIAMETER_OCTET_STRING;
} /* diameter_avp_get_type */

static gchar *
diameter_avp_get_name(guint32 avpCode)
{
  static gchar buffer[64];

  int i;
  for (i=0; diameter_avps[i].name; i++) {
	if (avpCode == diameter_avps[i].code) {
	  /* We found it! */
	  return diameter_avps[i].name;
	}
  }
  /* If we don't find it, build a name string */
  sprintf(buffer, "Unknown AVP:0x%08x", avpCode);
  return buffer;
} /* diameter_avp_get_name */
static gchar *
diameter_avp_get_value(guint32 avpCode, guint32 avpValue)
{
  static gchar buffer[64];

  int i;
  for (i=0; diameter_avps[i].name; i++) {
	if (avpCode == diameter_avps[i].code) {
	  /* We found the code.  Now find the value! */
	  if (!diameter_avps[i].values) 
		break;
	  return val_to_str(avpValue, diameter_avps[i].values , "Unknown Value: 0x%08x");
	}
  }
  /* If we don't find the avp, build a value string */
  sprintf(buffer, "Unknown AVP! Value: 0x%08x", avpValue);
  return buffer;
} /* diameter_avp_get_value */

static gchar *
diameter_time_to_string(gchar *timeValue)
{
  static gchar buffer[64];
  int intval;
  struct tm lt;

  intval=pntohl(*((guint32*)timeValue));
  intval -= NTP_TIME_DIFF;
  lt=*localtime((time_t *)&intval);
  strftime(buffer, 1024, 
		   "%a, %d %b %Y %H:%M:%S %z",&lt);
  return buffer;
} /* diameter_time_to_string */


/* Code to actually dissect the packets */

/*
 * Main dissector
 */
static void dissect_diameter(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{

  /* Set up structures needed to add the protocol subtree and manage it */
  proto_item      *ti;
  proto_item      *tf;
  proto_tree      *flags_tree;
  tvbuff_t        *avp_tvb;
  proto_tree      *diameter_tree;
  e_diameterhdr    dh;
  size_t           offset=0;
  size_t           avplength;
  proto_tree      *avp_tree;
  proto_item      *avptf;
  int              BadPacket = FALSE;
  guint32          commandCode, pktLength;
  guint8           version, flags;
  gchar            flagstr[64] = "<None>";
  gchar           *fstr[] = {"RSVD7", "RSVD6", "RSVD5", "RSVD4", "RSVD3", "Error", "Proxyable", "Request" };
  gchar            commandString[64], vendorString[64];
  gint        i;
  guint      bpos;
	
  /* Make entries in Protocol column and Info column on summary display */
  if (check_col(pinfo->fd, COL_PROTOCOL)) 
	col_add_str(pinfo->fd, COL_PROTOCOL, "Diameter");
  if (check_col(pinfo->fd, COL_INFO)) 
	col_clear(pinfo->fd, COL_INFO);
	
  /* Copy our header */
  tvb_memcpy(tvb, (guint8*) &dh, offset, sizeof(dh));
	
  /* Fix byte ordering in our static structure */
  dh.versionLength = ntohl(dh.versionLength);
  dh.flagsCmdCode = ntohl(dh.flagsCmdCode);
  dh.vendorId = ntohl(dh.vendorId);
  dh.hopByHopId = ntohl(dh.hopByHopId);
  dh.endToEndId = ntohl(dh.endToEndId);

  if (dh.vendorId) {
	strcpy(vendorString, 
		   val_to_str(dh.vendorId, diameter_vendor_specific_vendors, "Unknown Vendor: %08x"));
  } else {
	strcpy(vendorString, "None");
  }


  /* Do the bit twiddling */
  version = DIAM_GET_VERSION(dh);
  pktLength = DIAM_GET_LENGTH(dh);
  flags = DIAM_GET_FLAGS(dh);
  commandCode = DIAM_GET_COMMAND(dh);

  /* Set up our flags */
  if (check_col(pinfo->fd, COL_INFO) || tree) {  
	flagstr[0]=0;
	for (i = 0; i < 8; i++) {
	  bpos = 1 << i;
	  if (flags & bpos) {
		if (flagstr[0]) {
		  strcat(flagstr, ", ");
		}
		strcat(flagstr, fstr[i]);
	  }
	}
	if (strlen(flagstr) == 0) {
	  strcpy(flagstr,"<None>");
	}
  }
  
  /* Set up our commandString */
  strcpy(commandString, val_to_str(commandCode, diameter_command_code_vals, "Unknown Command: 0x%08x"));
  if (flags & DIAM_FLAGS_R) 
	strcat(commandString, "-Request");
  else
	strcat(commandString, "-Answer");

  /* Short packet.  Should have at LEAST one avp */
  if (pktLength < MIN_DIAMETER_SIZE) {
	g_warning("DIAMETER: Packet too short: %d bytes less than min size (%d bytes))",
			  pktLength, MIN_DIAMETER_SIZE);
	BadPacket = TRUE;
  }

  /* And, check our reserved flags/version */
  if ((flags & DIAM_FLAGS_RESERVED) ||
	  (version != 1)) {
	g_warning("DIAMETER: Bad packet: Bad Flags(0x%x) or Version(%u)",
			  flags, version);
	BadPacket = TRUE;
  }

  if (check_col(pinfo->fd, COL_INFO)) {
	col_add_fstr(pinfo->fd, COL_INFO,
				 "%s%s%s%s: %s vendor=%s (hop-id=%d) (end-id=%d) RPE=%d%d%d",
				 (BadPacket)?"***** Bad Packet!: ":"",
				 (flags & DIAM_FLAGS_P)?"Proxyable ":"",
				 (flags & DIAM_FLAGS_R)?"Request":"Answer",
				 (flags & DIAM_FLAGS_E)?" Error":"",
				 commandString, vendorString,
				 dh.hopByHopId, dh.endToEndId,
				 (flags & DIAM_FLAGS_R)?1:0,
				 (flags & DIAM_FLAGS_P)?1:0,
				 (flags & DIAM_FLAGS_E)?1:0);
  }
	

  /* In the interest of speed, if "tree" is NULL, don't do any work not
	 necessary to generate protocol tree items. */
  if (tree) {

	/* create display subtree for the protocol */
	ti = proto_tree_add_item(tree, proto_diameter, tvb, offset, tvb_length(tvb), FALSE);
	diameter_tree = proto_item_add_subtree(ti, ett_diameter);

	/* Version */
	proto_tree_add_uint(diameter_tree,
						hf_diameter_version,
						tvb, offset, 1,
						version);

	offset+=1;

	/* Length */
	proto_tree_add_uint(diameter_tree,
						hf_diameter_length, tvb,
						offset, 3, pktLength);
	offset += 3;

	/* Flags */
	tf = proto_tree_add_uint_format(diameter_tree, hf_diameter_flags, tvb,
									offset , 1, flags, "Flags: 0x%02x (%s)", flags,
									flagstr);
	flags_tree = proto_item_add_subtree(tf, ett_diameter_avp_flags);
	proto_tree_add_boolean(flags_tree, hf_diameter_flags_request, tvb, offset, 1, flags);
	proto_tree_add_boolean(flags_tree, hf_diameter_flags_proxyable, tvb, offset, 1, flags);
	proto_tree_add_boolean(flags_tree, hf_diameter_flags_error, tvb, offset, 1, flags);
	proto_tree_add_boolean(flags_tree, hf_diameter_flags_reserved3, tvb, offset, 1, flags);
	proto_tree_add_boolean(flags_tree, hf_diameter_flags_reserved4, tvb, offset, 1, flags);
	proto_tree_add_boolean(flags_tree, hf_diameter_flags_reserved5, tvb, offset, 1, flags);
	proto_tree_add_boolean(flags_tree, hf_diameter_flags_reserved6, tvb, offset, 1, flags);
	proto_tree_add_boolean(flags_tree, hf_diameter_flags_reserved7, tvb, offset, 1, flags);

	offset += 1;

	/* Command Code */
	proto_tree_add_uint_format(diameter_tree, hf_diameter_code,
						tvb, offset, 3, commandCode, "Command Code: %s", commandString);
	offset += 3;

	/* Vendor Id */
	proto_tree_add_uint_format(diameter_tree,hf_diameter_vendor_id,
						tvb, offset, 4,	dh.vendorId, "Vendor-Id: %s", vendorString);
	offset += 4;

	/* Hop-by-hop Identifier */
	proto_tree_add_uint(diameter_tree, hf_diameter_hopbyhopid,
						tvb, offset, 4, dh.hopByHopId);
	offset += 4;

	/* End-to-end Identifier */
	proto_tree_add_uint(diameter_tree, hf_diameter_endtoendid,
						tvb, offset, 4, dh.endToEndId);
	offset += 4;

	/* If we have a bad packet, don't bother trying to parse the AVPs */
	if (BadPacket) {
	  return;
	}

	/* Start looking at the AVPS */
	/* Make the next tvbuff */

	/* Update the lengths */
	avplength= pktLength - sizeof(e_diameterhdr);
    
	avp_tvb = tvb_new_subset(tvb, offset, -1, avplength);
	avptf = proto_tree_add_text(diameter_tree,
								tvb, offset, tvb_length(tvb),
								"Attribute Value Pairs");
		
	avp_tree = proto_item_add_subtree(avptf,
									  ett_diameter_avp);
	if (avp_tree != NULL) {
	  dissect_avps( avp_tvb, pinfo, avp_tree);
	}
  }
} /* dissect_diameter */

/*
 * This function will dissect the AVPs in a diameter packet.  It handles
 * all normal types, and even recursively calls itself for grouped AVPs
 */
static void dissect_avps(tvbuff_t *tvb, packet_info *pinfo, proto_tree *avp_tree)
{
  /* adds the attribute value pairs to the tree */
  e_avphdr avph;
  gchar avpTypeString[64];
  gchar avpNameString[64];
  gchar *valstr;
  guint32 vendorId=0;
  gchar    vendorString[64];
  int hdrLength;
  int fixAmt;
  proto_tree *avpi_tree;
  size_t offset = 0 ;
  char dataBuffer[4096];
  tvbuff_t        *group_tvb;
  proto_tree *group_tree;
  proto_item *grouptf;
  proto_item *avptf;
  char buffer[1024];
  int BadPacket = FALSE;
  guint32 avpLength;
  guint8 flags;
  proto_item      *tf;
  proto_tree      *flags_tree;
	
  gint32 packetLength;
  size_t avpDataLength;
  int avpType;
  gchar flagstr[64] = "<None>";
  gchar *fstr[] = {"RSVD7", "RSVD6", "RSVD5", "RSVD4", "RSVD3", "Protected", "Mandatory", "Vendor-Specific" };
  gint        i;
  guint      bpos;

  packetLength = tvb_length(tvb);

  /* Check for invalid packet lengths */
  if (packetLength <= 0) {
	proto_tree_add_text(avp_tree, tvb, offset, tvb_length(tvb),
						"No Attribute Value Pairs Found");
	return;
  }

  /* Spin around until we run out of packet */
  while (packetLength > 0 ) {

	/* Check for short packet */
	if (packetLength < (long)MIN_AVP_SIZE) {
	  g_warning("DIAMETER: AVP Payload too short: %d bytes less than min size (%d bytes))",
				packetLength, MIN_AVP_SIZE);
	  BadPacket = TRUE;
	  /* Don't even bother trying to parse a short packet. */
	  return;
	}
	
	/* Copy our header */
	tvb_memcpy(tvb, (guint8*) &avph, offset, MIN((long)sizeof(avph),packetLength));
	
	/* Fix the byte ordering */
	avph.avp_code = ntohl(avph.avp_code);
	avph.avp_flagsLength = ntohl(avph.avp_flagsLength);
	
	flags = (avph.avp_flagsLength & 0xff000000) >> 24;
	avpLength = avph.avp_flagsLength & 0x00ffffff;
	
	/* Set up our flags string */
	if (check_col(pinfo->fd, COL_INFO) || avp_tree) {  
	  flagstr[0]=0;
	  for (i = 0; i < 8; i++) {
		bpos = 1 << i;
		if (flags & bpos) {
		  if (flagstr[0]) {
			strcat(flagstr, ", ");
		  }
		  strcat(flagstr, fstr[i]);
		}
	  }
	  if (strlen(flagstr) == 0) {
		strcpy(flagstr,"<None>");
	  }
	}

	/* Dissect our vendor id if it exists  and set hdr length */
	if (flags & AVP_FLAGS_V) {
	  vendorId = ntohl(avph.avp_vendorId);
	  /* Vendor id */
	  hdrLength = sizeof(e_avphdr);
	} else {
	  /* No vendor */
	  hdrLength = sizeof(e_avphdr) - 
		sizeof(guint32);
	  vendorId = 0;
	}

	if (vendorId) {
	  strcpy(vendorString, 
			 val_to_str(vendorId, diameter_vendor_specific_vendors, "Unknown Vendor: %08x"));
	} else {
	  vendorString[0]='\0';
	}

	/* Check for bad length */
	if (avpLength < MIN_AVP_SIZE || 
		((long)avpLength > packetLength)) {
	  g_warning("DIAMETER: AVP payload size invalid: avp_length: %d bytes,  "
				"min: %d bytes,    packetLen: %d",
				avpLength, MIN_AVP_SIZE, packetLength);
	  BadPacket = TRUE;
	}

	/* Check for bad flags */
	if (flags & AVP_FLAGS_RESERVED) {
	  g_warning("DIAMETER: Invalid AVP: Reserved bit set.  flags = 0x%x,"
				" resFl=0x%x",
				flags, AVP_FLAGS_RESERVED);
	  /* For now, don't set bad packet, since I'm accidentally setting a wrong bit */
	  // BadPacket = TRUE;
	}
		
	/*
	 * Compute amount of byte-alignment fix (Diameter AVPs are sent on 4 byte
	 * boundries)
	 */
	fixAmt = 4 - (avpLength % 4);
	if (fixAmt == 4) fixAmt = 0;
	
	/* shrink our packetLength */
	packetLength = packetLength - (avpLength + fixAmt);
	
	/* Check for out of bounds */
	if (packetLength < 0) {
	  g_warning("DIAMETER: Bad AVP: Bad new length (%d bytes) ",
				packetLength);
	  BadPacket = TRUE;
	}

	/* Make avp Name & type */
	strcpy(avpTypeString, val_to_str(diameter_avp_get_type(avph.avp_code), diameter_avp_type_vals, 
								   "Unknown-Type: 0x%08x"));
	strcpy(avpNameString, diameter_avp_get_name(avph.avp_code));

	avptf = proto_tree_add_text(avp_tree, tvb,
								offset, avpLength + fixAmt,
								"%s (%s) l:0x%x (%d bytes) (%d padded bytes)",
								avpNameString, avpTypeString, avpLength,
								avpLength, avpLength+fixAmt);
	avpi_tree = proto_item_add_subtree(avptf,
									   ett_diameter_avpinfo);

	if (avpi_tree !=NULL) {
	  /* Command Code */
	  proto_tree_add_uint_format(avpi_tree, hf_diameter_avp_code,
						  tvb, offset, 4, avph.avp_code, "AVP Code: %s", avpNameString);
	  offset += 4;
		
	  tf = proto_tree_add_uint_format(avpi_tree, hf_diameter_avp_flags, tvb,
									  offset , 1, flags, "Flags: 0x%02x (%s)", flags,
									  flagstr);
	  flags_tree = proto_item_add_subtree(tf, ett_diameter_avp_flags);
	  proto_tree_add_boolean(flags_tree, hf_diameter_avp_flags_vendor_specific, tvb, offset, 1, flags);
	  proto_tree_add_boolean(flags_tree, hf_diameter_avp_flags_mandatory, tvb, offset, 1, flags);
	  proto_tree_add_boolean(flags_tree, hf_diameter_avp_flags_protected, tvb, offset, 1, flags);
	  proto_tree_add_boolean(flags_tree, hf_diameter_avp_flags_reserved3,  tvb, offset, 1, flags);
	  proto_tree_add_boolean(flags_tree, hf_diameter_avp_flags_reserved4,  tvb, offset, 1, flags);
	  proto_tree_add_boolean(flags_tree, hf_diameter_avp_flags_reserved5,  tvb, offset, 1, flags);
	  proto_tree_add_boolean(flags_tree, hf_diameter_avp_flags_reserved6,  tvb, offset, 1, flags);
	  proto_tree_add_boolean(flags_tree, hf_diameter_avp_flags_reserved7,  tvb, offset, 1, flags);
	  offset += 1;

	  proto_tree_add_uint(avpi_tree, hf_diameter_avp_length,
						  tvb, offset, 3, avpLength);
	  offset += 3;

	  if (flags & AVP_FLAGS_V) {
		proto_tree_add_uint_format(avpi_tree, hf_diameter_avp_vendor_id,
								   tvb, offset, 4, vendorId, vendorString);
		offset += 4;
	  }

	  avpDataLength = avpLength - hdrLength;

	  /*
	   * If we've got a bad packet, just highlight the data.  Don't try
	   * to parse it, and, don't move to next AVP.
	   */
	  if (BadPacket) {
		offset -= hdrLength;
		proto_tree_add_bytes_format(avpi_tree, hf_diameter_avp_data_bytes,
									tvb, offset, tvb_length(tvb) - offset, dataBuffer,
									"Bad AVP (Suspect Data Not Dissected)");
		return;
	  }

	  avpType=diameter_avp_get_type(avph.avp_code);
	  tvb_memcpy(tvb, (guint8*) dataBuffer, offset, MIN(4095,avpDataLength));
	
	  
	  switch(avpType) {
	  case DIAMETER_GROUPED:
		sprintf(buffer, "%s Grouped AVPs", avpNameString);
		/* Recursively call ourselves */
		grouptf = proto_tree_add_text(avpi_tree,
									  tvb, offset, tvb_length(tvb),
									  buffer);
				
		group_tree = proto_item_add_subtree(grouptf,
											ett_diameter_avp);

		group_tvb = tvb_new_subset(tvb, offset,
								   MIN(avpDataLength, tvb_length(tvb)-offset), avpDataLength);
		if (group_tree != NULL) {
		  dissect_avps( group_tvb, pinfo, group_tree);
		}
		break;

	  case DIAMETER_IDENTITY:
		proto_tree_add_string_format(avpi_tree, hf_diameter_avp_data_string,
									 tvb, offset, avpDataLength, dataBuffer,
									 "Identity: %*.*s", (int)avpDataLength, (int)avpDataLength,
									 dataBuffer);
		break;
	  case DIAMETER_UTF8STRING:
		proto_tree_add_string_format(avpi_tree, hf_diameter_avp_data_string,
									 tvb, offset, avpDataLength, dataBuffer,
									 "UTF8String: %*.*s", (int)avpDataLength, (int)avpDataLength,
									 dataBuffer);
		break;
	  case DIAMETER_IP_ADDRESS:
		if (avpDataLength == 4) {
		  guint32 ipv4Address = ntohl((*(guint32*)dataBuffer));
		  proto_tree_add_ipv4_format(avpi_tree, hf_diameter_avp_data_v4addr,
									 tvb, offset, avpDataLength, ipv4Address,
									 "IPv4 Address: %u.%u.%u.%u",
									 (ipv4Address&0xff000000)>>24,
									 (ipv4Address&0xff0000)>>16,
									 (ipv4Address&0xff00)>>8,
									 (ipv4Address&0xff));
		} else if (avpDataLength == 16) {
		  proto_tree_add_ipv6_format(avpi_tree, hf_diameter_avp_data_v6addr,
									 tvb, offset, avpDataLength, dataBuffer, 
									 "IPv6 Address: %04x:%04x:%04x:%04x",
									 *((guint32*)dataBuffer),
									 *((guint32*)&dataBuffer[4]),
									 *((guint32*)&dataBuffer[8]),
									 *((guint32*)&dataBuffer[12]));
		} else {
		  proto_tree_add_bytes_format(avpi_tree, hf_diameter_avp_data_bytes,
									  tvb, offset, avpDataLength, dataBuffer,
									  "Error!  Bad Address Length");
		}
		break;

	  case DIAMETER_INTEGER32:
		{
		  gint32 data;
		  memcpy(&data, dataBuffer, 4);
		  data = ntohl(data);
		  proto_tree_add_int_format(avpi_tree, hf_diameter_avp_data_int32,
									tvb, offset, avpDataLength, data,
									"Value: %d", data );
		}
		break;

	  case DIAMETER_UNSIGNED32:
		{
		  guint32 data;

		  memcpy(&data, dataBuffer, 4);
		  data=ntohl(data);
		  proto_tree_add_uint_format(avpi_tree, hf_diameter_avp_data_uint32,
									 tvb, offset, avpDataLength, data,
									 "Value: 0x%08x (%u)", data,
									 data );
		}
		break;

	  case DIAMETER_INTEGER64:
		{
		  gint64 data;
		  memcpy(&data, dataBuffer, 8);
		  /* data = ntohll(data); */
		  proto_tree_add_int_format(avpi_tree, hf_diameter_avp_data_int64,
									tvb, offset, avpDataLength, data,
									"Value: 0x%016llx (%lld)", data, data );
		}
		break;
	  case DIAMETER_UNSIGNED64:
		{
		  guint64 data;
		  memcpy(&data, dataBuffer, 8);
		  /* data = ntohll(data); */
		  proto_tree_add_int_format(avpi_tree, hf_diameter_avp_data_uint64,
									tvb, offset, avpDataLength, data,
									"Value: 0x%016llx (%llu)", data, data );
		}
		break;

	  case DIAMETER_TIME:
		valstr=diameter_time_to_string(dataBuffer);

		proto_tree_add_bytes_format(avpi_tree, hf_diameter_avp_data_bytes,
									tvb, offset, avpDataLength, dataBuffer, "Time: %s", valstr);
		break;

	  case DIAMETER_ENUMERATED:
		{
		  guint32 data;
		  
		  memcpy(&data, dataBuffer, 4);
		  data = ntohl(data);
		  valstr = diameter_avp_get_value(avph.avp_code, data);
		  proto_tree_add_uint_format(avpi_tree, hf_diameter_avp_data_uint32,
									 tvb, offset, avpDataLength, data,
									 "Value: 0x%08x (%u): %s", data, data, valstr);
		}
		break;
	  default:
	  case DIAMETER_OCTET_STRING:
		proto_tree_add_bytes_format(avpi_tree, hf_diameter_avp_data_bytes,
									tvb, offset, avpDataLength, dataBuffer,
									"Hex Data Highlighted Below");
		break;
				
	  } /* switch type */
	} /* avpi_tree != null */
	offset += (avpLength - hdrLength);
	offset += fixAmt; /* fix byte alignment */
  } /* loop */
} /* dissect_avps */



void
proto_reg_handoff_diameter(void)
{
	static int Initialized=FALSE;
	static int TcpPort=0;
	static int SctpPort=0;

	if (Initialized) {
		dissector_delete("tcp.port", TcpPort, dissect_diameter);
		dissector_delete("sctp.port", SctpPort, dissect_diameter);
	} else {
		Initialized=TRUE;
	}

	/* set port for future deletes */
	TcpPort=gbl_diameterTcpPort;
	SctpPort=gbl_diameterSctpPort;

	strcpy(gbl_diameterString, "Diameter Protocol");

        /* g_warning ("Diameter: Adding tcp dissector to port %d",
		gbl_diameterTcpPort); */
	dissector_add("tcp.port", gbl_diameterTcpPort, dissect_diameter,
	    proto_diameter);
	dissector_add("sctp.port", gbl_diameterSctpPort,
	    dissect_diameter, proto_diameter);
}

/* registration with the filtering engine */
void
proto_register_diameter(void)
{

	static hf_register_info hf[] = {
		{ &hf_diameter_version,
		  { "Version", "diameter.version", FT_UINT8, BASE_HEX, NULL, 0x00,
		    "", HFILL }},
		{ &hf_diameter_length,
		  { "Length","diameter.length", FT_UINT24, BASE_DEC, NULL, 0x0,
		    "", HFILL }},

		{ &hf_diameter_flags,
		  { "Flags", "diameter.flags", FT_UINT8, BASE_HEX, NULL, 0x0,
		    "", HFILL }},
		{ &hf_diameter_flags_request,
		{ "Request", "diameter.flags.request", FT_BOOLEAN, 8, TFS(&flags_set_truth), DIAM_FLAGS_R,
			"", HFILL }},
		{ &hf_diameter_flags_proxyable,
		{ "Proxyable", "diameter.flags.proxyable", FT_BOOLEAN, 8, TFS(&flags_set_truth), DIAM_FLAGS_P,
			"", HFILL }},
		{ &hf_diameter_flags_error,
		{ "Error","diameter.flags.error", FT_BOOLEAN, 8, TFS(&flags_set_truth), DIAM_FLAGS_E,
			"", HFILL }},
		{ &hf_diameter_flags_reserved3,
		{ "Reserved","diameter.flags.reserved3", FT_BOOLEAN, 8, TFS(&reserved_set),
		  DIAM_FLAGS_RESERVED3, "", HFILL }},
		{ &hf_diameter_flags_reserved4,
		{ "Reserved","diameter.flags.reserved4", FT_BOOLEAN, 8, TFS(&reserved_set),
		  DIAM_FLAGS_RESERVED4, "", HFILL }},
		{ &hf_diameter_flags_reserved5,
		{ "Reserved","diameter.flags.reserved5", FT_BOOLEAN, 8, TFS(&reserved_set),
		  DIAM_FLAGS_RESERVED5, "", HFILL }},
		{ &hf_diameter_flags_reserved6,
		{ "Reserved","diameter.flags.reserved6", FT_BOOLEAN, 8, TFS(&reserved_set),
		  DIAM_FLAGS_RESERVED6, "", HFILL }},
		{ &hf_diameter_flags_reserved7,
		{ "Reserved","diameter.flags.reserved7", FT_BOOLEAN, 8, TFS(&reserved_set),
		  DIAM_FLAGS_RESERVED7, "", HFILL }},

		{ &hf_diameter_code,
		  { "Command Code","diameter.code", FT_UINT24, BASE_DEC,
		    NULL, 0x0, "", HFILL }},
		{ &hf_diameter_vendor_id,
		  { "VendorId",	"diameter.vendorId", FT_UINT32, BASE_DEC, NULL,
			0x0,"", HFILL }},
		{ &hf_diameter_hopbyhopid,
		  { "Hop-by-Hop Identifier", "diameter.hopbyhopid", FT_UINT32,
		    BASE_HEX, NULL, 0x0, "", HFILL }},
		{ &hf_diameter_endtoendid,
		  { "End-to-End Identifier", "diameter.endtoendid", FT_UINT32, 
		    BASE_HEX, NULL, 0x0, "", HFILL }},

		{ &hf_diameter_avp_code,
		  { "AVP Code","diameter.avp.code", FT_UINT32, BASE_DEC,
		    NULL, 0x0, "", HFILL }},
		{ &hf_diameter_avp_length,
		  { "AVP Length","diameter.avp.length", FT_UINT24, BASE_DEC,
		    NULL, 0x0, "", HFILL }},


		{ &hf_diameter_avp_flags,
		  { "AVP Flags","diameter.avp.flags", FT_UINT8, BASE_HEX,
		    NULL, 0x0, "", HFILL }},
		{ &hf_diameter_avp_flags_vendor_specific,
		{ "Vendor-Specific", "diameter.flags.vendorspecific", FT_BOOLEAN, 8, TFS(&flags_set_truth), AVP_FLAGS_V,
			"", HFILL }},
		{ &hf_diameter_avp_flags_mandatory,
		{ "Mandatory", "diameter.flags.mandatory", FT_BOOLEAN, 8, TFS(&flags_set_truth), AVP_FLAGS_M,
			"", HFILL }},
		{ &hf_diameter_avp_flags_protected,
		{ "Protected","diameter.avp.flags.protected", FT_BOOLEAN, 8, TFS(&flags_set_truth), AVP_FLAGS_P,
			"", HFILL }},
		{ &hf_diameter_avp_flags_reserved3,
		{ "Reserved","diameter.avp.flags.reserved3", FT_BOOLEAN, 8, TFS(&reserved_set),
		  AVP_FLAGS_RESERVED3,	"", HFILL }},
		{ &hf_diameter_avp_flags_reserved4,
		{ "Reserved","diameter.avp.flags.reserved4", FT_BOOLEAN, 8, TFS(&reserved_set),
		  AVP_FLAGS_RESERVED4,	"", HFILL }},
		{ &hf_diameter_avp_flags_reserved5,
		{ "Reserved","diameter.avp.flags.reserved5", FT_BOOLEAN, 8, TFS(&reserved_set),
		  AVP_FLAGS_RESERVED5,	"", HFILL }},
		{ &hf_diameter_avp_flags_reserved6,
		{ "Reserved","diameter.avp.flags.reserved6", FT_BOOLEAN, 8, TFS(&reserved_set),
		  AVP_FLAGS_RESERVED6,	"", HFILL }},
		{ &hf_diameter_avp_flags_reserved7,
		{ "Reserved","diameter.avp.flags.reserved7", FT_BOOLEAN, 8, TFS(&reserved_set),
		  AVP_FLAGS_RESERVED7,	"", HFILL }},
		{ &hf_diameter_avp_vendor_id,
		  { "AVP Vendor Id","diameter.avp.vendorId", FT_UINT32, BASE_DEC,
		    NULL, 0x0, "", HFILL }},
		{ &hf_diameter_avp_data_uint64,
		  { "AVP Data","diameter.avp.data.uint64", FT_UINT32, BASE_DEC,
		    NULL, 0x0, "", HFILL }},
		{ &hf_diameter_avp_data_int64,
		  { "AVP Data","diameter.avp.data.int64", FT_INT32, BASE_DEC,
		    NULL, 0x0, "", HFILL }},
		{ &hf_diameter_avp_data_uint32,
		  { "AVP Data","diameter.avp.data.uint32", FT_UINT32, BASE_DEC,
		    NULL, 0x0, "", HFILL }},
		{ &hf_diameter_avp_data_int32,
		  { "AVP Data","diameter.avp.data.int32", FT_INT32, BASE_DEC,
		    NULL, 0x0, "", HFILL }},
		{ &hf_diameter_avp_data_bytes,
		  { "AVP Data","diameter.avp.data.bytes", FT_BYTES, BASE_NONE,
		    NULL, 0x0, "", HFILL }},

		{ &hf_diameter_avp_data_string,
		  { "AVP Data","diameter.avp.data.string", FT_STRING, BASE_NONE,
		    NULL, 0x0, "", HFILL }},
		{ &hf_diameter_avp_data_v4addr,
		  { "AVP Data","diameter.avp.data.v4addr", FT_IPv4, BASE_NONE,
		    NULL, 0x0, "", HFILL }},
		{ &hf_diameter_avp_data_v6addr,
		  { "AVP Data","diameter.avp.data.v6addr", FT_IPv6, BASE_NONE,
		    NULL, 0x0, "", HFILL }},
		{ &hf_diameter_avp_data_time,
		  { "AVP Data","diameter.avp.data.time", FT_ABSOLUTE_TIME, BASE_NONE,
		    NULL, 0x0, "", HFILL }},

	};
	static gint *ett[] = {
		&ett_diameter,
		&ett_diameter_flags,
		&ett_diameter_avp,
		&ett_diameter_avp_flags,
		&ett_diameter_avpinfo
	};
	module_t *diameter_module;

	proto_diameter = proto_register_protocol (gbl_diameterString,
	    "DIAMETER", "diameter");
	proto_register_field_array(proto_diameter, hf, array_length(hf));
	proto_register_subtree_array(ett, array_length(ett));

	/* Register a configuration option for port */
	diameter_module = prefs_register_protocol(proto_diameter,
	    proto_reg_handoff_diameter);
	prefs_register_uint_preference(diameter_module, "tcp.port",
				       "DIAMETER TCP Port",
				       "Set the TCP port for DIAMETER messages",
				       10,
				       &gbl_diameterTcpPort);
	prefs_register_uint_preference(diameter_module, "sctp.port",
				       "DIAMETER SCTP Port",
				       "Set the SCTP port for DIAMETER messages",
				       10,
				       &gbl_diameterSctpPort);
}
