/* packet-acn.c
 * Routines for ACN packet disassembly
 *
 * $Id: packet-acn.c 35168 2010-12-10 12:13:53Z etxrab $
 *
 * Copyright (c) 2003 by Erwin Rol <erwin@erwinrol.com>
 * Copyright (c) 2006 by Electronic Theatre Controls, Inc.
 *                    Bill Florac <bflorac@etcconnect.com>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1999 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.
 */

 /*
    Todo:
      Add reading of DDL files so we can futher explode DMP packets
      For some of the Set/Get properties where we have a range of data
      it would be better to show the block of data rather and
      address-data pair on each line...

      Build CID to "Name" table from file so we can display real names
      rather than CIDs
 */

/* Include files */

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

#include <gmodule.h>

#include <epan/packet.h>
#include <epan/prefs.h>
#include <epan/packet.h>
#include <epan/ipv6-utils.h>
#include <string.h>

#include "packet-acn.h"

/*
 * See
 * ANSI BSR E1.17 Architecture for Control Networks
 * ANSI BSR E1.31
 */

#define ACTUAL_ADDRESS  0
/* forward reference */
static gboolean dissect_acn_heur( tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree );
static guint32 acn_add_channel_owner_info_block(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, int offset);
static guint32 acn_add_channel_member_info_block(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, int offset);
static guint32 acn_add_expiry(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, int offset, const char *label);
static guint32 acn_add_channel_parameter(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, int offset);
static guint32 acn_add_address(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, int offset, const char *label);
static guint32 acn_add_dmp_address_type(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, int offset, acn_dmp_adt_type *adt);
static guint32 acn_add_dmp_address(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, int offset, acn_dmp_adt_type *adt);
static guint32 dissect_acn_dmp_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, acn_pdu_offsets *last_pdu_offsets);
static guint32 dissect_acn_sdt_wrapped_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, int offset, acn_pdu_offsets *last_pdu_offsets);
static guint32 dissect_acn_sdt_client_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, acn_pdu_offsets *last_pdu_offsets);
static guint32 dissect_acn_dmx_data_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, acn_pdu_offsets *last_pdu_offsets);
static guint32 dissect_acn_dmx_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, acn_pdu_offsets *last_pdu_offsets);
static guint32 dissect_acn_sdt_base_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, acn_pdu_offsets *last_pdu_offsets);
static guint32 dissect_acn_root_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, acn_pdu_offsets *last_pdu_offsets);
static int dissect_acn(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree);
static gboolean is_acn(tvbuff_t *tvb);

/* Global variables */
static int proto_acn = -1;
static gint ett_acn = -1;
static gint ett_acn_channel_owner_info_block = -1;
static gint ett_acn_channel_member_info_block = -1;
static gint ett_acn_channel_parameter = -1;
static gint ett_acn_address = -1;
static gint ett_acn_address_type = -1;
static gint ett_acn_pdu_flags = -1;
static gint ett_acn_dmp_pdu = -1;
static gint ett_acn_sdt_pdu = -1;
static gint ett_acn_sdt_client_pdu = -1;
static gint ett_acn_sdt_base_pdu = -1;
static gint ett_acn_root_pdu = -1;
static gint ett_acn_dmx_address = -1;
static gint ett_acn_dmx_data_pdu = -1;
static gint ett_acn_dmx_pdu = -1;

/*  Register fields */
/* In alphabetical order */
static int hf_acn_association = -1;
static int hf_acn_channel_number = -1;
static int hf_acn_cid = -1;
static int hf_acn_client_protocol_id = -1;
static int hf_acn_data = -1;
static int hf_acn_data8 = -1;
static int hf_acn_data16 = -1;
static int hf_acn_data24 = -1;
static int hf_acn_data32 = -1;
static int hf_acn_dmp_address1 = -1;
static int hf_acn_dmp_address2 = -1;
static int hf_acn_dmp_address4 = -1;
static int hf_acn_dmp_adt = -1; /* address and data type*/
static int hf_acn_dmp_adt_a = -1;
static int hf_acn_dmp_adt_v = -1;
static int hf_acn_dmp_adt_r = -1;
static int hf_acn_dmp_adt_d = -1;
static int hf_acn_dmp_adt_x = -1;
static int hf_acn_dmp_reason_code = -1;
static int hf_acn_dmp_vector = -1;
static int hf_acn_dmp_address_data_pairs = -1;
static int hf_acn_expiry = -1;
static int hf_acn_first_memeber_to_ack = -1;
static int hf_acn_first_missed_sequence = -1;
static int hf_acn_ip_address_type = -1;
static int hf_acn_ipv4 = -1;
static int hf_acn_ipv6 = -1;
static int hf_acn_last_memeber_to_ack = -1;
static int hf_acn_last_missed_sequence = -1;
static int hf_acn_mak_threshold = -1;
static int hf_acn_member_id = -1;
static int hf_acn_nak_holdoff = -1;
static int hf_acn_nak_max_wait = -1;
static int hf_acn_nak_modulus = -1;
static int hf_acn_nak_outbound_flag = -1;
static int hf_acn_oldest_available_wrapper = -1;
static int hf_acn_packet_identifier = -1;
static int hf_acn_pdu = -1;
static int hf_acn_pdu_flag_d = -1;
static int hf_acn_pdu_flag_h = -1;
static int hf_acn_pdu_flag_l = -1;
static int hf_acn_pdu_flag_v = -1;
static int hf_acn_pdu_flags = -1;
static int hf_acn_pdu_length = -1;
static int hf_acn_port = -1;
static int hf_acn_postamble_size = -1;
static int hf_acn_preamble_size = -1;
static int hf_acn_protocol_id = -1;
static int hf_acn_reason_code = -1;
static int hf_acn_reciprocal_channel = -1;
static int hf_acn_refuse_code = -1;
static int hf_acn_reliable_sequence_number = -1;
/* static int hf_acn_sdt_pdu = -1; */
static int hf_acn_sdt_vector = -1;
static int hf_acn_dmx_vector = -1;
static int hf_acn_session_count = -1;
static int hf_acn_total_sequence_number = -1;
static int hf_acn_dmx_source_name = -1;
static int hf_acn_dmx_priority = -1;
static int hf_acn_dmx_sequence_number = -1;
static int hf_acn_dmx_universe = -1;

static int hf_acn_dmx_start_code = -1;
static int hf_acn_dmx_increment = -1;
static int hf_acn_dmx_count = -1;

/* static int hf_acn_dmx_dmp_vector = -1; */

/* Try heuristic ACN decode */
static gboolean global_acn_heur = FALSE;
static gboolean global_acn_dmx_enable = FALSE;
static gint     global_acn_dmx_display_view = 0;
static gint     global_acn_dmx_display_line_format = 0;
static gboolean global_acn_dmx_display_zeros = FALSE;
static gboolean global_acn_dmx_display_leading_zeros = FALSE;


static const value_string acn_protocol_id_vals[] = {
  { ACN_PROTOCOL_ID_SDT, "SDT Protocol" },
  { ACN_PROTOCOL_ID_DMP, "DMP Protocol" },
  { ACN_PROTOCOL_ID_DMX, "DMX Protocol" },
  { 0,       NULL },
};

static const value_string acn_dmp_adt_r_vals[] = {
  { 0, "Relative" },
  { 1, "Absolute" },
  { 0,       NULL },
};

static const value_string acn_dmp_adt_v_vals[] = {
  { 0, "Actual" },
  { 1, "Virtual" },
  { 0,       NULL },
};

static const value_string acn_dmp_adt_d_vals[] = {
  { ACN_DMP_ADT_D_NS, "Non-range, single data item" },
  { ACN_DMP_ADT_D_RS, "Range, single data item" },
  { ACN_DMP_ADT_D_RE, "Range, array of equal size data items" },
  { ACN_DMP_ADT_D_RM, "Range, series of mixed size data items" },
  { 0,       NULL },
};

static const value_string acn_dmp_adt_a_vals[] = {
  { ACN_DMP_ADT_A_1, "1 octet" },
  { ACN_DMP_ADT_A_2, "2 octets" },
  { ACN_DMP_ADT_A_4, "4 octets" },
  { ACN_DMP_ADT_A_R, "reserved" },
  { 0,       NULL },
};


static const value_string acn_sdt_vector_vals[] = {
  {ACN_SDT_VECTOR_UNKNOWN, "Unknown"},
  {ACN_SDT_VECTOR_REL_WRAP, "Reliable Wrapper"},
  {ACN_SDT_VECTOR_UNREL_WRAP, "Unreliable Wrapper"},
  {ACN_SDT_VECTOR_CHANNEL_PARAMS, "Channel Parameters"},
  {ACN_SDT_VECTOR_JOIN, "Join"},
  {ACN_SDT_VECTOR_JOIN_REFUSE, "Join Refuse"},
  {ACN_SDT_VECTOR_JOIN_ACCEPT, "Join Accept"},
  {ACN_SDT_VECTOR_LEAVE, "Leave"},
  {ACN_SDT_VECTOR_LEAVING, "Leaving"},
  {ACN_SDT_VECTOR_CONNECT, "Connect"},
  {ACN_SDT_VECTOR_CONNECT_ACCEPT, "Connect Accept"},
  {ACN_SDT_VECTOR_CONNECT_REFUSE, "Connect Refuse"},
  {ACN_SDT_VECTOR_DISCONNECT, "Disconnect"},
  {ACN_SDT_VECTOR_DISCONNECTING, "Disconnecting"},
  {ACN_SDT_VECTOR_ACK, "Ack"},
  {ACN_SDT_VECTOR_NAK, "Nak"},
  {ACN_SDT_VECTOR_GET_SESSION, "Get Session"},
  {ACN_SDT_VECTOR_SESSIONS, "Sessions"},
  { 0,       NULL },
};

static const value_string acn_dmx_vector_vals[] = {
  {ACN_DMX_VECTOR,  "Streaming DMX"},
  { 0,       NULL },
};

static const value_string acn_dmp_vector_vals[] = {
  {ACN_DMP_VECTOR_UNKNOWN, "Unknown"},
  {ACN_DMP_VECTOR_GET_PROPERTY, "Get Property"},
  {ACN_DMP_VECTOR_SET_PROPERTY, "Set Property"},
  {ACN_DMP_VECTOR_GET_PROPERTY_REPLY, "Get property reply"},
  {ACN_DMP_VECTOR_EVENT, "Event"},
  {ACN_DMP_VECTOR_MAP_PROPERTY, "Map Property"},
  {ACN_DMP_VECTOR_UNMAP_PROPERTY, "Unmap Property"},
  {ACN_DMP_VECTOR_SUBSCRIBE, "Subscribe"},
  {ACN_DMP_VECTOR_UNSUBSCRIBE, "Unsubscribe"},
  {ACN_DMP_VECTOR_GET_PROPERTY_FAIL, "Get Property Fail"},
  {ACN_DMP_VECTOR_SET_PROPERTY_FAIL, "Set Property Fail"},
  {ACN_DMP_VECTOR_MAP_PROPERTY_FAIL, "Map Property Fail"},
  {ACN_DMP_VECTOR_SUBSCRIBE_ACCEPT, "Subscribe Accept"},
  {ACN_DMP_VECTOR_SUBSCRIBE_REJECT, "Subscribe Reject"},
  {ACN_DMP_VECTOR_ALLOCATE_MAP, "Allocate Map"},
  {ACN_DMP_VECTOR_ALLOCATE_MAP_REPLY, "Allocate Map Reply"},
  {ACN_DMP_VECTOR_DEALLOCATE_MAP, "Deallocate Map"},
  { 0,       NULL },
};

static const value_string acn_ip_address_type_vals[] = {
  { ACN_ADDR_NULL, "Null"},
  { ACN_ADDR_IPV4, "IPv4"},
  { ACN_ADDR_IPV6, "IPv6"},
  { ACN_ADDR_IPPORT, "Port"},
  { 0,       NULL },
};

static const value_string acn_refuse_code_vals[] = {
  { ACN_REFUSE_CODE_NONSPECIFIC, "Nonspecific" },
  { ACN_REFUSE_CODE_ILLEGAL_PARAMS, "Illegal Parameters" },
  { ACN_REFUSE_CODE_LOW_RESOURCES, "Low Resources" },
  { ACN_REFUSE_CODE_ALREADY_MEMBER, "Already Member" },
  { ACN_REFUSE_CODE_BAD_ADDR_TYPE, "Bad Address Type" },
  { ACN_REFUSE_CODE_NO_RECIP_CHAN, "No Reciprocal Channel" },
  { 0,       NULL },
};

static const value_string acn_reason_code_vals[] = {
  { ACN_REASON_CODE_NONSPECIFIC, "Nonspecific" },
  { ACN_REASON_CODE_NO_RECIP_CHAN, "No Reciprocal Channel" },
  { ACN_REASON_CODE_CHANNEL_EXPIRED, "Channel Expired" },
  { ACN_REASON_CODE_LOST_SEQUENCE, "Lost Sequence" },
  { ACN_REASON_CODE_SATURATED, "Saturated" },
  { ACN_REASON_CODE_TRANS_ADDR_CHANGING, "Transport Address Changing" },
  { ACN_REASON_CODE_ASKED_TO_LEAVE, "Asked to Leave" },
  { ACN_REASON_CODE_NO_RECIPIENT, "No Recipient"},
  { 0,       NULL },
};

static const value_string acn_dmp_reason_code_vals[] = {
  { ACN_DMP_REASON_CODE_NONSPECIFIC, "Nonspecific" },
  { ACN_DMP_REASON_CODE_NOT_A_PROPERTY, "Not a Property" },
  { ACN_DMP_REASON_CODE_WRITE_ONLY, "Write Only" },
  { ACN_DMP_REASON_CODE_NOT_WRITABLE, "Not Writable" },
  { ACN_DMP_REASON_CODE_DATA_ERROR, "Data Error" },
  { ACN_DMP_REASON_CODE_MAPS_NOT_SUPPORTED, "Maps not Supported" },
  { ACN_DMP_REASON_CODE_SPACE_NOT_AVAILABLE, "Space not Available" },
  { ACN_DMP_REASON_CODE_PROP_NOT_MAPPABLE, "Property not Mappable"},
  { ACN_DMP_REASON_CODE_MAP_NOT_ALLOCATED, "Map not Allocated"},
  { ACN_DMP_REASON_CODE_SUBSCRIPTION_NOT_SUPPORTED, "Subscription not Supported"},
  { ACN_DMP_REASON_CODE_NO_SUBSCRIPTIONS_SUPPORTED, "No Subscriptions Supported"},
  { 0,       NULL },
};

static const enum_val_t dmx_display_view[] = {
  { "hex"    , "Hex    ",     ACN_PREF_DMX_DISPLAY_HEX  },
  { "decimal", "Decimal",     ACN_PREF_DMX_DISPLAY_DEC  },
  { "percent", "Percent",     ACN_PREF_DMX_DISPLAY_PER  },
  { NULL, NULL, 0 }
};

static const enum_val_t dmx_display_line_format[] = {
  { "20 per line", "20 per line",     ACN_PREF_DMX_DISPLAY_20PL  },
  { "16 per line", "16 per line",     ACN_PREF_DMX_DISPLAY_16PL  },
  { NULL, NULL, 0 }
};

/******************************************************************************/
/* Test to see if it is an ACN Packet                                         */
static gboolean is_acn(tvbuff_t *tvb)
{
  static char acn_packet_id[] = "ASC-E1.17\0\0\0";  /* must be 12 bytes */
  guint8      *packet_id;

  /* Get the fields in octets 2 - 12 octet */
  packet_id = tvb_get_ephemeral_string(tvb, 4, 12);
  if (memcmp(packet_id, &acn_packet_id, 12) == 0) {
    return TRUE;
  }
  return FALSE;
}


/******************************************************************************/
/* Heuristic dissector                                                        */
static gboolean
dissect_acn_heur( tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree )
{
  /* This is a heuristic dissector, which means we get all the UDP
	 * traffic not sent to a known dissector and not claimed by
	 * a heuristic dissector called before us!
	 */

  /* abort if not enabled! */
  if (!global_acn_heur) return FALSE;

  /* abort if it is NOT an ACN packet */
  if (!is_acn(tvb)) return FALSE;

  /* else, dissect it */
  dissect_acn(tvb, pinfo, tree);
  return TRUE;
}

/******************************************************************************/
/*  Adds tree branch for channel owner info block                             */
static guint32
acn_add_channel_owner_info_block(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, int offset)
{
  proto_item *pi;
  proto_tree *this_tree = NULL;
  guint32 session_count;
  guint32 x;

  pi = proto_tree_add_text(this_tree, tvb, offset, 8, "Channel Owner Info Block");
  this_tree = proto_item_add_subtree(pi, ett_acn_channel_owner_info_block);

  proto_tree_add_item(this_tree, hf_acn_member_id, tvb, offset, 2, FALSE);
  offset += 2;
  proto_tree_add_item(this_tree, hf_acn_channel_number, tvb, offset, 2, FALSE);
  offset += 2;
  offset += acn_add_address(tvb, pinfo, this_tree, offset, "Destination Address:");
  offset += acn_add_address(tvb, pinfo, this_tree, offset, "Source Address:");

  session_count = tvb_get_ntohs(tvb, offset);
  for (x=0; x<session_count; x++) {
    pi = proto_tree_add_item(this_tree, hf_acn_protocol_id, tvb, offset, 4, FALSE);
    proto_item_append_text(pi, " #%d",  x+1);
    offset += 4;
  }
  return offset;
}

/******************************************************************************/
/*  Adds tree branch for channel member info block                            */
static guint32
acn_add_channel_member_info_block(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, int offset)
{
  proto_item *pi;
  proto_tree *this_tree = NULL;
  guint32 session_count;
  guint32 x;

  pi = proto_tree_add_text(this_tree, tvb, offset, 8, "Channel Member Info Block");
  this_tree = proto_item_add_subtree(pi, ett_acn_channel_member_info_block);

  proto_tree_add_item(this_tree, hf_acn_member_id, tvb, offset, 2, FALSE);
  offset += 2;
  proto_tree_add_item(this_tree, hf_acn_cid, tvb, offset, 16, FALSE);
  offset += 16;
  proto_tree_add_item(this_tree, hf_acn_channel_number, tvb, offset, 2, FALSE);
  offset += 2;
  offset += acn_add_address(tvb, pinfo, this_tree, offset, "Destination Address:");
  offset += acn_add_address(tvb, pinfo, this_tree, offset, "Source Address:");
  proto_tree_add_item(this_tree, hf_acn_reciprocal_channel, tvb, offset, 2, FALSE);
  offset += 2;

  session_count = tvb_get_ntohs(tvb, offset);
  for (x=0; x<session_count; x++) {
    pi = proto_tree_add_item(this_tree, hf_acn_protocol_id, tvb, offset, 4, FALSE);
    proto_item_append_text(pi, " #%d",  x+1);
    offset += 4;
  }
  return offset;
}


/******************************************************************************/
/* Add labeled expiry                                                         */
static guint32
acn_add_expiry(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, int offset, const char *label)
{
  proto_tree_add_text(tree, tvb, offset, 2, "%s %d", label, tvb_get_guint8(tvb, offset));
  offset += 1;
  return offset;
}


/******************************************************************************/
/*  Adds tree branch for channel parameters                                   */
static guint32
acn_add_channel_parameter(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, int offset)
{
  proto_item *pi;
  proto_tree *param_tree = NULL;

  pi = proto_tree_add_text(tree, tvb, offset, 8, "Channel Parameter Block");
  param_tree = proto_item_add_subtree(pi, ett_acn_channel_parameter);
  proto_tree_add_item(param_tree, hf_acn_expiry, tvb, offset, 1, FALSE);
  offset += 1;
  proto_tree_add_item(param_tree, hf_acn_nak_outbound_flag, tvb, offset, 1, FALSE);
  offset += 1;
  proto_tree_add_item(param_tree, hf_acn_nak_holdoff, tvb, offset, 2, FALSE);
  offset += 2;
  proto_tree_add_item(param_tree, hf_acn_nak_modulus, tvb, offset, 2, FALSE);
  offset += 2;
  proto_tree_add_item(param_tree, hf_acn_nak_max_wait, tvb, offset, 2, FALSE);
  offset += 2;
  return offset; /* bytes used */
}


/******************************************************************************/
/* Add an address tree                                                        */
static guint32
acn_add_address(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, int offset, const char *label)
{
  proto_item *pi;
  proto_tree *addr_tree = NULL;
  guint8 ip_address_type;

  address addr;
  guint32  IPv4;
  guint32  port;
  struct e_in6_addr IPv6;


  /* Get type */
  ip_address_type = tvb_get_guint8(tvb, offset);

  switch (ip_address_type) {
  case ACN_ADDR_NULL:
    proto_tree_add_item(tree, hf_acn_ip_address_type, tvb, offset, 1, FALSE);
    offset += 1;
    break;
  case ACN_ADDR_IPV4:
    /* Build tree and add type*/
    pi = proto_tree_add_text(tree, tvb, offset, 7, "%s", label);
    addr_tree = proto_item_add_subtree(pi, ett_acn_address);
    proto_tree_add_item(addr_tree, hf_acn_ip_address_type, tvb, offset, 1, FALSE);
    offset +=1;
    /* Add port */
    port = tvb_get_ntohs(tvb, offset);
    proto_tree_add_item(addr_tree, hf_acn_port, tvb, offset, 2, FALSE);
    offset += 2;
    /* Add Address */
    proto_tree_add_item(addr_tree, hf_acn_ipv4, tvb, offset, 4, FALSE);
    /* Append port and address to tree item */
    IPv4 = tvb_get_ipv4(tvb, offset);
    SET_ADDRESS(&addr, AT_IPv4, sizeof(IPv4), &IPv4);
    proto_item_append_text(pi, " %s, Port %d", ep_address_to_str(&addr), port);
    offset += 4;
    break;
  case ACN_ADDR_IPV6:
    /* Build tree and add type*/
    pi = proto_tree_add_text(tree, tvb, offset, 19, "%s", label);
    addr_tree = proto_item_add_subtree(pi, ett_acn_address);
    proto_tree_add_item(addr_tree, hf_acn_ip_address_type, tvb, offset, 1, FALSE);
    offset +=1;
    /* Add port */
    port = tvb_get_ntohs(tvb, offset);
    proto_tree_add_item(addr_tree, hf_acn_port, tvb, offset, 2, FALSE);
    offset += 2;
    /* Add Address */
    proto_tree_add_item(addr_tree, hf_acn_ipv6, tvb, offset, 16, FALSE);
    /* Append port and address to tree item */
    tvb_get_ipv6(tvb, offset, &IPv6);
    SET_ADDRESS(&addr, AT_IPv6, sizeof(struct e_in6_addr), &IPv6);
    proto_item_append_text(pi, " %s, Port %d", ep_address_to_str(&addr), port);
    offset += 16;
    break;
  case ACN_ADDR_IPPORT:
    /* Build tree and add type*/
    pi = proto_tree_add_text(tree, tvb, offset, 3, "%s", label);
    addr_tree = proto_item_add_subtree(pi, ett_acn_address);
    proto_tree_add_item(addr_tree, hf_acn_ip_address_type, tvb, offset, 1, FALSE);
    offset +=1;
    /* Add port */
    port = tvb_get_ntohs(tvb, offset);
    proto_tree_add_item(addr_tree, hf_acn_port, tvb, offset, 2, FALSE);
    /* Append port to tree item */
    proto_item_append_text(pi, " %s Port %d", ep_address_to_str(&addr), port);
    offset += 2;
    break;
  }
  return offset;
}

/******************************************************************************/
/*  Adds tree branch for address type                             */
static guint32
acn_add_dmp_address_type(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, int offset, acn_dmp_adt_type *adt)
{
  proto_item *pi;
  proto_tree *this_tree = NULL;
  guint8 D;
  const gchar *name;

    /* header contains address and data type */
  adt->flags = tvb_get_guint8(tvb, offset);

  D = ACN_DMP_ADT_EXTRACT_D(adt->flags);
  name = val_to_str(D, acn_dmp_adt_d_vals, "not valid (%d)");
  pi = proto_tree_add_text(tree, tvb, offset, 1, "Address and Data Type: %s", name);

  this_tree = proto_item_add_subtree(pi, ett_acn_address_type);
  proto_tree_add_uint(this_tree, hf_acn_dmp_adt_v, tvb, offset, 1, adt->flags);
  proto_tree_add_uint(this_tree, hf_acn_dmp_adt_r, tvb, offset, 1, adt->flags);
  proto_tree_add_uint(this_tree, hf_acn_dmp_adt_d, tvb, offset, 1, adt->flags);
  proto_tree_add_uint(this_tree, hf_acn_dmp_adt_x, tvb, offset, 1, adt->flags);
  proto_tree_add_uint(this_tree, hf_acn_dmp_adt_a, tvb, offset, 1, adt->flags);
  offset++;

  return offset; /* bytes used */
}

/******************************************************************************/
/* Add an dmp address                                                         */
static guint32
acn_add_dmp_address(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, int offset, acn_dmp_adt_type *adt)
{
  guint32 start_offset;
  guint32 bytes_used;
  guint8 D, A;

  start_offset = offset;

  D = ACN_DMP_ADT_EXTRACT_D(adt->flags);
  A = ACN_DMP_ADT_EXTRACT_A(adt->flags);
  switch (D) {
    case ACN_DMP_ADT_D_NS: /* Non-range address, Single data item */
      adt->increment = 1;
      adt->count = 1;
      switch (A) { /* address */
        case ACN_DMP_ADT_A_1: /* One octet address, (range: one octet address, increment, and count). */
          adt->address =   tvb_get_guint8(tvb, offset);
          offset += 1;
          bytes_used = 1;
          break;
        case ACN_DMP_ADT_A_2: /* Two octet address, (range: two octet address, increment, and count). */
          adt->address =   tvb_get_ntohs(tvb, offset);
          offset += 2;
          bytes_used = 2;
          break;
        case ACN_DMP_ADT_A_4: /* Four octet address, (range: one octet address, increment, and count). */
          adt->address =   tvb_get_ntohl(tvb, offset);
          offset += 4;
          bytes_used = 4;
          break;
        default: /* and ACN_DMP_ADT_A_R (Four octet address, (range: four octet address, increment, and count)*/
          return offset;
      } /* of switch (A)  */

      if (adt->flags & ACN_DMP_ADT_FLAG_V) {
        proto_tree_add_text(tree, tvb, start_offset, bytes_used, "Virtual Address: 0x%X", adt->address);
      } else {
        proto_tree_add_text(tree, tvb, start_offset, bytes_used, "Actual Address: 0x%X", adt->address);
      }
      break;

    case ACN_DMP_ADT_D_RS: /* Range address, Single data item */
      switch (A) {
        case ACN_DMP_ADT_A_1: /* One octet address, (range: one octet address, increment, and count). */
          adt->address =   tvb_get_guint8(tvb, offset);
          offset += 1;
          adt->increment =   tvb_get_guint8(tvb, offset);
          offset += 1;
          adt->count =   tvb_get_guint8(tvb, offset);
          offset += 1;
          bytes_used = 3;
          break;
        case ACN_DMP_ADT_A_2: /* Two octet address, (range: two octet address, increment, and count). */
          adt->address =   tvb_get_ntohs(tvb, offset);
          offset += 2;
          adt->increment =   tvb_get_ntohs(tvb, offset);
          offset += 2;
          adt->count =   tvb_get_ntohs(tvb, offset);
          offset += 2;
          bytes_used = 6;
          break;
        case ACN_DMP_ADT_A_4: /* Four octet address, (range: four octet address, increment, and count). */
          adt->address =   tvb_get_ntohl(tvb, offset);
          offset += 4;
          adt->increment =   tvb_get_ntohl(tvb, offset);
          offset += 4;
          adt->count =   tvb_get_ntohl(tvb, offset);
          offset += 4;
          bytes_used = 12;
          break;
        default: /* and ACN_DMP_ADT_A_R, this reserved....so it has no meaning yet */
          return offset;
      } /* of switch (A)  */

      if (adt->flags & ACN_DMP_ADT_FLAG_V) {
        proto_tree_add_text(tree, tvb, start_offset, bytes_used, "Virtual Address first: 0x%X, inc: %d, count: %d", adt->address, adt->increment, adt->count);
      } else {
        proto_tree_add_text(tree, tvb, start_offset, bytes_used, "Actual Address first: 0x%X, inc: %d, count: %d", adt->address, adt->increment, adt->count);
      }
      break;

    case ACN_DMP_ADT_D_RE: /* Range address, Array of equal size data items */
      switch (A) {
        case ACN_DMP_ADT_A_1: /* One octet address, (range: one octet address, increment, and count). */
          adt->address =   tvb_get_guint8(tvb, offset);
          offset += 1;
          adt->increment =   tvb_get_guint8(tvb, offset);
          offset += 1;
          adt->count =   tvb_get_guint8(tvb, offset);
          offset += 1;
          bytes_used = 3;
          break;
        case ACN_DMP_ADT_A_2: /* Two octet address, (range: two octet address, increment, and count). */
          adt->address =   tvb_get_ntohs(tvb, offset);
          offset += 2;
          adt->increment =   tvb_get_ntohs(tvb, offset);
          offset += 2;
          adt->count =   tvb_get_ntohs(tvb, offset);
          offset += 2;
          bytes_used = 6;
          break;
        case ACN_DMP_ADT_A_4: /* Four octet address, (range: four octet address, increment, and count). */
          adt->address =   tvb_get_ntohl(tvb, offset);
          offset += 4;
          adt->increment =   tvb_get_ntohl(tvb, offset);
          offset += 4;
          adt->count =   tvb_get_ntohl(tvb, offset);
          offset += 4;
          bytes_used = 12;
          break;
        default: /* and ACN_DMP_ADT_A_R, this reserved....so it has no meaning yet */
          return offset;
      } /* of switch (A)  */

      if (adt->flags & ACN_DMP_ADT_FLAG_V) {
        proto_tree_add_text(tree, tvb, start_offset, bytes_used, "Virtual Address first: 0x%X, inc: %d, count: %d", adt->address, adt->increment, adt->count);
      } else {
        proto_tree_add_text(tree, tvb, start_offset, bytes_used, "Actual Address first: 0x%X, inc: %d, count: %d", adt->address, adt->increment, adt->count);
      }
      break;

    case ACN_DMP_ADT_D_RM: /* Range address, Series of mixed size data items */
      switch (A) {
        case ACN_DMP_ADT_A_1: /* One octet address, (range: one octet address, increment, and count). */
          adt->address =   tvb_get_guint8(tvb, offset);
          offset += 1;
          adt->increment =   tvb_get_guint8(tvb, offset);
          offset += 1;
          adt->count =   tvb_get_guint8(tvb, offset);
          offset += 1;
          bytes_used = 3;
          break;
        case ACN_DMP_ADT_A_2: /* Two octet address, (range: two octet address, increment, and count). */
          adt->address =   tvb_get_ntohs(tvb, offset);
          offset += 2;
          adt->increment =   tvb_get_ntohs(tvb, offset);
          offset += 2;
          adt->count =   tvb_get_ntohs(tvb, offset);
          offset += 2;
          bytes_used = 6;
          break;
        case ACN_DMP_ADT_A_4: /* Four octet address, (range: four octet address, increment, and count). */
          adt->address =   tvb_get_ntohl(tvb, offset);
          offset += 4;
          adt->increment =   tvb_get_ntohl(tvb, offset);
          offset += 4;
          adt->count =   tvb_get_ntohl(tvb, offset);
          offset += 4;
          bytes_used = 12;
          break;
        default: /* and ACN_DMP_ADT_A_R, this reserved....so it has no meaning yet */
          return offset;
      } /* of switch (A)  */

      if (adt->flags & ACN_DMP_ADT_FLAG_V) {
        proto_tree_add_text(tree, tvb, start_offset, bytes_used, "Virtual Address first: 0x%X, inc: %d, count: %d", adt->address, adt->increment, adt->count);
      } else {
        proto_tree_add_text(tree, tvb, start_offset, bytes_used, "Actual Address first: 0x%X, inc: %d, count: %d", adt->address, adt->increment, adt->count);
      }
      break;
  } /* of switch (D) */

  return offset;
}


/*******************************************************************************/
/* Display DMP Data                                                            */
#define BUFFER_SIZE 128
static guint32
acn_add_dmp_data(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, int offset, acn_dmp_adt_type *adt)
{
  guint8 D, A;
  guint32 start_offset;
  guint32 data_size;
  guint32 data_value;
  guint32 data_address;
  guint32 x,y;
  gchar buffer[BUFFER_SIZE];
  proto_item *ti;
  guint32 ok_to_process = FALSE;

  start_offset = offset;
  buffer[0] = 0;

  /* We would like to rip through Property Address-Data pairs                 */
  /* but since we don't now how many there are nor how big the data size is,  */
  /* it not possible. So, we just show the whole thing as a block of date!    */
  /*                                                                          */
  /* There are a few exceptions however                                       */
  /* 1) if the address type is ACN_DMP_ADT_D_NS or ACN_DMP_ADT_D_RS and       */
  /*    or ACN_DMP_ADT_D_RE                                                   */
  /*    then number of bytes is <= count + 4. Each value is at least one byte */
  /*    and another address/data pair is at least 4 bytes so if the remaining */
  /*    bytes is less than the count plus 4 then the remaining data           */
  /*    must be all data                                                      */
  /*                                                                          */
  /* 2) if the address type is ACN_DMP_ADT_D_RE and the number of bytes       */
  /*    equals the number of bytes in remaining in the pdu then there is      */
  /*    a 1 to one match                                                      */

  D = ACN_DMP_ADT_EXTRACT_D(adt->flags);
  switch (D) {
    case ACN_DMP_ADT_D_NS:
    case ACN_DMP_ADT_D_RS:
      if (adt->data_length <= adt->count + 4) {
        ok_to_process = TRUE;
      }
      break;
    case ACN_DMP_ADT_D_RE:
      if (adt->data_length == adt->count) {
        ok_to_process = TRUE;
      }
      if (adt->data_length <= adt->count + 4) {
        ok_to_process = TRUE;
      }
      break;
  }

  if (!ok_to_process) {
    data_size = adt->data_length;
    ti = proto_tree_add_item(tree, hf_acn_data, tvb, offset, data_size, FALSE);
    offset += data_size;
    proto_item_set_text(ti, "Data and more Address-Data Pairs (further dissection not possible)");
    return offset;
  }

  A = ACN_DMP_ADT_EXTRACT_A(adt->flags);
  switch (D) {
    case ACN_DMP_ADT_D_NS: /* Non-range address, Single data item */
      /* calculate data size */
      data_size = adt->data_length;
      data_address = adt->address;

      switch (A) {
        case ACN_DMP_ADT_A_1: /* One octet address, (range: one octet address, increment, and count). */
          g_snprintf(buffer, BUFFER_SIZE, "Addr 0x%2.2X ->", data_address);
          break;
        case ACN_DMP_ADT_A_2: /* Two octet address, (range: two octet address, increment, and count). */
          g_snprintf(buffer, BUFFER_SIZE, "Addr 0x%4.4X ->", data_address);
          break;
        case ACN_DMP_ADT_A_4: /* Four octet address, (range: four octet address, increment, and count). */
          g_snprintf(buffer, BUFFER_SIZE, "Addr 0x%8.8X ->", data_address);
          break;
        default: /* and ACN_DMP_ADT_A_R, this reserved....so it has no meaning yet */
          offset += data_size;
          return offset;
      }

      switch (data_size) {
        case 1:
          data_value = tvb_get_guint8(tvb, offset);
          proto_tree_add_uint_format(tree, hf_acn_data8, tvb, offset, 1, data_value, "%s %2.2X", buffer, data_value);
          break;
        case 2:
          data_value = tvb_get_ntohs(tvb, offset);
          proto_tree_add_uint_format(tree, hf_acn_data16, tvb, offset, 2, data_value, "%s %4.4X", buffer, data_value);
          break;
        case 3:
          data_value = tvb_get_ntoh24(tvb, offset);
          proto_tree_add_uint_format(tree, hf_acn_data24, tvb, offset, 3, data_value, "%s %6.6X", buffer, data_value);
          break;
        case 4:
          data_value = tvb_get_ntohl(tvb, offset);
          proto_tree_add_uint_format(tree, hf_acn_data32, tvb, offset, 4, data_value, "%s %8.8X", buffer, data_value);
          break;
        default:
          /* build string of values */
          for (y=0;y<20 && y<data_size;y++) {
            data_value = tvb_get_guint8(tvb, offset+y);
            g_snprintf(buffer, BUFFER_SIZE, "%s %2.2X", buffer, data_value);
          }
          /* add the item */
          ti = proto_tree_add_item(tree, hf_acn_data, tvb, offset, data_size, FALSE);
          offset += data_size;
          /* change the text */
          proto_item_set_text(ti, "%s", buffer);
          break;
      } /* of switch (data_size) */
      offset += data_size;
      break;

    case ACN_DMP_ADT_D_RS: /* Range address, Single data item */
      /* calculate data size */
      data_size = adt->data_length;
      data_address = adt->address;

      for (x=0;x<adt->count;x++) {
        switch (A) {
          case ACN_DMP_ADT_A_1: /* One octet address, (range: one octet address, increment, and count). */
            g_snprintf(buffer, BUFFER_SIZE, "Addr 0x%2.2X ->", data_address);
            break;
          case ACN_DMP_ADT_A_2: /* Two octet address, (range: two octet address, increment, and count). */
            g_snprintf(buffer, BUFFER_SIZE, "Addr 0x%4.4X ->", data_address);
            break;
          case ACN_DMP_ADT_A_4: /* Four octet address, (range: four octet address, increment, and count). */
            g_snprintf(buffer, BUFFER_SIZE, "Addr 0x%8.8X ->", data_address);
            break;
          default: /* and ACN_DMP_ADT_A_R, this reserved....so it has no meaning yet */
            return offset;
        }

        switch (data_size) {
          case 1:
            data_value = tvb_get_guint8(tvb, offset);
            proto_tree_add_uint_format(tree, hf_acn_data8, tvb, offset, 1, data_value, "%s %2.2X", buffer, data_value);
            break;
          case 2:
            data_value = tvb_get_ntohs(tvb, offset);
            proto_tree_add_uint_format(tree, hf_acn_data8, tvb, offset, 2, data_value, "%s %4.4X", buffer, data_value);
            break;
          case 3:
            data_value = tvb_get_ntoh24(tvb, offset);
            proto_tree_add_uint_format(tree, hf_acn_data8, tvb, offset, 3, data_value, "%s %6.6X", buffer, data_value);
            break;
          case 4:
            data_value = tvb_get_ntohl(tvb, offset);
            proto_tree_add_uint_format(tree, hf_acn_data8, tvb, offset, 4, data_value, "%s %8.8X", buffer, data_value);
            break;
          default:
            /* build string of values */
            for (y=0;y<20 && y<data_size;y++) {
              data_value = tvb_get_guint8(tvb, offset+y);
              g_snprintf(buffer, BUFFER_SIZE, "%s %2.2X", buffer, data_value);
            }
            /* add the item */
            ti = proto_tree_add_item(tree, hf_acn_data, tvb, offset, data_size, FALSE);
            /* change the text */
            proto_item_set_text(ti, "%s", buffer);
            break;
        } /* of switch (data_size) */
        data_address += adt->increment;
      } /* of (x=0;x<adt->count;x++) */
      offset += data_size;
      break;

    case ACN_DMP_ADT_D_RE: /* Range address, Array of equal size data items */
      /* calculate data size */
      data_size = adt->data_length / adt->count;
      data_address = adt->address;

      for (x=0;x<adt->count;x++) {
        switch (A) {
          case ACN_DMP_ADT_A_1: /* One octet address, (range: one octet address, increment, and count). */
            g_snprintf(buffer, BUFFER_SIZE, "Addr 0x%2.2X ->", data_address);
            break;
          case ACN_DMP_ADT_A_2: /* Two octet address, (range: two octet address, increment, and count). */
            g_snprintf(buffer, BUFFER_SIZE, "Addr 0x%4.4X ->", data_address);
            break;
          case ACN_DMP_ADT_A_4: /* Four octet address, (range: four octet address, increment, and count). */
            g_snprintf(buffer, BUFFER_SIZE, "Addr 0x%8.8X ->", data_address);
            break;
          default: /* and ACN_DMP_ADT_A_R, this reserved....so it has no meaning yet */
            return offset;
        }

        switch (data_size) {
          case 1:
            data_value = tvb_get_guint8(tvb, offset);
            proto_tree_add_uint_format(tree, hf_acn_data8, tvb, offset, 1, data_value, "%s %2.2X", buffer, data_value);
            break;
          case 2:
            data_value = tvb_get_ntohs(tvb, offset);
            proto_tree_add_uint_format(tree, hf_acn_data8, tvb, offset, 2, data_value, "%s %4.4X", buffer, data_value);
            break;
          case 3:
            data_value = tvb_get_ntoh24(tvb, offset);
            proto_tree_add_uint_format(tree, hf_acn_data8, tvb, offset, 3, data_value, "%s %6.6X", buffer, data_value);
            break;
          case 4:
            data_value = tvb_get_ntohl(tvb, offset);
            proto_tree_add_uint_format(tree, hf_acn_data8, tvb, offset, 4, data_value, "%s %8.8X", buffer, data_value);
            break;
          default:
            /* build string of values */
            for (y=0;y<20 && y<data_size;y++) {
              data_value = tvb_get_guint8(tvb, offset+y);
              g_snprintf(buffer, BUFFER_SIZE, "%s %2.2X", buffer, data_value);
            }
            /* add the item */
            ti = proto_tree_add_item(tree, hf_acn_data, tvb, offset, data_size, FALSE);
            /* change the text */
            proto_item_set_text(ti, "%s", buffer);
            break;
        } /* of switch (data_size) */

        offset += data_size;
        data_address += adt->increment;
      } /* of (x=0;x<adt->count;x++) */
      break;

    case ACN_DMP_ADT_D_RM: /* Range address, Series of mixed size data items */
      data_size = adt->data_length;
      ti = proto_tree_add_item(tree, hf_acn_data, tvb, offset, data_size, FALSE);
      offset += data_size;
      /* change the text */
      proto_item_set_text(ti, "Mixed size data items");
      break;
  } /* of switch (D) */

  return offset;
}

/*******************************************************************************/
/* Display DMP Reason codes                                                    */
  #define BUFFER_SIZE 128
static guint32
acn_add_dmp_reason_codes(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, int offset, acn_dmp_adt_type *adt)
{
  guint8 D, A;
  guint32 start_offset;
  guint32 data_value;
  guint32 data_address;
  guint32 x;

  gchar buffer[BUFFER_SIZE];
  const gchar *name;

  start_offset = offset;
  buffer[0] = 0;

  D = ACN_DMP_ADT_EXTRACT_D(adt->flags);
  A = ACN_DMP_ADT_EXTRACT_A(adt->flags);
  switch (D) {
    case ACN_DMP_ADT_D_NS: /* Non-range address, Single data item */
      data_address = adt->address;
      switch (A) {
        case ACN_DMP_ADT_A_1: /* One octet address, (range: one octet address, increment, and count). */
          g_snprintf(buffer, BUFFER_SIZE, "Addr 0x%2.2X ->", data_address);
          break;
        case ACN_DMP_ADT_A_2: /* Two octet address, (range: two octet address, increment, and count). */
          g_snprintf(buffer, BUFFER_SIZE, "Addr 0x%4.4X ->", data_address);
          break;
        case ACN_DMP_ADT_A_4: /* Four octet address, (range: four octet address, increment, and count). */
          g_snprintf(buffer, BUFFER_SIZE, "Addr 0x%8.8X ->", data_address);
          break;
        default: /* and ACN_DMP_ADT_A_R, this reserved....so it has no meaning yet */
          return offset;
      }

      /* Get reason */
      data_value = tvb_get_guint8(tvb, offset);
      /* convert to string */
      name = val_to_str(data_value, acn_dmp_reason_code_vals, "reason not valid (%d)");
      /* Add item */
      proto_tree_add_uint_format(tree, hf_acn_data8, tvb, offset, 1, data_value, "%s %s", buffer, name);
      offset++;
      break;

    case ACN_DMP_ADT_D_RS: /* Range address, Single data item */
      data_address = adt->address;
      for (x=0;x<adt->count;x++) {
        switch (A) {
          case ACN_DMP_ADT_A_1: /* One octet address, (range: one octet address, increment, and count). */
            g_snprintf(buffer, BUFFER_SIZE, "Addr 0x%2.2X ->", data_address);
            break;
          case ACN_DMP_ADT_A_2: /* Two octet address, (range: two octet address, increment, and count). */
            g_snprintf(buffer, BUFFER_SIZE, "Addr 0x%4.4X ->", data_address);
            break;
          case ACN_DMP_ADT_A_4: /* Four octet address, (range: four octet address, increment, and count). */
            g_snprintf(buffer, BUFFER_SIZE, "Addr 0x%8.8X ->", data_address);
            break;
          default: /* and ACN_DMP_ADT_A_R, this reserved....so it has no meaning yet */
            return offset;
        }

        /* Get reason */
        data_value = tvb_get_guint8(tvb, offset);
        /* convert to string */
        name = val_to_str(data_value, acn_dmp_reason_code_vals, "reason not valid (%d)");
        /* Add item */
        proto_tree_add_uint_format(tree, hf_acn_data8, tvb, offset, 1, data_value, "%s %s", buffer, name);

        data_address += adt->increment;
      } /* of (x=0;x<adt->count;x++) */
      offset++;
      break;

    case ACN_DMP_ADT_D_RE: /* Range address, Array of equal size data items */
    case ACN_DMP_ADT_D_RM: /* Range address, Series of mixed size data items */
      data_address = adt->address;
      for (x=0;x<adt->count;x++) {
        switch (A) {
          case ACN_DMP_ADT_A_1: /* One octet address, (range: one octet address, increment, and count). */
            g_snprintf(buffer, BUFFER_SIZE, "Addr 0x%2.2X ->", data_address);
            break;
          case ACN_DMP_ADT_A_2: /* Two octet address, (range: two octet address, increment, and count). */
            g_snprintf(buffer, BUFFER_SIZE, "Addr 0x%4.4X ->", data_address);
            break;
          case ACN_DMP_ADT_A_4: /* Four octet address, (range: four octet address, increment, and count). */
            g_snprintf(buffer, BUFFER_SIZE, "Addr 0x%8.8X ->", data_address);
            break;
          default: /* and ACN_DMP_ADT_A_R, this reserved....so it has no meaning yet */
            return offset;
        }
        /* Get reason */
        data_value = tvb_get_guint8(tvb, offset);
        /* convert to string */
        name = val_to_str(data_value, acn_dmp_reason_code_vals, "reason not valid (%d)");
        /* Add item */
        proto_tree_add_uint_format(tree, hf_acn_data8, tvb, offset, 1, data_value, "%s %s", buffer, name);
        data_address += adt->increment;
        offset++;
      } /* of (x=0;x<adt->count;x++) */
      break;
  } /* of switch (D) */

  return offset;
}

/******************************************************************************/
/* Dissect wrapped SDT PDU                                                    */
static guint32
dissect_acn_dmp_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, acn_pdu_offsets *last_pdu_offsets)
{
  /* common to all pdu */
  guint8 pdu_flags;
  guint32 pdu_start;
  guint32 pdu_length;
  guint32 pdu_flvh_length; /* flags, length, vector, header */
  acn_pdu_offsets pdu_offsets = {0,0,0,0,0};
  guint8 D;
  guint8 octet;
  guint32 length1;
  guint32 length2;
  guint32 length3;
  guint32 vector_offset;
  guint32 header_offset;
  guint32 data_offset;
  guint32 old_offset;
  guint32 end_offset;
  guint32 data_length;
  guint32 address_count;

  proto_item *ti, *pi;
  proto_tree *pdu_tree = NULL;
  proto_tree *flag_tree = NULL;

  /* this pdu */
  const gchar *name;
  acn_dmp_adt_type adt = {0,0,0,0,0,0};
  acn_dmp_adt_type adt2 = {0,0,0,0,0,0};
  guint32 vector;

  /* save start of pdu block */
  pdu_start = offset;
  pdu_offsets.start = pdu_start;

  /* get PDU flags and length flag first */
  octet = tvb_get_guint8(tvb, offset++);
  pdu_flags =  octet & 0xf0;
  length1 = octet & 0x0f;                   /* bottom 4 bits only */
  length2 = tvb_get_guint8(tvb, offset++);

  /* if length flag is set, then we have a 20 bit length else we have a 12 bit */
  /* flvh = flags, length, vector, header */
  if (pdu_flags & ACN_PDU_FLAG_L) {
    length3 = tvb_get_guint8(tvb, offset);
    offset++;
    pdu_length = length3 | (length2 << 8) | (length1 << 16);
    pdu_flvh_length = 3;
  } else {
    pdu_length = length2 | (length1 << 8);
    pdu_flvh_length = 2;
  }
  /* offset should now be pointing to vector (if one exists) */

  /* Add pdu item and tree */
  ti = proto_tree_add_item(tree, hf_acn_pdu, tvb, pdu_start, pdu_length, FALSE);
  pdu_tree = proto_item_add_subtree(ti, ett_acn_dmp_pdu);

  /* Add flag item and tree */
  pi = proto_tree_add_uint(pdu_tree, hf_acn_pdu_flags, tvb, pdu_start, 1, pdu_flags);
  flag_tree = proto_item_add_subtree(pi, ett_acn_pdu_flags);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_l, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_v, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_h, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_d, tvb, pdu_start, 1, FALSE);

  /* Add PDU Length item */
  proto_tree_add_uint(pdu_tree, hf_acn_pdu_length, tvb, pdu_start, pdu_flvh_length, pdu_length);

  /* Set vector offset */
  if (pdu_flags & ACN_PDU_FLAG_V) {
    /* use new values */
    vector_offset = offset;
    last_pdu_offsets->vector = offset;
    offset++;
    pdu_flvh_length++;
  } else {
    /* use last values */
    vector_offset = last_pdu_offsets->vector;
  }
  /* offset should now be pointing to header (if one exists) */

  /* Add Vector item */
  vector = tvb_get_guint8(tvb, vector_offset);
  proto_tree_add_uint(pdu_tree, hf_acn_dmp_vector, tvb, vector_offset, 1, vector);

  /* Add Vector item to tree*/
  name = val_to_str(vector, acn_dmp_vector_vals, "not valid (%d)");
  proto_item_append_text(ti, ": ");
  proto_item_append_text(ti, "%s", name);

  /* Set header offset */
  if (pdu_flags & ACN_PDU_FLAG_H) {
    /* use new values */
    header_offset = offset;
    last_pdu_offsets->header = offset;
    offset++;
    pdu_flvh_length++;
  } else {
    /* use last values */
    header_offset = last_pdu_offsets->header;
  }
  /* offset should now be pointing to data (if one exists) */

  /* header contains address and data type */
  acn_add_dmp_address_type(tvb, pinfo, pdu_tree, header_offset, &adt);

  /* Adjust data */
  if (pdu_flags & ACN_PDU_FLAG_D) {
    /* use new values */
    data_offset = offset;
    data_length = pdu_length - pdu_flvh_length;
    last_pdu_offsets->data = offset;
    last_pdu_offsets->data_length = data_length;
  } else {
    /* use last values */
    data_offset = last_pdu_offsets->data;
    data_length = last_pdu_offsets->data_length;
  }
  end_offset = data_offset + data_length;

  switch (vector) {
  case ACN_DMP_VECTOR_UNKNOWN:
    break;
  case ACN_DMP_VECTOR_GET_PROPERTY:
    /* Rip trough property address */
    while (data_offset < end_offset) {
      old_offset = data_offset;
      data_offset = acn_add_dmp_address(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;
    }
    break;
  case ACN_DMP_VECTOR_SET_PROPERTY:
    /* Rip through Property Address-Data pairs                                 */
    /* But, in reality, this generally won't work as we have know way of       */
    /* calculating the next Address-Data pair                                  */
    while (data_offset < end_offset) {
      old_offset = data_offset;
      data_offset = acn_add_dmp_address(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;

      adt.data_length = data_length - (data_offset - old_offset);
      old_offset = data_offset;
      data_offset = acn_add_dmp_data(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;
    }
    break;
  case ACN_DMP_VECTOR_GET_PROPERTY_REPLY:
    /* Rip through Property Address-Data pairs */
    /* But, in reality, this generally won't work as we have know way of       */
    /* calculating the next Address-Data pair                                  */
    while (data_offset < end_offset) {
      old_offset = data_offset;
      data_offset = acn_add_dmp_address(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;

      adt.data_length = data_length - (data_offset - old_offset);
      old_offset = data_offset;
      data_offset = acn_add_dmp_data(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;
    }
    break;
  case ACN_DMP_VECTOR_EVENT:
    /* Rip through Property Address-Data pairs */
    /* But, in reality, this generally won't work as we have know way of       */
    /* calculating the next Address-Data pair                                  */
    while (data_offset < end_offset) {
      old_offset = data_offset;
      data_offset = acn_add_dmp_address(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;

      adt.data_length = data_length - (data_offset - old_offset);
      old_offset = data_offset;
      data_offset = acn_add_dmp_data(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;
    }
    break;
  case ACN_DMP_VECTOR_MAP_PROPERTY:
    /* Virtual Address type */
    data_offset = acn_add_dmp_address_type(tvb, pinfo, pdu_tree, data_offset, &adt2);
    /* Rip through Actual-Virtual Address Pairs */
    while (data_offset < end_offset) {
      /* actual */
      old_offset = data_offset;
      data_offset = acn_add_dmp_address(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;
      D = ACN_DMP_ADT_EXTRACT_D(adt.flags);
      switch (D) {
        case ACN_DMP_ADT_D_NS:
          address_count = 1;
          break;
        case ACN_DMP_ADT_D_RS:
          address_count = 1;
          break;
        case ACN_DMP_ADT_D_RE:
          address_count = adt.count;
          break;
        /*case ACN_DMP_ADT_D_RM: */
        default:
          /* OUCH */
          return pdu_start + pdu_length;
          break;
      }

      /* virtual */
      while (address_count > 0) {
        data_offset = acn_add_dmp_address(tvb, pinfo, pdu_tree, data_offset, &adt2);
        address_count--;
      }
    }
    break;
  case ACN_DMP_VECTOR_UNMAP_PROPERTY:
    /* Rip trough Actaul Proptery Address */
    while (data_offset < end_offset) {
      old_offset = data_offset;
      data_offset = acn_add_dmp_address(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;
    }
    break;
  case ACN_DMP_VECTOR_SUBSCRIBE:
    /* Rip trough Proptery Address */
    while (data_offset < end_offset) {
      old_offset = data_offset;
      data_offset = acn_add_dmp_address(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;
    }
    break;
  case ACN_DMP_VECTOR_UNSUBSCRIBE:
    /* Rip trough Proptery Address */
    while (data_offset < end_offset) {
      old_offset = data_offset;
      data_offset = acn_add_dmp_address(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;
    }
    break;
  case ACN_DMP_VECTOR_GET_PROPERTY_FAIL:
    /* Rip trough Address-Reason Code Pairs */
    while (data_offset < end_offset) {
      old_offset = data_offset;
      data_offset = acn_add_dmp_address(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;

      adt.data_length = data_length - (data_offset - old_offset);
      old_offset = data_offset;
      data_offset = acn_add_dmp_reason_codes(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;
    }
    break;
  case ACN_DMP_VECTOR_SET_PROPERTY_FAIL:
    /* Rip trough Address-Reason Code Pairs */
    while (data_offset < end_offset) {
      old_offset = data_offset;
      data_offset = acn_add_dmp_address(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;

      adt.data_length = data_length - (data_offset - old_offset);
      old_offset = data_offset;
      data_offset = acn_add_dmp_reason_codes(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;
    }
    break;
  case ACN_DMP_VECTOR_MAP_PROPERTY_FAIL:
    /* Rip trough Address-Reason Code Pairs */
    while (data_offset < end_offset) {
      old_offset = data_offset;
      data_offset = acn_add_dmp_address(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;

      adt.data_length = data_length - (data_offset - old_offset);
      old_offset = data_offset;
      data_offset = acn_add_dmp_reason_codes(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;
    }
    break;
  case ACN_DMP_VECTOR_SUBSCRIBE_ACCEPT:
    /* Rip through Property Addrsses */
    while (data_offset < end_offset) {
      old_offset = data_offset;
      data_offset = acn_add_dmp_address(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;
    }
    break;
  case ACN_DMP_VECTOR_SUBSCRIBE_REJECT:
    /* Rip trough Address-Reason Code Pairs */
    while (data_offset < end_offset) {
      old_offset = data_offset;
      data_offset = acn_add_dmp_address(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;

      adt.data_length = data_length - (data_offset - old_offset);
      old_offset = data_offset;
      data_offset = acn_add_dmp_reason_codes(tvb, pinfo, pdu_tree, data_offset, &adt);
      if (old_offset == data_offset) break;
    }
    break;
  case ACN_DMP_VECTOR_ALLOCATE_MAP:
    /* No data for this */
    break;
  case ACN_DMP_VECTOR_ALLOCATE_MAP_REPLY:
    /* Single reason code  */
    proto_tree_add_item(pdu_tree, hf_acn_dmp_reason_code, tvb, data_offset, 1, FALSE);
    data_offset++;
  case ACN_DMP_VECTOR_DEALLOCATE_MAP:
    /* No data for this */
    break;
  }

  return pdu_start + pdu_length;
}


/******************************************************************************/
/* Dissect wrapped SDT PDU                                                    */
static guint32
dissect_acn_sdt_wrapped_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, int offset, acn_pdu_offsets *last_pdu_offsets)
{
  /* common to all pdu */
  guint8 pdu_flags;
  guint32 pdu_start;
  guint32 pdu_length;
  guint32 pdu_flvh_length; /* flags, length, vector, header */
  acn_pdu_offsets pdu_offsets = {0,0,0,0,0};
  guint8 octet;
  guint32 length1;
  guint32 length2;
  guint32 length3;
  guint32 vector_offset;
  guint32 data_offset;
  guint32 end_offset;
  guint32 data_length;

  proto_item *ti, *pi;
  proto_tree *pdu_tree = NULL;
  proto_tree *flag_tree = NULL;

  /* this pdu */
  const gchar *name;
  guint32 vector;

  /* save start of pdu block */
  pdu_start = offset;
  pdu_offsets.start = pdu_start;

  /* get PDU flags and length flag first */
  octet = tvb_get_guint8(tvb, offset++);
  pdu_flags =  octet & 0xf0;
  length1 = octet & 0x0f;                   /* bottom 4 bits only */
  length2 = tvb_get_guint8(tvb, offset++);

  /* if length flag is set, then we have a 20 bit length else we have a 12 bit */
  /* flvh = flags, length, vector, header */
  if (pdu_flags & ACN_PDU_FLAG_L) {
    length3 = tvb_get_guint8(tvb, offset);
    offset++;
    pdu_length = length3 | (length2 << 8) | (length1 << 16);
    pdu_flvh_length = 3;
  } else {
    pdu_length = length2 | (length1 << 8);
    pdu_flvh_length = 2;
  }
  /* offset should now be pointing to vector (if one exists) */

  /* Add pdu item and tree */
  ti = proto_tree_add_item(tree, hf_acn_pdu, tvb, pdu_start, pdu_length, FALSE);
  pdu_tree = proto_item_add_subtree(ti, ett_acn_sdt_pdu);

  /* Add flag item and tree */
  pi = proto_tree_add_uint(pdu_tree, hf_acn_pdu_flags, tvb, pdu_start, 1, pdu_flags);
  flag_tree = proto_item_add_subtree(pi, ett_acn_pdu_flags);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_l, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_v, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_h, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_d, tvb, pdu_start, 1, FALSE);

  /* Add PDU Length item */
  proto_tree_add_uint(pdu_tree, hf_acn_pdu_length, tvb, pdu_start, pdu_flvh_length, pdu_length);

  /* Set vector offset */
  if (pdu_flags & ACN_PDU_FLAG_V) {
    /* use new values */
    vector_offset = offset;
    last_pdu_offsets->vector = offset;
    offset++;
    pdu_flvh_length++;
  } else {
    /* use last values */
    vector_offset = last_pdu_offsets->vector;
  }
  /* offset should now be pointing to header (if one exists) */

  /* Add Vector item */
  vector = tvb_get_guint8(tvb, vector_offset);
  proto_tree_add_uint(pdu_tree, hf_acn_sdt_vector, tvb, vector_offset, 1, vector);

  /* Add Vector item to tree*/
  name = val_to_str(vector, acn_sdt_vector_vals, "not valid (%d)");
  proto_item_append_text(ti, ": ");
  proto_item_append_text(ti, "%s", name);

  /* NO HEADER DATA ON THESE* (at least so far) */

  /* Adjust data */
  if (pdu_flags & ACN_PDU_FLAG_D) {
    /* use new values */
    data_offset = offset;
    data_length = pdu_length - pdu_flvh_length;
    last_pdu_offsets->data = offset;
    last_pdu_offsets->data_length = data_length;
  } else {
    /* use last values */
    data_offset = last_pdu_offsets->data;
    data_length = last_pdu_offsets->data_length;
  }
  end_offset = data_offset + data_length;

  switch (vector) {
  case ACN_SDT_VECTOR_ACK:
    proto_tree_add_item(pdu_tree, hf_acn_reliable_sequence_number, tvb, data_offset, 4, FALSE);
    data_offset += 4;
    break;
  case ACN_SDT_VECTOR_CHANNEL_PARAMS:
    data_offset = acn_add_channel_parameter(tvb, pinfo, pdu_tree, data_offset);
    data_offset = acn_add_address(tvb, pinfo, pdu_tree, data_offset, "Ad-hoc Address:");
    data_offset = acn_add_expiry(tvb, pinfo, pdu_tree, data_offset, "Ad-hoc Expiry:");
   break;
  case ACN_SDT_VECTOR_LEAVE:
    /* nothing more */
    break;
  case ACN_SDT_VECTOR_CONNECT:
    /* Protocol ID item */
    proto_tree_add_item(pdu_tree, hf_acn_protocol_id, tvb, data_offset, 4, FALSE);
    data_offset += 4;
    break;
  case ACN_SDT_VECTOR_CONNECT_ACCEPT:
    /* Protocol ID item */
    proto_tree_add_item(pdu_tree, hf_acn_protocol_id, tvb, data_offset, 4, FALSE);
    data_offset += 4;
    break;
  case ACN_SDT_VECTOR_CONNECT_REFUSE:
    /* Protocol ID item */
    proto_tree_add_item(pdu_tree, hf_acn_protocol_id, tvb, data_offset, 4, FALSE);
    data_offset += 4;
    proto_tree_add_item(pdu_tree, hf_acn_refuse_code, tvb, data_offset, 1, FALSE);
    data_offset++;
    break;
  case ACN_SDT_VECTOR_DISCONNECT:
    /* Protocol ID item */
    proto_tree_add_item(pdu_tree, hf_acn_protocol_id, tvb, data_offset, 4, FALSE);
    data_offset += 4;
    break;
  case ACN_SDT_VECTOR_DISCONNECTING:
    /* Protocol ID item */
    proto_tree_add_item(pdu_tree, hf_acn_protocol_id, tvb, data_offset, 4, FALSE);
    data_offset += 4;
    proto_tree_add_item(pdu_tree, hf_acn_reason_code, tvb, data_offset, 1, FALSE);
    data_offset++;
    break;

  }

  return pdu_start + pdu_length;
}


/******************************************************************************/
/* Dissect SDT Client PDU                                                     */
static guint32
dissect_acn_sdt_client_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, acn_pdu_offsets *last_pdu_offsets)
{
  /* common to all pdu */
  guint8 pdu_flags;
  guint32 pdu_start;
  guint32 pdu_length;
  guint32 pdu_flvh_length; /* flags, length, vector, header */
  acn_pdu_offsets pdu_offsets = {0,0,0,0,0};
  guint8 octet;
  guint32 length1;
  guint32 length2;
  guint32 length3;
  guint32 vector_offset;
  guint32 header_offset;
  guint32 data_offset;
  guint32 data_length;
  guint32 old_offset;
  guint32 end_offset;

  proto_item *ti, *pi;
  proto_tree *pdu_tree = NULL;
  proto_tree *flag_tree = NULL;

  /* this pdu */
  const gchar *name;
  guint32 member_id;
  guint32 protocol_id;
  guint16 association;

  /* save start of pdu block */
  pdu_start = offset;
  pdu_offsets.start = pdu_start;

  /* get PDU flags and length flag first */
  octet = tvb_get_guint8(tvb, offset++);
  pdu_flags =  octet & 0xf0;
  length1 = octet & 0x0f;                   /* bottom 4 bits only */
  length2 = tvb_get_guint8(tvb, offset++);

  /* if length flag is set, then we have a 20 bit length else we have a 12 bit */
  /* flvh = flags, length, vector, header */
  if (pdu_flags & ACN_PDU_FLAG_L) {
    length3 = tvb_get_guint8(tvb, offset);
    offset++;
    pdu_length = length3 | (length2 << 8) | (length1 << 16);
    pdu_flvh_length = 3;
  } else {
    pdu_length = length2 | (length1 << 8);
    pdu_flvh_length = 2;
  }
  /* offset should now be pointing to vector (if one exists) */

  /* Add pdu item and tree */
  ti = proto_tree_add_item(tree, hf_acn_pdu, tvb, pdu_start, pdu_length, FALSE);
  pdu_tree = proto_item_add_subtree(ti, ett_acn_sdt_client_pdu);

  /* Add flag item and tree */
  pi = proto_tree_add_uint(pdu_tree, hf_acn_pdu_flags, tvb, pdu_start, 1, pdu_flags);
  flag_tree = proto_item_add_subtree(pi, ett_acn_pdu_flags);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_l, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_v, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_h, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_d, tvb, pdu_start, 1, FALSE);

  /* Add PDU Length item */
  proto_tree_add_uint(pdu_tree, hf_acn_pdu_length, tvb, pdu_start, pdu_flvh_length, pdu_length);

  /* Set vector offset */
  if (pdu_flags & ACN_PDU_FLAG_V) {
    /* use new values */
    vector_offset = offset;
    last_pdu_offsets->vector = offset;
    offset += 2;
    pdu_flvh_length += 2;
  } else {
    /* use last values */
    vector_offset = last_pdu_offsets->vector;
  }
  /* offset should now be pointing to header (if one exists) */

  /* add Member ID item  */
  member_id = tvb_get_ntohs(tvb, vector_offset);
  proto_tree_add_uint(pdu_tree, hf_acn_member_id, tvb, vector_offset, 2, member_id);

  /* Set header offset */
  if (pdu_flags & ACN_PDU_FLAG_H) {
    /* use new values */
    header_offset = offset;
    last_pdu_offsets->header = offset;
    offset += 6;
    pdu_flvh_length += 6;
  } else {
    /* use last values */
    header_offset = last_pdu_offsets->header;
  }
  /* offset should now be pointing to data (if one exists) */

  /* add Protocol ID item (Header)*/
  protocol_id = tvb_get_ntohl(tvb, header_offset);
  proto_tree_add_uint(pdu_tree, hf_acn_protocol_id, tvb, header_offset, 4, protocol_id);
  header_offset += 4;

  /* Add protocol to tree*/
  name = val_to_str(protocol_id, acn_protocol_id_vals, "id not valid (%d)");
  proto_item_append_text(ti, ": ");
  proto_item_append_text(ti, "%s", name);

  /* add association item */
  association = tvb_get_ntohs(tvb, header_offset);
  proto_tree_add_uint(pdu_tree, hf_acn_association, tvb, header_offset, 2, association);
  header_offset += 2;

  /* Adjust data */
  if (pdu_flags & ACN_PDU_FLAG_D) {
    /* use new values */
    data_offset = offset;
    data_length = pdu_length - pdu_flvh_length;
    last_pdu_offsets->data = offset;
    last_pdu_offsets->data_length = data_length;
  } else {
    /* use last values */
    data_offset = last_pdu_offsets->data;
    data_length = last_pdu_offsets->data_length;
  }
  end_offset = data_offset + data_length;

  switch (protocol_id) {
  case ACN_PROTOCOL_ID_SDT:
    while (data_offset < end_offset) {
      old_offset = data_offset;
      data_offset = dissect_acn_sdt_wrapped_pdu(tvb, pinfo, pdu_tree, data_offset, &pdu_offsets);
      if (old_offset == data_offset) break;
    }
    break;
  case ACN_PROTOCOL_ID_DMP:
    while (data_offset < end_offset) {
      old_offset = data_offset;
      data_offset = dissect_acn_dmp_pdu(tvb, pinfo, pdu_tree, data_offset, &pdu_offsets);
      if (data_offset == old_offset) break;
    }
    break;
  }
  return pdu_start + pdu_length;
}


/******************************************************************************/
/* level to string (ascii)                                                    */
/*  level    : 8 bit value                                                    */
/*  string   : pointer to buffer to fill                                      */
/*  leading_char: character to buffer left of digits                             */
/*  min_char : mininum number of characters (for filling, not including space)*/
/*  show_zero: show zeros or dots                                             */
/* also adds a space to right end                                             */
/*                                                                            */
/*  returns end of string                                                     */
/*  faster than printf()                                                      */
static char *
ltos(guint8 level, gchar *string, guint8 base, gchar leading_char, guint8 min_chars, gboolean show_zero)
{
  guint8 i;
  /* verify base */
  if (base < 2 || base > 16) {
    *string = '\0';
    return(string);
  }
  /* deal with zeros */
  if ((level == 0) && (!show_zero)) {
    for (i=0;i<min_chars;i++) {
      string[i] = '.';
    }
    string[i++] = ' ';
    string[i] = '\0';
    return(string + i);
  }

  i = 0;
  /* do our convert, comes out backwords! */
  do {
    string[i++] = "0123456789ABCDEF"[level % base];
  } while ((level /= base) > 0);

  /* expand to needed character */
  for (;i<min_chars;i++) {
    string[i] = leading_char;
  }
  /* terminate */
  string[i] = '\0';

  /* now reverse (and correct) the order */
  g_strreverse(string);

  /* add a space at the end (ok its at the start but it will be at the end)*/
  string[i++] = ' ';
  string[i] = '\0';
  return(string + i);
}


/******************************************************************************/
/* Dissect DMX data PDU                                                       */
#define BUFFER_SIZE 128
static guint32
dissect_acn_dmx_data_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, acn_pdu_offsets *last_pdu_offsets)
{
  /* common to all pdu */
  guint8 pdu_flags;
  guint32 pdu_start;
  guint32 pdu_length;
  guint32 pdu_flvh_length; /* flags, length, vector, header */
  acn_pdu_offsets pdu_offsets = {0,0,0,0,0};
  guint8 octet;
  guint32 length1;
  guint32 length2;
  guint32 length3;
  guint32 vector_offset;
  guint32 data_offset;
  guint32 end_offset;
  guint32 data_length;
  guint32 header_offset;
  guint32 total_cnt;
  guint32 item_cnt;


  proto_item *ti, *pi;
  proto_tree *pdu_tree = NULL;
  proto_tree *flag_tree = NULL;
/*  proto_tree *addr_tree = NULL; */

/* this pdu */
  acn_dmp_adt_type adt = {0,0,0,0,0,0};
  const gchar *name;
  guint32 vector;
  gchar buffer[BUFFER_SIZE];
  char *buf_ptr;
  guint32 x;
  guint8 level;
  guint8  min_char;
  guint8  base;
  gchar   leading_char;
  guint8  perline;
  guint8  halfline;
  guint16 dmx_count;
  guint16 dmx_start_code;

  buffer[0] = 0;

  /* save start of pdu block */
  pdu_start = offset;
  pdu_offsets.start = pdu_start;

  /* get PDU flags and length flag first */
  octet = tvb_get_guint8(tvb, offset++);
  pdu_flags =  octet & 0xf0;
  length1 = octet & 0x0f;                   /* bottom 4 bits only */
  length2 = tvb_get_guint8(tvb, offset++);

  /* if length flag is set, then we have a 20 bit length else we have a 12 bit */
  /* flvh = flags, length, vector, header */
  if (pdu_flags & ACN_PDU_FLAG_L) {
    length3 = tvb_get_guint8(tvb, offset);
    offset++;
    pdu_length = length3 | (length2 << 8) | (length1 << 16);
    pdu_flvh_length = 3;
  } else {
    pdu_length = length2 | (length1 << 8);
    pdu_flvh_length = 2;
  }
  /* offset should now be pointing to vector (if one exists) */

  /* Add pdu item and tree */
  ti = proto_tree_add_item(tree, hf_acn_pdu, tvb, pdu_start, pdu_length, FALSE);
  pdu_tree = proto_item_add_subtree(ti, ett_acn_dmx_data_pdu);

  /* Add flag item and tree */
  pi = proto_tree_add_uint(pdu_tree, hf_acn_pdu_flags, tvb, pdu_start, 1, pdu_flags);
  flag_tree = proto_item_add_subtree(pi, ett_acn_pdu_flags);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_l, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_v, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_h, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_d, tvb, pdu_start, 1, FALSE);

  /* Add PDU Length item */
  proto_tree_add_uint(pdu_tree, hf_acn_pdu_length, tvb, pdu_start, pdu_flvh_length, pdu_length);

  /* Set vector offset */
  if (pdu_flags & ACN_PDU_FLAG_V) {
    /* use new values */
    vector_offset = offset;
    last_pdu_offsets->vector = offset;
    offset += 1;
    pdu_flvh_length += 1;
  } else {
    /* use last values */
    vector_offset = last_pdu_offsets->vector;
  }
  /* offset should now be pointing to header (if one exists) */

  /* Add Vector item */
  vector = tvb_get_guint8(tvb, vector_offset);
  proto_tree_add_uint(pdu_tree, hf_acn_dmp_vector, tvb, vector_offset, 1, vector);

  /* Add Vector item to tree*/
  name = val_to_str(vector, acn_dmp_vector_vals, "not valid (%d)");
  proto_item_append_text(ti, ": ");
  proto_item_append_text(ti, "%s", name);

  /* Set header offset */
  if (pdu_flags & ACN_PDU_FLAG_H) {
    /* use new values */
    header_offset = offset;
    last_pdu_offsets->header = offset;
    offset++;
    pdu_flvh_length++;
  } else {
    /* use last values */
    header_offset = last_pdu_offsets->header;
  }
  /* offset should now be pointing to data (if one exists) */

  /* process based on vector */
  acn_add_dmp_address_type(tvb, pinfo, pdu_tree, header_offset, &adt);

  /* Adjust data */
  if (pdu_flags & ACN_PDU_FLAG_D) {
    /* use new values */
    data_offset = offset;
    data_length = pdu_length - pdu_flvh_length;
    last_pdu_offsets->data = offset;
    last_pdu_offsets->data_length = data_length;
  } else {
    /* use last values */
    data_offset = last_pdu_offsets->data;
    data_length = last_pdu_offsets->data_length;
  }
  end_offset = data_offset + data_length;

  switch (vector) {
    case ACN_DMP_VECTOR_SET_PROPERTY:
      dmx_start_code = tvb_get_ntohs(tvb, data_offset);
      proto_tree_add_item(pdu_tree, hf_acn_dmx_start_code, tvb, data_offset, 2, FALSE);
      data_offset += 2;
      proto_tree_add_item(pdu_tree, hf_acn_dmx_increment, tvb, data_offset, 2, FALSE);
      data_offset += 2;
      dmx_count = tvb_get_ntohs(tvb, data_offset);
      proto_tree_add_item(pdu_tree, hf_acn_dmx_count, tvb, data_offset, 2, FALSE);
      data_offset += 2;

      buf_ptr = buffer;

      switch (global_acn_dmx_display_line_format) {
        case ACN_PREF_DMX_DISPLAY_16PL:
          perline = 16;
          halfline = 8;
          break;
        default:
          perline = 20;
          halfline = 10;
      }

      /* values base on display mode */
      switch ((guint)global_acn_dmx_display_view) {
        case ACN_PREF_DMX_DISPLAY_HEX:
          min_char = 2;
          base = 16;
          break;
/*        case ACN_PREF_DMX_DISPLAY_PER: */
        default:
          min_char = 3;
          base = 10;
      }

      /* do we display leading zeros */
      if (global_acn_dmx_display_leading_zeros) {
        leading_char = '0';
      } else {
        leading_char = ' ';
      }

      /* add a snippet to info (this may be slow) */
      if(check_col(pinfo->cinfo,COL_INFO)){
        col_append_fstr(pinfo->cinfo,COL_INFO, ", Sc %02x, [%02x %02x %02x %02x %02x %02x...]",
          dmx_start_code,
          tvb_get_guint8(tvb, data_offset),
          tvb_get_guint8(tvb, data_offset+1),
          tvb_get_guint8(tvb, data_offset+2),
          tvb_get_guint8(tvb, data_offset+3),
          tvb_get_guint8(tvb, data_offset+4),
          tvb_get_guint8(tvb, data_offset+5));
      }

      /* add a header line */
      g_snprintf(buffer, BUFFER_SIZE, "%-10s: ", "Data...");

      buf_ptr += 9;
      for (x=1;x<=perline;x++) {
        buf_ptr = ltos((guint8)x, buf_ptr, 10, ' ', min_char, FALSE);
        if (x==halfline) {
          *buf_ptr++ =  '|';
          *buf_ptr++ =  ' ';
        }
      }
      *buf_ptr = '\0';
      proto_tree_add_text(pdu_tree, tvb, data_offset, dmx_count, "%s", buffer);

      /* start our line */
      g_snprintf(buffer, BUFFER_SIZE, "001-%03d: ", perline);
      buf_ptr = buffer + 9;

      total_cnt = 0;
      item_cnt = 0;
      for (x=data_offset; x < end_offset; x++) {
        level = tvb_get_guint8(tvb, x);
        if (global_acn_dmx_display_view==ACN_PREF_DMX_DISPLAY_PER) {
          if ((level > 0) && (level < 3)) {
            level = 1;
          } else {
            level = level * 100 / 255;
          }
        }
        buf_ptr = ltos(level, buf_ptr, base, leading_char, min_char, global_acn_dmx_display_zeros);
        total_cnt++;
        item_cnt++;

        if (item_cnt == perline || x == (end_offset-1)) {
          /* add leader... */
          proto_tree_add_text(pdu_tree, tvb, data_offset, item_cnt, "%s", buffer);
          data_offset += perline;
          g_snprintf(buffer, BUFFER_SIZE, "%03d-%03d: ",total_cnt, total_cnt+perline);
          buf_ptr = buffer + 9;
          item_cnt = 0;
        } else {
          /* add separater character */
          if (item_cnt == halfline) {
            *buf_ptr++ = '|';
            *buf_ptr++ = ' ';
            *buf_ptr = '\0';
          }
        }
      }
    /* NOTE:
     address data type                   (fixed at 0xA2)
     start code - 1 byte, reserved       (should be 0)
                - 1 byte, start code     (0x255)
                - 2 bytes, packet offset (should be 0000)
     address increment - 4 bytes         (ignore)
     number of dmx values - 4 bytes      (0-512)
     dmx values 0-512 bytes              (data)
     */

    break;
  }
  return pdu_start + pdu_length;
}



/******************************************************************************/
/* Dissect DMX Base PDU                                                       */
static guint32
dissect_acn_dmx_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, acn_pdu_offsets *last_pdu_offsets)
{
  /* common to all pdu */
  guint8 pdu_flags;
  guint32 pdu_start;
  guint32 pdu_length;
  guint32 pdu_flvh_length; /* flags, length, vector, header */
  acn_pdu_offsets pdu_offsets = {0,0,0,0,0};
  guint8 octet;
  guint32 length1;
  guint32 length2;
  guint32 length3;
  guint32 vector_offset;
  guint32 data_offset;
  guint32 end_offset;
  guint32 data_length;

  proto_item *ti, *pi;
  proto_tree *pdu_tree = NULL;
  proto_tree *flag_tree = NULL;

  const char *name;

/* this pdu */
  guint32 vector;

  guint32 universe;
  guint32 priority;
  guint32 sequence;

  /* save start of pdu block */
  pdu_start = offset;
  pdu_offsets.start = pdu_start;

  /* get PDU flags and length flag first */
  octet = tvb_get_guint8(tvb, offset++);
  pdu_flags =  octet & 0xf0;
  length1 = octet & 0x0f;                   /* bottom 4 bits only */
  length2 = tvb_get_guint8(tvb, offset++);

  /* if length flag is set, then we have a 20 bit length else we have a 12 bit */
  /* flvh = flags, length, vector, header */
  if (pdu_flags & ACN_PDU_FLAG_L) {
    length3 = tvb_get_guint8(tvb, offset);
    offset++;
    pdu_length = length3 | (length2 << 8) | (length1 << 16);
    pdu_flvh_length = 3;
  } else {
    pdu_length = length2 | (length1 << 8);
    pdu_flvh_length = 2;
  }

  /* offset should now be pointing to vector (if one exists) */

  /* Add pdu item and tree */
  ti = proto_tree_add_item(tree, hf_acn_pdu, tvb, pdu_start, pdu_length, FALSE);
  pdu_tree = proto_item_add_subtree(ti, ett_acn_dmx_pdu);

  /* Add flag item and tree */
  pi = proto_tree_add_uint(pdu_tree, hf_acn_pdu_flags, tvb, pdu_start, 1, pdu_flags);
  flag_tree = proto_item_add_subtree(pi, ett_acn_pdu_flags);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_l, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_v, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_h, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_d, tvb, pdu_start, 1, FALSE);

  /* Add PDU Length item */
  proto_tree_add_uint(pdu_tree, hf_acn_pdu_length, tvb, pdu_start, pdu_flvh_length, pdu_length);

  /* Set vector offset */
  if (pdu_flags & ACN_PDU_FLAG_V) {
    /* use new values */
    vector_offset = offset;
    last_pdu_offsets->vector = offset;
    offset += 4;
    pdu_flvh_length += 4;
  } else {
    /* use last values */
    vector_offset = last_pdu_offsets->vector;
  }
  /* offset should now be pointing to header (if one exists) */

  /* Add Vector item */
  vector = tvb_get_ntohl(tvb, vector_offset);
  proto_tree_add_item(pdu_tree, hf_acn_dmx_vector, tvb, vector_offset, 4, FALSE);
  /* vector_offset +=4; */

  /* Add Vector item to tree*/
  name = val_to_str(vector, acn_dmx_vector_vals, "not valid (%d)");
  proto_item_append_text(ti, ": %s", name);

  /* NO HEADER DATA ON THESE* (at least so far) */

  /* Adjust data */
  if (pdu_flags & ACN_PDU_FLAG_D) {
    /* use new values */
    data_offset = offset;
    data_length = pdu_length - pdu_flvh_length;
    last_pdu_offsets->data = offset;
    last_pdu_offsets->data_length = data_length;
  } else {
    /* use last values */
    data_offset = last_pdu_offsets->data;
    data_length = last_pdu_offsets->data_length;
  }
  end_offset = data_offset + data_length;

  /* process based on vector */
  switch (vector) {
  case 0x02:
    proto_tree_add_item(pdu_tree, hf_acn_dmx_source_name, tvb, data_offset, 32, FALSE);
    data_offset += 32;

    priority = tvb_get_guint8(tvb, data_offset);
    proto_tree_add_item(pdu_tree, hf_acn_dmx_priority, tvb, data_offset, 1, FALSE);
    data_offset += 1;

    sequence = tvb_get_guint8(tvb, data_offset);
    proto_tree_add_item(pdu_tree, hf_acn_dmx_sequence_number, tvb, data_offset, 1, FALSE);
    data_offset += 1;

    universe = tvb_get_ntohs(tvb, data_offset);
    proto_tree_add_item(pdu_tree, hf_acn_dmx_universe       , tvb, data_offset, 2, FALSE);
    data_offset += 2;

    /* add universe to info */
    if(check_col(pinfo->cinfo,COL_INFO)){
      col_append_fstr(pinfo->cinfo,COL_INFO, ", Universe %d, Seq %3d", universe, sequence );
    }

    proto_item_append_text(ti, ", Universe: %d, Priority: %d", universe, priority);

    data_offset = dissect_acn_dmx_data_pdu(tvb, pinfo, pdu_tree, data_offset, &pdu_offsets);

    break;
  }
  return pdu_start + pdu_length;
}

/******************************************************************************/
/* Dissect SDT Base PDU                                                       */
static guint32
dissect_acn_sdt_base_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, acn_pdu_offsets *last_pdu_offsets)
{
  /* common to all pdu */
  guint8 pdu_flags;
  guint32 pdu_start;
  guint32 pdu_length;
  guint32 pdu_flvh_length; /* flags, length, vector, header */
  acn_pdu_offsets pdu_offsets = {0,0,0,0,0};
  guint8 octet;
  guint32 length1;
  guint32 length2;
  guint32 length3;
  guint32 vector_offset;
  guint32 data_offset;
  guint32 end_offset;
  guint32 old_offset;
  guint32 data_length;

  proto_item *ti, *pi;
  proto_tree *pdu_tree = NULL;
  proto_tree *flag_tree = NULL;

  /* this pdu */
  const gchar *name;
  guint32 vector;
  guint32 member_id;

  /* save start of pdu block */
  pdu_start = offset;
  pdu_offsets.start = pdu_start;

  /* get PDU flags and length flag first */
  octet = tvb_get_guint8(tvb, offset++);
  pdu_flags =  octet & 0xf0;
  length1 = octet & 0x0f;                   /* bottom 4 bits only */
  length2 = tvb_get_guint8(tvb, offset++);

  /* if length flag is set, then we have a 20 bit length else we have a 12 bit */
  /* flvh = flags, length, vector, header */
  if (pdu_flags & ACN_PDU_FLAG_L) {
    length3 = tvb_get_guint8(tvb, offset);
    offset++;
    pdu_length = length3 | (length2 << 8) | (length1 << 16);
    pdu_flvh_length = 3;
  } else {
    pdu_length = length2 | (length1 << 8);
    pdu_flvh_length = 2;
  }
  /* offset should now be pointing to vector (if one exists) */

  /* Add pdu item and tree */
  ti = proto_tree_add_item(tree, hf_acn_pdu, tvb, pdu_start, pdu_length, FALSE);
  pdu_tree = proto_item_add_subtree(ti, ett_acn_sdt_base_pdu);

  /* Add flag item and tree */
  pi = proto_tree_add_uint(pdu_tree, hf_acn_pdu_flags, tvb, pdu_start, 1, pdu_flags);
  flag_tree = proto_item_add_subtree(pi, ett_acn_pdu_flags);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_l, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_v, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_h, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_d, tvb, pdu_start, 1, FALSE);

  /* Add PDU Length item */
  proto_tree_add_uint(pdu_tree, hf_acn_pdu_length, tvb, pdu_start, pdu_flvh_length, pdu_length);

  /* Set vector offset */
  if (pdu_flags & ACN_PDU_FLAG_V) {
    /* use new values */
    vector_offset = offset;
    last_pdu_offsets->vector = offset;
    offset++;
    pdu_flvh_length++;
  } else {
    /* use last values */
    vector_offset = last_pdu_offsets->vector;
  }
  /* offset should now be pointing to header (if one exists) */

  /* Add Vector item */
  vector = tvb_get_guint8(tvb, vector_offset);
  proto_tree_add_uint(pdu_tree, hf_acn_sdt_vector, tvb, vector_offset, 1, vector);

  /* Add Vector item to tree*/
  name = val_to_str(vector, acn_sdt_vector_vals, "not valid (%d)");
  proto_item_append_text(ti, ": ");
  proto_item_append_text(ti, "%s", name);

  /* NO HEADER DATA ON THESE* (at least so far) */

  /* Adjust data */
  if (pdu_flags & ACN_PDU_FLAG_D) {
    /* use new values */
    data_offset = offset;
    data_length = pdu_length - pdu_flvh_length;
    last_pdu_offsets->data = offset;
    last_pdu_offsets->data_length = data_length;
  } else {
    /* use last values */
    data_offset = last_pdu_offsets->data;
    data_length = last_pdu_offsets->data_length;
  }
  end_offset = data_offset + data_length;

  /* process based on vector */
  switch (vector) {
  case ACN_SDT_VECTOR_UNKNOWN:
    break;
  case ACN_SDT_VECTOR_REL_WRAP:
  case ACN_SDT_VECTOR_UNREL_WRAP:
    proto_tree_add_item(pdu_tree, hf_acn_channel_number, tvb, data_offset, 2, FALSE);
    data_offset += 2;
    proto_tree_add_item(pdu_tree, hf_acn_total_sequence_number, tvb, data_offset, 4, FALSE);
    data_offset += 4;
    proto_tree_add_item(pdu_tree, hf_acn_reliable_sequence_number, tvb, data_offset, 4, FALSE);
    data_offset += 4;
    proto_tree_add_item(pdu_tree, hf_acn_oldest_available_wrapper, tvb, data_offset, 4, FALSE);
    data_offset += 4;
    proto_tree_add_item(pdu_tree, hf_acn_first_memeber_to_ack, tvb, data_offset, 2, FALSE);
    data_offset += 2;
    proto_tree_add_item(pdu_tree, hf_acn_last_memeber_to_ack, tvb, data_offset, 2, FALSE);
    data_offset += 2;
    proto_tree_add_item(pdu_tree, hf_acn_mak_threshold, tvb, data_offset, 2, FALSE);
    data_offset += 2;

    while (data_offset < end_offset) {
      old_offset = data_offset;
      data_offset = dissect_acn_sdt_client_pdu(tvb, pinfo, pdu_tree, data_offset, &pdu_offsets);
      if (data_offset == old_offset) break;
    }
    break;
  case ACN_SDT_VECTOR_CHANNEL_PARAMS:
    break;
  case ACN_SDT_VECTOR_JOIN:
    proto_tree_add_item(pdu_tree, hf_acn_cid, tvb, data_offset, 16, FALSE);
    data_offset += 16;
    proto_tree_add_item(pdu_tree, hf_acn_member_id, tvb, data_offset, 2, FALSE);
    data_offset += 2;
    proto_tree_add_item(pdu_tree, hf_acn_channel_number, tvb, data_offset, 2, FALSE);
    data_offset += 2;
    proto_tree_add_item(pdu_tree, hf_acn_reciprocal_channel, tvb, data_offset, 2, FALSE);
    data_offset += 2;
    proto_tree_add_item(pdu_tree, hf_acn_total_sequence_number, tvb, data_offset, 4, FALSE);
    data_offset += 4;
    proto_tree_add_item(pdu_tree, hf_acn_reliable_sequence_number, tvb, data_offset, 4, FALSE);
    data_offset += 4;
    data_offset = acn_add_address(tvb, pinfo, pdu_tree, data_offset, "Destination Address:");
    data_offset = acn_add_channel_parameter(tvb, pinfo, pdu_tree, data_offset);
    data_offset = acn_add_expiry(tvb, pinfo, pdu_tree, data_offset, "Ad-hoc Expiry:");
    break;
  case ACN_SDT_VECTOR_JOIN_REFUSE:
    pi = proto_tree_add_item(pdu_tree, hf_acn_cid, tvb, data_offset, 16, FALSE);
    data_offset += 16;
    proto_item_append_text(pi, "(Leader)");
    proto_tree_add_item(pdu_tree, hf_acn_channel_number, tvb, data_offset, 2, FALSE);
    data_offset += 2;
    proto_tree_add_item(pdu_tree, hf_acn_member_id, tvb, data_offset, 2, FALSE);
    data_offset += 2;
    proto_tree_add_item(pdu_tree, hf_acn_reliable_sequence_number, tvb, data_offset, 4, FALSE);
    data_offset += 4;
    proto_tree_add_item(pdu_tree, hf_acn_refuse_code, tvb, data_offset, 1, FALSE);
    data_offset ++;
    break;
  case ACN_SDT_VECTOR_JOIN_ACCEPT:
    pi = proto_tree_add_item(pdu_tree, hf_acn_cid, tvb, data_offset, 16, FALSE);
    data_offset += 16;
    proto_item_append_text(pi, "(Leader)");
    proto_tree_add_item(pdu_tree, hf_acn_channel_number, tvb, data_offset, 2, FALSE);
    data_offset += 2;
    proto_tree_add_item(pdu_tree, hf_acn_member_id, tvb, data_offset, 2, FALSE);
    data_offset += 2;
    proto_tree_add_item(pdu_tree, hf_acn_reliable_sequence_number, tvb, data_offset, 4, FALSE);
    data_offset += 4;
    proto_tree_add_item(pdu_tree, hf_acn_reciprocal_channel, tvb, data_offset, 2, FALSE);
    data_offset += 2;
    break;
  case ACN_SDT_VECTOR_LEAVE:
    break;
  case ACN_SDT_VECTOR_LEAVING:
    pi = proto_tree_add_item(pdu_tree, hf_acn_cid, tvb, data_offset, 16, FALSE);
    data_offset += 16;
    proto_item_append_text(pi, "(Leader)");
    proto_tree_add_item(pdu_tree, hf_acn_channel_number, tvb, data_offset, 2, FALSE);
    data_offset += 2;
    proto_tree_add_item(pdu_tree, hf_acn_member_id, tvb, data_offset, 2, FALSE);
    data_offset += 2;
    proto_tree_add_item(pdu_tree, hf_acn_reliable_sequence_number, tvb, data_offset, 4, FALSE);
    data_offset += 4;
    proto_tree_add_item(pdu_tree, hf_acn_reason_code, tvb, data_offset, 1, FALSE);
    offset++;
    break;
  case ACN_SDT_VECTOR_CONNECT:
    break;
  case ACN_SDT_VECTOR_CONNECT_ACCEPT:
    break;
  case ACN_SDT_VECTOR_CONNECT_REFUSE:
    break;
  case ACN_SDT_VECTOR_DISCONNECT:
    break;
  case ACN_SDT_VECTOR_DISCONNECTING:
    break;
  case ACN_SDT_VECTOR_ACK:
    break;
  case ACN_SDT_VECTOR_NAK:
    pi = proto_tree_add_item(pdu_tree, hf_acn_cid, tvb, data_offset, 16, FALSE);
    data_offset += 16;
    proto_item_append_text(pi, "(Leader)");
    proto_tree_add_item(pdu_tree, hf_acn_channel_number, tvb, data_offset, 2, FALSE);
    data_offset += 2;
    proto_tree_add_item(pdu_tree, hf_acn_member_id, tvb, data_offset, 2, FALSE);
    data_offset += 2;
    proto_tree_add_item(pdu_tree, hf_acn_reliable_sequence_number, tvb, data_offset, 4, FALSE);
    data_offset += 4;
    proto_tree_add_item(pdu_tree, hf_acn_first_missed_sequence, tvb, data_offset, 4, FALSE);
    data_offset += 4;
    proto_tree_add_item(pdu_tree, hf_acn_last_missed_sequence, tvb, data_offset, 4, FALSE);
    data_offset += 4;
    break;
  case ACN_SDT_VECTOR_GET_SESSION:
    proto_tree_add_item(pdu_tree, hf_acn_cid, tvb, data_offset, 16, FALSE);
    data_offset += 16;
    break;
  case ACN_SDT_VECTOR_SESSIONS:
    member_id = tvb_get_ntohs(tvb, data_offset);
    switch (member_id) {
    case 0:
      data_offset = acn_add_channel_owner_info_block(tvb, pinfo, pdu_tree, data_offset);
      break;
    case 1:
      data_offset = acn_add_channel_member_info_block(tvb, pinfo, pdu_tree, data_offset);
      break;
    }
    break;
  }

  return pdu_start + pdu_length;
}

/******************************************************************************/
/* Dissect Root PDU                                                           */
static guint32
dissect_acn_root_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, acn_pdu_offsets *last_pdu_offsets)
{
  /* common to all pdu */
  guint8 pdu_flags;
  guint32 pdu_start;
  guint32 pdu_length;
  guint32 pdu_flvh_length; /* flags, length, vector, header */
  acn_pdu_offsets pdu_offsets = {0,0,0,0,0};
  guint8 octet;
  guint32 length1;
  guint32 length2;
  guint32 length3;
  guint32 vector_offset;
  guint32 header_offset;
  guint32 data_offset;
  guint32 end_offset;
  guint32 old_offset;
  guint32 data_length;

  proto_item *ti, *pi;
  proto_tree *pdu_tree = NULL;
  proto_tree *flag_tree = NULL;

  /* this pdu */
  guint32 protocol_id;
  e_guid_t guid;

  /* save start of pdu block */
  pdu_start = offset;
  pdu_offsets.start = pdu_start;

  /* get PDU flags and length flag first */
  octet = tvb_get_guint8(tvb, offset++);
  pdu_flags =  octet & 0xf0;
  length1 = octet & 0x0f;                   /* bottom 4 bits only */
  length2 = tvb_get_guint8(tvb, offset++);

  /* if length flag is set, then we have a 20 bit length else we have a 12 bit */
  /* flvh = flags, length, vector, header */
  if (pdu_flags & ACN_PDU_FLAG_L) {
    length3 = tvb_get_guint8(tvb, offset);
    offset++;
    pdu_length = length3 | (length2 << 8) | (length1 << 16);
    pdu_flvh_length = 3;
  } else {
    pdu_length = length2 | (length1 << 8);
    pdu_flvh_length = 2;
  }
  /* offset should now be pointing to vector (if one exists) */

  /* Add pdu item and tree */
  ti = proto_tree_add_item(tree, hf_acn_pdu, tvb, pdu_start, pdu_length, FALSE);
  pdu_tree = proto_item_add_subtree(ti, ett_acn_root_pdu);

  /* Add flag item and tree */
  pi = proto_tree_add_uint(pdu_tree, hf_acn_pdu_flags, tvb, pdu_start, 1, pdu_flags);
  flag_tree = proto_item_add_subtree(pi, ett_acn_pdu_flags);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_l, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_v, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_h, tvb, pdu_start, 1, FALSE);
  proto_tree_add_item(flag_tree, hf_acn_pdu_flag_d, tvb, pdu_start, 1, FALSE);

  /* Add PDU Length item */
  proto_tree_add_uint(pdu_tree, hf_acn_pdu_length, tvb, pdu_start, pdu_flvh_length, pdu_length);

  /* Set vector offset */
  if (pdu_flags & ACN_PDU_FLAG_V) {
    /* use new values */
    vector_offset = offset;
    last_pdu_offsets->vector = offset;
    offset += 4;
    pdu_flvh_length += 4;
  } else {
    /* use last values */
    vector_offset = last_pdu_offsets->vector;
  }
  /* offset should now be pointing to header (if one exists) */



  /* Get Protocol ID (vector) */
  protocol_id = tvb_get_ntohl(tvb, vector_offset);
  proto_tree_add_uint(pdu_tree, hf_acn_protocol_id, tvb, vector_offset, 4, protocol_id);

  /* process based on protocol_id */
  switch (protocol_id) {
  case ACN_PROTOCOL_ID_DMX:
    if (global_acn_dmx_enable) {
      proto_item_append_text(ti,": Root DMX");

      /* Set header offset */
      if (pdu_flags & ACN_PDU_FLAG_H) {
        /* use new values */
        header_offset = offset;
        last_pdu_offsets->header = offset;
        offset += 16;
        pdu_flvh_length += 16;
      } else {
        /* use last values */
        header_offset = last_pdu_offsets->header;
      }
      /* offset should now be pointing to data (if one exists) */

      /* get Header (CID) 16 bytes */
      tvb_get_guid(tvb, header_offset, &guid, FALSE);
      proto_item_append_text(ti, ", Src: %s", guid_to_str(&guid));

      /* add cid to info */
      if(check_col(pinfo->cinfo,COL_INFO)){
        col_clear(pinfo->cinfo,COL_INFO);
        col_add_fstr(pinfo->cinfo,COL_INFO, "CID %s", guid_to_str(&guid));
      }

      proto_tree_add_item(pdu_tree, hf_acn_cid, tvb, header_offset, 16, FALSE);
      header_offset += 16;

      /* Adjust data */
      if (pdu_flags & ACN_PDU_FLAG_D) {
        /* use new values */
        data_offset = offset;
        data_length = pdu_length - pdu_flvh_length;
        last_pdu_offsets->data = offset;
        last_pdu_offsets->data_length = data_length;
      } else {
        /* use last values */
        data_offset = last_pdu_offsets->data;
        data_length = last_pdu_offsets->data_length;
      }
      end_offset = data_offset + data_length;

      /* adjust for what we used */
      while (data_offset < end_offset) {
        old_offset = data_offset;
        data_offset = dissect_acn_dmx_pdu(tvb, pinfo, pdu_tree, data_offset, &pdu_offsets);
        if (data_offset == old_offset) break;
      }
    }
    break;
  case ACN_PROTOCOL_ID_SDT:
    /* Adjust header */
    proto_item_append_text(ti,": Root SDT");

    /* Set header offset */
    if (pdu_flags & ACN_PDU_FLAG_H) {
      /* use new values */
      header_offset = offset;
      last_pdu_offsets->header = offset;
      offset += 16;
      pdu_flvh_length += 16;
    } else {
      /* use last values */
      header_offset = last_pdu_offsets->header;
    }
    /* offset should now be pointing to data (if one exists) */

    /* get Header (CID) 16 bytes */
    tvb_get_guid(tvb, header_offset, &guid, FALSE);
    proto_item_append_text(ti, ", Src: %s", guid_to_str(&guid));

    proto_tree_add_item(pdu_tree, hf_acn_cid, tvb, header_offset, 16, FALSE);
    header_offset += 16;

    /* Adjust data */
    if (pdu_flags & ACN_PDU_FLAG_D) {
      /* use new values */
      data_offset = offset;
      data_length = pdu_length - pdu_flvh_length;
      last_pdu_offsets->data = offset;
      last_pdu_offsets->data_length = data_length;
    } else {
      /* use last values */
      data_offset = last_pdu_offsets->data;
      data_length = last_pdu_offsets->data_length;
    }
    end_offset = data_offset + data_length;

    /* adjust for what we used */
    while (data_offset < end_offset) {
      old_offset = data_offset;
      data_offset = dissect_acn_sdt_base_pdu(tvb, pinfo, pdu_tree, data_offset, &pdu_offsets);
      if (data_offset == old_offset) break;
    }
    break;
  }

  return pdu_start + pdu_length;
}

/******************************************************************************/
/* Dissect ACN                                                                */
static int
dissect_acn(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
  proto_item *ti = NULL;
  proto_tree *acn_tree = NULL;
  guint32 data_offset = 0;
  guint32 old_offset;
  guint32 end_offset;
  acn_pdu_offsets pdu_offsets = {0,0,0,0,0};

/*   if (!is_acn(tvb)) { */
/*     return 0;         */
/*   }                   */

  /* Set the protocol column */
  if (check_col(pinfo->cinfo, COL_PROTOCOL)) {
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "ACN");
  }

  /* Clear out stuff in the info column */
  if(check_col(pinfo->cinfo,COL_INFO)){
  /* col_clear(pinfo->cinfo,COL_INFO); */
    col_add_fstr(pinfo->cinfo,COL_INFO, "ACN [Src Port: %d, Dst Port: %d]", pinfo->srcport, pinfo->destport );
  }

  if (tree) { /* we are being asked for details */
    ti = proto_tree_add_item(tree, proto_acn, tvb, 0, -1, FALSE);
    acn_tree = proto_item_add_subtree(ti, ett_acn);

    pdu_offsets.start = data_offset;

    /* add preamble, postamble and ACN Packet ID */
    proto_tree_add_item(acn_tree, hf_acn_preamble_size, tvb, data_offset, 2, FALSE);
    data_offset += 2;
    proto_tree_add_item(acn_tree, hf_acn_postamble_size, tvb, data_offset, 2, FALSE);
    data_offset += 2;
    proto_tree_add_item(acn_tree, hf_acn_packet_identifier, tvb, data_offset, 12, FALSE);
    data_offset += 12;

    /* one past the last byte */
    end_offset = data_offset + tvb_reported_length_remaining(tvb, data_offset);
    while (data_offset < end_offset) {
      old_offset = data_offset;
      data_offset = dissect_acn_root_pdu(tvb, pinfo, acn_tree, data_offset, &pdu_offsets);
      if (data_offset == old_offset) break;
    }
  }
  return tvb_length(tvb);
}

/******************************************************************************/
/* Register protocol                                                          */
void proto_register_acn(void)
{
  static hf_register_info hf[] = {
    /**************************************************************************/
    /* In alphabetical order */
    /* Address Type */
    /* PDU flags*/
    { &hf_acn_ip_address_type,
      { "Addr Type", "acn.ip_address_type",
        FT_UINT8, BASE_DEC, VALS(acn_ip_address_type_vals), 0x0,
        NULL, HFILL }
    },
    /* Association */
    { &hf_acn_association,
      { "Association", "acn.association",
        FT_UINT16, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    /* Channel Number */
    { &hf_acn_channel_number,
      { "Channel Number", "acn.channel_number",
        FT_UINT16, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    /* CID */
    { &hf_acn_cid,
      { "CID", "acn.cid",
        FT_GUID, BASE_NONE, NULL, 0x0,
        NULL, HFILL }
    },
    /* Client Protocol ID */
    { &hf_acn_client_protocol_id,
      { "Client Protocol ID", "acn.client_protocol_id",
        FT_UINT32, BASE_DEC, VALS(acn_protocol_id_vals), 0x0,
        NULL, HFILL }
    },
    /* DMP data */
    { &hf_acn_data,
      { "Data", "acn.dmp_data",
        FT_BYTES, BASE_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    { &hf_acn_data8,
      { "Addr", "acn.dmp_data8",
        FT_UINT8, BASE_DEC_HEX, NULL, 0x0,
        "Data8", HFILL }
    },
    { &hf_acn_data16,
      { "Addr", "acn.dmp_data16",
        FT_UINT16, BASE_DEC_HEX, NULL, 0x0,
        "Data16", HFILL }
    },
    { &hf_acn_data24,
      { "Addr", "acn.dmp_data24",
        FT_UINT24, BASE_DEC_HEX, NULL, 0x0,
        "Data24", HFILL }
    },
    { &hf_acn_data32,
      { "Addr", "acn.dmp_data32",
        FT_UINT32, BASE_DEC_HEX, NULL, 0x0,
        "Data32", HFILL }
    },

    { &hf_acn_dmp_address_data_pairs,
      { "Address-Data Pairs", "acn.dmp_address_data_pairs",
        FT_BYTES, BASE_DEC, NULL, 0x0,
        "More address-data pairs", HFILL }
    },

    /* DMP Address */
    { &hf_acn_dmp_address1,
      { "Address", "acn.dmp_address",
        FT_UINT8, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    { &hf_acn_dmp_address2,
      { "Address", "acn.dmp_address",
        FT_UINT16, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    { &hf_acn_dmp_address4,
      { "Address", "acn.dmp_address",
        FT_UINT32, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },

    /* DMP Address type*/
    { &hf_acn_dmp_adt,
      { "Address and Data Type", "acn.dmp_adt",
        FT_UINT8, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    { &hf_acn_dmp_adt_a,
      { "Size", "acn.dmp_adt_a",
        FT_UINT8, BASE_DEC, VALS(acn_dmp_adt_a_vals), 0x03,
        NULL, HFILL }
    },
    { &hf_acn_dmp_adt_d,
      { "Data Type", "acn.dmp_adt_d",
        FT_UINT8, BASE_DEC, VALS(acn_dmp_adt_d_vals), 0x30,
        NULL, HFILL }
    },
    { &hf_acn_dmp_adt_r,
      { "Relative", "acn.dmp_adt_r",
        FT_UINT8, BASE_DEC, VALS(acn_dmp_adt_r_vals), 0x40,
        NULL, HFILL }
    },
    { &hf_acn_dmp_adt_v,
      { "Virtual", "acn.dmp_adt_v",
        FT_UINT8, BASE_DEC, VALS(acn_dmp_adt_v_vals), 0x80,
        NULL, HFILL }
    },
    { &hf_acn_dmp_adt_x,
      { "Reserved", "acn.dmp_adt_x",
        FT_UINT8, BASE_DEC, NULL, 0x0c,
        NULL, HFILL }
    },

    /* DMP Reason Code */
    { &hf_acn_dmp_reason_code,
      { "Reason Code", "acn.dmp_reason_code",
        FT_UINT8, BASE_DEC, VALS(acn_dmp_reason_code_vals), 0x0,
        NULL, HFILL }
    },

    /* DMP Vector */
    { &hf_acn_dmp_vector,
      { "DMP Vector", "acn.dmp_vector",
        FT_UINT8, BASE_DEC, VALS(acn_dmp_vector_vals), 0x0,
        NULL, HFILL }
    },
    /* Expiry */
    { &hf_acn_expiry,
      { "Expiry", "acn.expiry",
        FT_UINT16, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    /* First Member to ACK */
    { &hf_acn_first_memeber_to_ack,
      { "First Member to ACK", "acn.first_member_to_ack",
        FT_UINT16, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    /* First Missed Sequence */
    { &hf_acn_first_missed_sequence,
      { "First Missed Sequence", "acn.first_missed_sequence",
        FT_UINT32, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    /* IPV4 */
    { &hf_acn_ipv4,
      { "IPV4", "acn.ipv4",
        FT_IPv4, BASE_NONE, NULL, 0x0,
        NULL, HFILL }
    },
    /* IPV6 */
    { &hf_acn_ipv6,
      { "IPV6", "acn.ipv6",
        FT_IPv6, BASE_NONE, NULL, 0x0,
        NULL, HFILL }
    },
    /* Last Member to ACK */
    { &hf_acn_last_memeber_to_ack,
      { "Last Member to ACK", "acn.last_member_to_ack",
        FT_UINT16, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    /* Last Missed Sequence */
    { &hf_acn_last_missed_sequence,
      { "Last Missed Sequence", "acn.last_missed_sequence",
        FT_UINT32, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    /* MAK threshold */
    { &hf_acn_mak_threshold,
      { "MAK Threshold", "acn.mak_threshold",
        FT_UINT16, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    /* MemberID */
    { &hf_acn_member_id,
      { "Member ID", "acn.member_id",
        FT_UINT16, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    /* NAK Holdoff */
    { &hf_acn_nak_holdoff,
      { "NAK holdoff (ms)", "acn.nak_holdoff",
        FT_UINT16, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    /* NAK Max Wait */
    { &hf_acn_nak_max_wait,
      { "NAK Max Wait (ms)", "acn.nak_max_wait",
        FT_UINT16, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    /* NAK Modulus */
    { &hf_acn_nak_modulus,
      { "NAK Modulus", "acn.nak_modulus",
        FT_UINT16, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    /* NAK Outbound Flag */
    { &hf_acn_nak_outbound_flag,
      { "NAK Outbound Flag", "acn.nak_outbound_flag",
        FT_BOOLEAN, 8, NULL, 0x80,
        NULL, HFILL }
    },
    /* Oldest Available Wrapper */
    { &hf_acn_oldest_available_wrapper,
      { "Oldest Available Wrapper", "acn.oldest_available_wrapper",
        FT_UINT32, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    /* Preamble Sizet */
    { &hf_acn_preamble_size,
      { "Size of preamble", "acn.preamble_size",
        FT_UINT16, BASE_DEC, NULL, 0x0,
        "Preamble size in bytes", HFILL }
    },
    /* Packet Identifier */
    { &hf_acn_packet_identifier,
      { "Packet Identifier", "acn.packet_identifier",
        FT_STRING, BASE_NONE, NULL, 0x0,
        NULL, HFILL }
    },
    /* PDU */
    { &hf_acn_pdu,
      { "PDU", "acn.pdu",
        FT_NONE, BASE_NONE, NULL, 0x0,
        NULL, HFILL }
    },
    /* PDU flags*/
    { &hf_acn_pdu_flags,
      { "Flags", "acn.pdu.flags",
        FT_UINT8, BASE_HEX, NULL, 0x0,
        "PDU Flags", HFILL }
    },
    { &hf_acn_pdu_flag_d,
      { "Data", "acn.pdu.flag_d",
        FT_BOOLEAN, 8, NULL, ACN_PDU_FLAG_D,
        "Data flag", HFILL }
    },
    { &hf_acn_pdu_flag_h,
      { "Header", "acn.pdu.flag_h",
        FT_BOOLEAN, 8, NULL, ACN_PDU_FLAG_H,
        "Header flag", HFILL }
    },
    { &hf_acn_pdu_flag_l,
      { "Length", "acn.pdu.flag_l",
        FT_BOOLEAN, 8, NULL, ACN_PDU_FLAG_L,
        "Length flag", HFILL }
    },
    { &hf_acn_pdu_flag_v,
      { "Vector", "acn.pdu.flag_v",
        FT_BOOLEAN, 8, NULL, ACN_PDU_FLAG_V,
        "Vector flag", HFILL }
    },
    /* PDU Length */
    { &hf_acn_pdu_length,
      { "Length", "acn.pdu.flag_d",
        FT_UINT32, BASE_DEC, NULL, 0x0,
        "PDU Length", HFILL }
    },
    /* Port */
    { &hf_acn_port,
      { "Port", "acn.port",
        FT_UINT16, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    /* Postamble Size */
    { &hf_acn_postamble_size,
      { "Size of postamble", "acn.postamble_size",
        FT_UINT16, BASE_DEC, NULL, 0x0,
        "Postamble size in bytes", HFILL }
    },
    /* Protocol ID */
    { &hf_acn_protocol_id,
      { "Protocol ID", "acn.protocol_id",
        FT_UINT32, BASE_DEC, VALS(acn_protocol_id_vals), 0x0,
        NULL, HFILL }
    },
    /* Reason Code */
    { &hf_acn_reason_code,
      { "Reason Code", "acn.reason_code",
        FT_UINT8, BASE_DEC, VALS(acn_reason_code_vals), 0x0,
        NULL, HFILL }
    },
    /* Reciprocal Channel */
    { &hf_acn_reciprocal_channel,
      { "Reciprocal Channel Number", "acn.acn_reciprocal_channel",
        FT_UINT16, BASE_DEC_HEX, NULL, 0x0,
        "Reciprocal Channel", HFILL }
    },
    /* Refuse Code */
    { &hf_acn_refuse_code,
      { "Refuse Code", "acn.acn_refuse_code",
        FT_UINT8, BASE_DEC, VALS(acn_refuse_code_vals), 0x0,
        NULL, HFILL }
    },
    /* Reliable Sequence Number */
    { &hf_acn_reliable_sequence_number,
      { "Reliable Sequence Number", "acn.reliable_sequence_number",
        FT_UINT32, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    /* SDT Vector */
    { &hf_acn_sdt_vector,
      { "STD Vector", "acn.sdt_vector",
        FT_UINT8, BASE_DEC, VALS(acn_sdt_vector_vals), 0x0,
        NULL, HFILL }
    },

    /* DMX Vector */
    { &hf_acn_dmx_vector,
      { "Vector", "acn.dmx_vector",
        FT_UINT32, BASE_DEC, VALS(acn_dmx_vector_vals), 0x0,
        "DMX Vector", HFILL }
    },
    /* DMX Source Name */
    { &hf_acn_dmx_source_name,
      { "Source", "acn.dmx.source_name",
        FT_STRING, BASE_NONE, NULL, 0x0,
        "DMX Source Name", HFILL }
    },

    /* DMX priority */
    { &hf_acn_dmx_priority,
      { "Priority", "acn.dmx.priority",
        FT_UINT8, BASE_DEC, NULL, 0x0,
        "DMX Priority", HFILL }
    },
    /* DMX Sequence number */
    { &hf_acn_dmx_sequence_number,
      { "Seq No", "acn.dmx.seq_number",
        FT_UINT8, BASE_DEC, NULL, 0x0,
        "DMX Sequence Number", HFILL }
    },
    /* DMX Universe */
    { &hf_acn_dmx_universe,
      { "Universe", "acn.dmx.universe",
        FT_UINT16, BASE_DEC, NULL, 0x0,
        "DMX Universe", HFILL }
    },

    /* DMX Start Code */
    { &hf_acn_dmx_start_code,
      { "Start Code", "acn.dmx.start_code",
        FT_UINT16, BASE_DEC_HEX, NULL, 0x0,
        "DMX Start Code", HFILL }
    },

    /* DMX Address Increment */
    { &hf_acn_dmx_increment,
      { "Increment", "acn.dmx.increment",
        FT_UINT16, BASE_DEC, NULL, 0x0,
        "DMX Increment", HFILL }
    },

    /* DMX Packet Count */
    { &hf_acn_dmx_count,
      { "Count", "acn.dmx.count",
        FT_UINT16, BASE_DEC, NULL, 0x0,
        "DMX Count", HFILL }
    },

    /* Session Count */
    { &hf_acn_session_count,
      { "Session Count", "acn.session_count",
        FT_UINT16, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    },
    /* Total Sequence Number */
    { &hf_acn_total_sequence_number,
      { "Total Sequence Number", "acn.total_sequence_number",
        FT_UINT32, BASE_DEC_HEX, NULL, 0x0,
        NULL, HFILL }
    }
  };

  /* Setup protocol subtree array */
  static gint *ett[] = {
    &ett_acn,
    &ett_acn_channel_owner_info_block,
    &ett_acn_channel_member_info_block,
    &ett_acn_channel_parameter,
    &ett_acn_address,
    &ett_acn_address_type,
    &ett_acn_pdu_flags,
    &ett_acn_dmp_pdu,
    &ett_acn_sdt_pdu,
    &ett_acn_sdt_client_pdu,
    &ett_acn_sdt_base_pdu,
    &ett_acn_root_pdu,
    &ett_acn_dmx_address,
    &ett_acn_dmx_data_pdu,
    &ett_acn_dmx_pdu
  };

  module_t *acn_module;
  proto_acn = proto_register_protocol (
    "Architecture for Control Networks", /* name */
    "ACN",                               /* short name */
    "acn"                                /* abbrev */
    );

  proto_register_field_array(proto_acn, hf, array_length(hf));
  proto_register_subtree_array(ett, array_length(ett));

  acn_module = prefs_register_protocol(proto_acn, NULL);
  prefs_register_bool_preference(acn_module, "heuristic_acn",
                                 "Decode ACN",
                                 "Enable Architecture for Control Networks dissector (ANSI BSR E1.17)",
                                 &global_acn_heur);

  prefs_register_bool_preference(acn_module, "dmx_enable",
                                 "Streaming DMX",
                                 "Enable Streaming DMX extension dissector (ANSI BSR E1.31)",
                                 &global_acn_dmx_enable);

  prefs_register_enum_preference(acn_module, "dmx_display_view",
                                 "DMX, display format",
                                 "Display format",
                                 &global_acn_dmx_display_view,
                                 dmx_display_view,
                                 TRUE);

  prefs_register_bool_preference(acn_module, "dmx_display_zeros",
                                 "DMX, display zeros",
                                 "Display zeros instead of dots",
                                 &global_acn_dmx_display_zeros);

  prefs_register_bool_preference(acn_module, "dmx_display_leading_zeros",
                                 "DMX, display leading zeros",
                                 "Display leading zeros on levels",
                                 &global_acn_dmx_display_leading_zeros);

  prefs_register_enum_preference(acn_module, "dmx_display_line_format",
                                 "DMX, display line format",
                                 "Display line format",
                                 &global_acn_dmx_display_line_format,
                                 dmx_display_line_format,
                                 TRUE);
}


/******************************************************************************/
/* Register handoff                                                           */
void
proto_reg_handoff_acn(void)
{
  /* dissector_handle_t acn_handle; */
  /* acn_handle = new_create_dissector_handle(dissect_acn, proto_acn); */
  /* dissector_add_handle("udp.port", acn_handle);                         */
  heur_dissector_add("udp", dissect_acn_heur, proto_acn);
}

