/* packet-atalk.c
 * Routines for AppleTalk packet disassembly: LLAP, DDP, NBP, ATP, ASP,
 * RTMP, PAP.
 *
 * $Id: packet-atalk.c 18196 2006-05-21 04:49:01Z sahlberg $
 *
 * Simon Wilkinson <sxw@dcs.ed.ac.uk>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

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

#include <glib.h>
#include <epan/packet.h>
#include <string.h>

#include <epan/etypes.h>
#include <epan/ppptypes.h>
#include <epan/aftypes.h>
#include <epan/arcnet_pids.h>
#include <epan/atalk-utils.h>
#include <epan/conversation.h>

#include <epan/prefs.h>
#include <epan/reassemble.h>
#include <epan/emem.h>

#include "packet-atalk.h"
#include "packet-afp.h"

/* Tables for reassembly of fragments. */
static GHashTable *atp_fragment_table = NULL;
static GHashTable *atp_reassembled_table = NULL;

/* desegmentation of ATP */
static gboolean atp_defragment = TRUE;

static dissector_handle_t afp_handle;

static int proto_llap = -1;
static int hf_llap_dst = -1;
static int hf_llap_src = -1;
static int hf_llap_type = -1;

static int proto_ddp = -1;
static int hf_ddp_hopcount = -1;
static int hf_ddp_len = -1;
static int hf_ddp_checksum = -1;
static int hf_ddp_dst = -1;
static int hf_ddp_dst_net = -1;
static int hf_ddp_src = -1;
static int hf_ddp_src_net = -1;
static int hf_ddp_dst_node = -1;
static int hf_ddp_src_node = -1;
static int hf_ddp_dst_socket = -1;
static int hf_ddp_src_socket = -1;
static int hf_ddp_type = -1;

static dissector_handle_t ddp_handle;

/* --------------------------------------
 * ATP protocol parameters
 * from netatalk/include/atalk/atp.h
 */
#define ATP_MAXDATA     (578+4)         /* maximum ATP data size */
#define ATP_BUFSIZ      587             /* maximum packet size */
#define ATP_HDRSIZE     5               /* includes DDP type field */

#define ATP_TRELMASK    0x07            /* mask all but TREL */
#define ATP_RELTIME     30              /* base release timer (in secs) */

#define ATP_TREL30      0x0             /* release time codes */
#define ATP_TREL1M      0x1             /* these are passed in flags of */
#define ATP_TREL2M      0x2             /* atp_sreq call, and set in the */
#define ATP_TREL4M      0x3             /* packet control info. */
#define ATP_TREL8M      0x4

/* flags for ATP options (and control byte)
*/
#define ATP_XO          0x20 /* (1<<5)          eXactly Once mode */
#define ATP_EOM         0x10 /* (1<<4)          End Of Message */
#define ATP_STS         0x08 /* (1<<3)          Transaction Status */

/* function codes
*/
#define ATP_FUNCMASK    (3<<6)          /* mask all but function */

#define ATP_TREQ        1 /* (1<<6)        Trans. REQuest */
#define ATP_TRESP       2 /* (2<<6)        Trans. RESPonse */
#define ATP_TREL        3 /* (3<<6)        Trans. RELease */

/* ------------------------- */
static dissector_handle_t asp_handle;
static dissector_handle_t pap_handle;

static int proto_atp = -1;
static int hf_atp_ctrlinfo  = -1; /* guint8_t    control information */
static int hf_atp_function  = -1; /* bits 7,6    function */
static int hf_atp_xo        = -1; /* bit 5       exactly-once */
static int hf_atp_eom       = -1; /* bit 4       end-of-message */
static int hf_atp_sts       = -1; /* bit 3       send transaction status */
static int hf_atp_treltimer = -1; /* bits 2,1,0  TRel timeout indicator */

static int hf_atp_bitmap = -1;   /* guint8_t  bitmap or sequence number */
static int hf_atp_tid = -1;      /* guint16_t transaction id. */
static int hf_atp_user_bytes = -1;

static int hf_atp_segments = -1;
static int hf_atp_segment = -1;
static int hf_atp_segment_overlap = -1;
static int hf_atp_segment_overlap_conflict = -1;
static int hf_atp_segment_multiple_tails = -1;
static int hf_atp_segment_too_long_segment = -1;
static int hf_atp_segment_error = -1;
static int hf_atp_reassembled_in = -1;

/* ------------------------- */
static int proto_zip = -1;
static dissector_handle_t zip_atp_handle;

static int hf_zip_function = -1;
static int hf_zip_atp_function = -1;
static int hf_zip_start_index = -1;
static int hf_zip_count = -1;
static int hf_zip_zero_value	= -1;

static int hf_zip_network_count = -1;
static int hf_zip_network = -1;
static int hf_zip_network_start = -1;
static int hf_zip_network_end = -1;

static int hf_zip_flags = -1;

static int hf_zip_flags_zone_invalid  = -1;
static int hf_zip_flags_use_broadcast = -1;
static int hf_zip_flags_only_one_zone = -1;

static int hf_zip_last_flag = -1;

static int hf_zip_zone_name    = -1;
static int hf_zip_default_zone = -1;

static int hf_zip_multicast_length  = -1;
static int hf_zip_multicast_address = -1;

static const value_string zip_function_vals[] = {
  {1, "Query"},
  {2, "Reply"},
  {5, "GetNetInfo request"},
  {6, "GetNetInfo reply"},
  {7, "notify"},
  {8, "Extended reply"},
  {0, NULL}
};

static const value_string zip_atp_function_vals[] = {
  {7, "GetMyZone"},
  {8, "GetZoneList"},
  {9, "GetLocalZones"},
  {0, NULL}
};

static gint ett_zip              = -1;
static gint ett_zip_flags        = -1;
static gint ett_zip_zones_list   = -1;
static gint ett_zip_network_list = -1;

/* --------------------------------
 * from netatalk/include/atalk/ats.h
 */

#define ASPFUNC_CLOSE   1
#define ASPFUNC_CMD     2
#define ASPFUNC_STAT    3
#define ASPFUNC_OPEN    4
#define ASPFUNC_TICKLE  5
#define ASPFUNC_WRITE   6
#define ASPFUNC_WRTCONT 7
#define ASPFUNC_ATTN    8

#define ASP_HDRSIZ      4
#define ASPERR_OK       0x0000
#define ASPERR_BADVERS  0xfbd6
#define ASPERR_BUFSMALL 0xfbd5
#define ASPERR_NOSESS   0xfbd4
#define ASPERR_NOSERV   0xfbd3
#define ASPERR_PARM     0xfbd2
#define ASPERR_SERVBUSY 0xfbd1
#define ASPERR_SESSCLOS 0xfbd0
#define ASPERR_SIZERR   0xfbcf
#define ASPERR_TOOMANY  0xfbce
#define ASPERR_NOACK    0xfbcd

static int proto_asp = -1;
static int hf_asp_func = -1;
static int hf_asp_error = -1;
static int hf_asp_socket	= -1;
static int hf_asp_version	= -1;
static int hf_asp_session_id	= -1;
static int hf_asp_zero_value	= -1;
static int hf_asp_init_error	= -1;
static int hf_asp_attn_code	= -1;
static int hf_asp_seq		= -1;
static int hf_asp_size		= -1;

/* status stuff same for asp and afp */
static int hf_asp_server_name = -1;
static int hf_asp_server_type = -1;
static int hf_asp_server_vers = -1;
static int hf_asp_server_uams = -1;
static int hf_asp_server_icon = -1;
static int hf_asp_server_directory = -1;

static int hf_asp_server_flag = -1;
static int hf_asp_server_flag_copyfile = -1;
static int hf_asp_server_flag_passwd   = -1;
static int hf_asp_server_flag_no_save_passwd = -1;
static int hf_asp_server_flag_srv_msg	= -1;
static int hf_asp_server_flag_srv_sig	= -1;
static int hf_asp_server_flag_tcpip	= -1;
static int hf_asp_server_flag_notify	= -1;
static int hf_asp_server_flag_reconnect	= -1;
static int hf_asp_server_flag_directory	= -1;
static int hf_asp_server_flag_utf8_name = -1;
static int hf_asp_server_flag_fast_copy = -1;
static int hf_asp_server_signature	= -1;
static int hf_asp_server_utf8_name_len  = -1;
static int hf_asp_server_utf8_name      = -1;

static int hf_asp_server_addr_len	= -1;
static int hf_asp_server_addr_type	= -1;
static int hf_asp_server_addr_value	= -1;

static gint ett_asp_status = -1;
static gint ett_asp_uams   = -1;
static gint ett_asp_vers   = -1;
static gint ett_asp_addr   = -1;
static gint ett_asp_addr_line = -1;
static gint ett_asp_directory = -1;
static gint ett_asp_utf8_name = -1;
static gint ett_asp_status_server_flag = -1;

typedef struct {
	guint32 conversation;
	guint8  src[4];
	guint16	seq;
} asp_request_key;

typedef struct {
	guint8	value;	/* command for asp, bitmap for atp */
} asp_request_val;

static GHashTable *asp_request_hash = NULL;

/* Hash Functions */
static gint  asp_equal (gconstpointer v, gconstpointer v2)
{
	const asp_request_key *val1 = (const asp_request_key*)v;
	const asp_request_key *val2 = (const asp_request_key*)v2;

	if (val1->conversation == val2->conversation &&
			val1->seq == val2->seq &&
			!memcmp(val1->src, val2->src, 4)) {
		return 1;
	}
	return 0;
}

static guint asp_hash  (gconstpointer v)
{
        const asp_request_key *asp_key = (const asp_request_key*)v;
        return asp_key->seq;
}

/* ------------------------------------ */
static GHashTable *atp_request_hash = NULL;


/* ------------------------------------ */
static int proto_nbp = -1;
static int hf_nbp_op = -1;
static int hf_nbp_info = -1;
static int hf_nbp_count = -1;
static int hf_nbp_tid = -1;

static int hf_nbp_node_net = -1;
static int hf_nbp_node_port = -1;
static int hf_nbp_node_node = -1;
static int hf_nbp_node_enum = -1;
static int hf_nbp_node_object = -1;
static int hf_nbp_node_type = -1;
static int hf_nbp_node_zone = -1;

static int proto_rtmp = -1;
static int hf_rtmp_net = -1;
static int hf_rtmp_node_len = -1;
static int hf_rtmp_node = -1;
static int hf_rtmp_tuple_net = -1;
static int hf_rtmp_tuple_range_start = -1;
static int hf_rtmp_tuple_range_end = -1;
static int hf_rtmp_tuple_dist = -1;
static int hf_rtmp_function = -1;

static gint ett_atp = -1;

static gint ett_atp_segments = -1;
static gint ett_atp_segment = -1;
static gint ett_atp_info = -1;
static gint ett_asp = -1;
static gint ett_pap = -1;

static gint ett_nbp = -1;
static gint ett_nbp_info = -1;
static gint ett_nbp_node = -1;
static gint ett_rtmp = -1;
static gint ett_rtmp_tuple = -1;
static gint ett_ddp = -1;
static gint ett_llap = -1;
static gint ett_pstring = -1;

static const fragment_items atp_frag_items = {
	&ett_atp_segment,
	&ett_atp_segments,
	&hf_atp_segments,
	&hf_atp_segment,
	&hf_atp_segment_overlap,
	&hf_atp_segment_overlap_conflict,
	&hf_atp_segment_multiple_tails,
	&hf_atp_segment_too_long_segment,
	&hf_atp_segment_error,
	&hf_atp_reassembled_in,
	"segments"
};

/* -------------------------------- */

#define PAPOpenConn       1
#define PAPOpenConnReply  2
#define PAPSendData       3
#define PAPData           4
#define PAPTickle         5
#define PAPCloseConn      6
#define PAPCloseConnReply 7
#define PAPSendStatus     8
#define PAPStatus         9

static int proto_pap = -1;

static int hf_pap_connid   = -1;
static int hf_pap_function = -1;
static int hf_pap_socket   = -1;
static int hf_pap_quantum  = -1;
static int hf_pap_waittime = -1;
static int hf_pap_result   = -1;
static int hf_pap_status   = -1;
static int hf_pap_seq      = -1;
static int hf_pap_eof      = -1;

static int hf_pap_pad = -1;

static const value_string pap_function_vals[] = {
  {PAPOpenConn       , "Open Connection Query"},
  {PAPOpenConnReply  , "Open Connection Reply"},
  {PAPSendData       , "Send Data"},
  {PAPData           , "Data"},
  {PAPTickle         , "Tickle"},
  {PAPCloseConn      , "Close Connection Query"},
  {PAPCloseConnReply , "Close Connection reply"},
  {PAPSendStatus     , "Send Status"},
  {PAPStatus         , "Status"},
  
  {0, NULL}
};

/* -------------------------------- */

static dissector_table_t ddp_dissector_table;

static dissector_handle_t data_handle;

#define DDP_SHORT_HEADER_SIZE 5

/*
 * P = Padding, H = Hops, L = Len
 *
 * PPHHHHLL LLLLLLLL
 *
 * Assumes the argument is in host byte order.
 */
#define ddp_hops(x)	( ( x >> 10) & 0x3C )
#define ddp_len(x)		( x & 0x03ff )
typedef struct _e_ddp {
  guint16	hops_len; /* combines pad, hops, and len */
  guint16	sum,dnet,snet;
  guint8	dnode,snode;
  guint8	dport,sport;
  guint8	type;
} e_ddp;

#define DDP_HEADER_SIZE 13


static const value_string op_vals[] = {
  {DDP_RTMPDATA, "AppleTalk Routing Table response or data" },
  {DDP_NBP, "AppleTalk Name Binding Protocol packet"},
  {DDP_ATP, "AppleTalk Transaction Protocol packet"},
  {DDP_AEP, "AppleTalk Echo Protocol packet"},
  {DDP_RTMPREQ, "AppleTalk Routing Table request"},
  {DDP_ZIP, "AppleTalk Zone Information Protocol packet"},
  {DDP_ADSP, "AppleTalk Data Stream Protocol"},
  {DDP_EIGRP, "Cisco EIGRP for AppleTalk"},
  {0, NULL}
};

static const value_string rtmp_function_vals[] = {
  {1, "Request"},
  {2, "Route Data Request (split horizon processed)"},
  {3, "Route Data Request (no split horizon processing)"},
  {0, NULL}
};

#define NBP_BROADCAST 1
#define NBP_LOOKUP 2
#define NBP_FORWARD 4
#define NBP_REPLY 3

static const value_string nbp_op_vals[] = {
  {NBP_BROADCAST, "broadcast request"},
  {NBP_LOOKUP, "lookup"},
  {NBP_FORWARD, "forward request"},
  {NBP_REPLY, "reply"},
  {0, NULL}
};

static const value_string atp_function_vals[] = {
  {ATP_TREQ        ,"REQuest"},
  {ATP_TRESP       ,"RESPonse"},
  {ATP_TREL        ,"RELease"},
  {0, NULL}
};

static const value_string atp_trel_timer_vals[] = {
  {0, "30 seconds"},
  {1, "1 minute"},
  {2, "2 minutes"},
  {3, "4 minutes"},
  {4, "8 minutes"},
  {0, NULL}
};

/*
*/
static const value_string asp_func_vals[] = {
  {ASPFUNC_CLOSE,	"CloseSession" },
  {ASPFUNC_CMD,		"Command" },
  {ASPFUNC_STAT,	"GetStatus" },
  {ASPFUNC_OPEN,	"OpenSession" },
  {ASPFUNC_TICKLE,	"Tickle" },
  {ASPFUNC_WRITE,	"Write" },
  {ASPFUNC_WRTCONT,	"Write Cont" },
  {ASPFUNC_ATTN,	"Attention" },
  {0,			NULL } };

const value_string asp_error_vals[] = {
  {AFP_OK		, "success"},
  {AFPERR_ACCESS	, "permission denied" },
  {AFPERR_AUTHCONT	, "logincont" },
  {AFPERR_BADUAM	, "uam doesn't exist" },
  {AFPERR_BADVERS	, "bad afp version number" },
  {AFPERR_BITMAP	, "invalid bitmap" },
  {AFPERR_CANTMOVE 	, "can't move file" },
  {AFPERR_DENYCONF	, "file synchronization locks conflict" },
  {AFPERR_DIRNEMPT	, "directory not empty" },
  {AFPERR_DFULL		, "disk full" },
  {AFPERR_EOF		, "end of file" },
  {AFPERR_BUSY		, "FileBusy" },
  {AFPERR_FLATVOL  	, "volume doesn't support directories" },
  {AFPERR_NOITEM	, "ItemNotFound" },
  {AFPERR_LOCK     	, "LockErr" },
  {AFPERR_MISC     	, "misc. err" },
  {AFPERR_NLOCK    	, "no more locks" },
  {AFPERR_NOSRVR	, "no response by server at that address" },
  {AFPERR_EXIST		, "object already exists" },
  {AFPERR_NOOBJ		, "object not found" },
  {AFPERR_PARAM		, "parameter error" },
  {AFPERR_NORANGE  	, "no range lock" },
  {AFPERR_RANGEOVR 	, "range overlap" },
  {AFPERR_SESSCLOS 	, "session closed" },
  {AFPERR_NOTAUTH	, "user not authenticated" },
  {AFPERR_NOOP		, "command not supported" },
  {AFPERR_BADTYPE	, "object is the wrong type" },
  {AFPERR_NFILE		, "too many files open" },
  {AFPERR_SHUTDOWN	, "server is going down" },
  {AFPERR_NORENAME 	, "can't rename" },
  {AFPERR_NODIR		, "couldn't find directory" },
  {AFPERR_ITYPE		, "wrong icon type" },
  {AFPERR_VLOCK		, "volume locked" },
  {AFPERR_OLOCK    	, "object locked" },
  {AFPERR_CTNSHRD  	, "share point contains a share point" },
  {AFPERR_NOID     	, "file thread not found" },
  {AFPERR_EXISTID  	, "file already has an id" },
  {AFPERR_DIFFVOL  	, "different volume" },
  {AFPERR_CATCHNG  	, "catalog has changed" },
  {AFPERR_SAMEOBJ  	, "source file == destination file" },
  {AFPERR_BADID    	, "non-existent file id" },
  {AFPERR_PWDSAME  	, "same password/can't change password" },
  {AFPERR_PWDSHORT 	, "password too short" },
  {AFPERR_PWDEXPR  	, "password expired" },
  {AFPERR_INSHRD   	, "folder being shared is inside a shared folder." },
  {AFPERR_INTRASH   , "shared folder in trash." },
  {AFPERR_PWDCHNG   , "password needs to be changed" },
  {AFPERR_PWDPOLCY  , "password fails policy check" },
  {AFPERR_USRLOGIN  , "user already logged on" },
  {0,			NULL } };

/*
 * XXX - do this with an FT_UINT_STRING?
 * Unfortunately, you can't extract from an FT_UINT_STRING the string,
 * which we'd want to do in order to put it into the "Data:" portion.
 *
 * Are these always in the Mac extended character set?
 */
static int dissect_pascal_string(tvbuff_t *tvb, int offset, proto_tree *tree,
	int hf_index)
{
	int len;

	len = tvb_get_guint8(tvb, offset);
	offset++;

	if ( tree )
	{
		char *tmp;
		proto_tree *item;
		proto_tree *subtree;

		/*
		 * XXX - if we could do this inside the protocol tree
		 * code, we could perhaps avoid allocating and freeing
		 * this string buffer.
		 */
		tmp = tvb_get_ephemeral_string(tvb, offset, len);
		item = proto_tree_add_string(tree, hf_index, tvb, offset-1, len+1, tmp);

		subtree = proto_item_add_subtree(item, ett_pstring);
		proto_tree_add_text(subtree, tvb, offset-1, 1, "Length: %d", len);
		proto_tree_add_text(subtree, tvb, offset, len, "Data: %s", tmp);

	}
	offset += len;

	return offset;
}

static void
dissect_rtmp_request(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) {
  proto_tree *rtmp_tree;
  proto_item *ti;
  guint8 function;

  if (check_col(pinfo->cinfo, COL_PROTOCOL))
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "RTMP");
  if (check_col(pinfo->cinfo, COL_INFO))
    col_clear(pinfo->cinfo, COL_INFO);

  function = tvb_get_guint8(tvb, 0);

  if (check_col(pinfo->cinfo, COL_INFO))
    col_add_fstr(pinfo->cinfo, COL_INFO, "%s",
	val_to_str(function, rtmp_function_vals, "Unknown function (%02x)"));

  if (tree) {
    ti = proto_tree_add_item(tree, proto_rtmp, tvb, 0, 1, FALSE);
    rtmp_tree = proto_item_add_subtree(ti, ett_rtmp);

    proto_tree_add_uint(rtmp_tree, hf_rtmp_function, tvb, 0, 1, function);
  }
}

static void
dissect_rtmp_data(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) {
  proto_tree *rtmp_tree;
  proto_item *ti;
  int offset = 0;
  guint16 net;
  guint8 nodelen,nodelen_bits;
  guint16 node; /* might be more than 8 bits */
  int i;

  if (check_col(pinfo->cinfo, COL_PROTOCOL))
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "RTMP");
  if (check_col(pinfo->cinfo, COL_INFO))
    col_clear(pinfo->cinfo, COL_INFO);

  net = tvb_get_ntohs(tvb, offset);
  nodelen_bits = tvb_get_guint8(tvb, offset+2);
  if ( nodelen_bits <= 8 ) {
    node = tvb_get_guint8(tvb, offset)+1;
    nodelen = 1;
  } else {
    node = tvb_get_ntohs(tvb, offset);
    nodelen = 2;
  }

  if (check_col(pinfo->cinfo, COL_INFO))
    col_add_fstr(pinfo->cinfo, COL_INFO, "Net: %u  Node Len: %u  Node: %u",
		net, nodelen_bits, node);

  if (tree) {
    ti = proto_tree_add_item(tree, proto_rtmp, tvb, offset, -1, FALSE);
    rtmp_tree = proto_item_add_subtree(ti, ett_rtmp);

    proto_tree_add_uint(rtmp_tree, hf_rtmp_net, tvb, offset, 2, net);
    proto_tree_add_uint(rtmp_tree, hf_rtmp_node_len, tvb, offset+2, 1,
			nodelen_bits);
    proto_tree_add_uint(rtmp_tree, hf_rtmp_node, tvb, offset+3, nodelen,
			node);
    offset += 3 + nodelen;

    i = 1;
    while (tvb_offset_exists(tvb, offset)) {
      proto_tree *tuple_item, *tuple_tree;
      guint16 tuple_net;
      guint8 tuple_dist;
      guint16 tuple_range_end;

      tuple_net = tvb_get_ntohs(tvb, offset);
      tuple_dist = tvb_get_guint8(tvb, offset+2);

      if (tuple_dist & 0x80) {
        tuple_range_end = tvb_get_ntohs(tvb, offset+3);
        tuple_item = proto_tree_add_text(rtmp_tree, tvb, offset, 6,
			"Tuple %d:  Range Start: %u  Dist: %u  Range End: %u",
			i, tuple_net, tuple_dist&0x7F, tuple_range_end);
      } else {
        tuple_item = proto_tree_add_text(rtmp_tree, tvb, offset, 3,
			"Tuple %d:  Net: %u  Dist: %u",
			i, tuple_net, tuple_dist);
      }
      tuple_tree = proto_item_add_subtree(tuple_item, ett_rtmp_tuple);

      if (tuple_dist & 0x80) {
        proto_tree_add_uint(tuple_tree, hf_rtmp_tuple_range_start, tvb, offset, 2,
			tuple_net);
      } else {
        proto_tree_add_uint(tuple_tree, hf_rtmp_tuple_net, tvb, offset, 2,
			tuple_net);
      }
      proto_tree_add_uint(tuple_tree, hf_rtmp_tuple_dist, tvb, offset+2, 1,
			tuple_dist & 0x7F);

      if (tuple_dist & 0x80) {
        /*
         * Extended network tuple.
         */
        proto_tree_add_item(tuple_tree, hf_rtmp_tuple_range_end, tvb, offset+3, 2,
				FALSE);
	offset += 6;
      } else
        offset += 3;

      i++;
    }
  }
}

static void
dissect_nbp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) {
  proto_tree *nbp_tree;
  proto_tree *nbp_info_tree;
  proto_item *ti, *info_item;
  int offset = 0;
  guint8 info;
  guint op, count;
  unsigned int i;

  if (check_col(pinfo->cinfo, COL_PROTOCOL))
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "NBP");
  if (check_col(pinfo->cinfo, COL_INFO))
    col_clear(pinfo->cinfo, COL_INFO);

  info = tvb_get_guint8(tvb, offset);
  op = info >> 4;
  count = info & 0x0F;

  if (check_col(pinfo->cinfo, COL_INFO))
    col_add_fstr(pinfo->cinfo, COL_INFO, "Op: %s  Count: %u",
      val_to_str(op, nbp_op_vals, "Unknown (0x%01x)"), count);

  if (tree) {
    ti = proto_tree_add_item(tree, proto_nbp, tvb, offset, -1, FALSE);
    nbp_tree = proto_item_add_subtree(ti, ett_nbp);

    info_item = proto_tree_add_uint_format(nbp_tree, hf_nbp_info, tvb, offset, 1,
		info,
		"Info: 0x%01X  Operation: %s  Count: %u", info,
		val_to_str(op, nbp_op_vals, "Unknown (0x%01X)"),
		count);
    nbp_info_tree = proto_item_add_subtree(info_item, ett_nbp_info);
    proto_tree_add_uint(nbp_info_tree, hf_nbp_op, tvb, offset, 1, info);
    proto_tree_add_uint(nbp_info_tree, hf_nbp_count, tvb, offset, 1, info);
    proto_tree_add_item(nbp_tree, hf_nbp_tid, tvb, offset+1, 1, FALSE);
    offset += 2;

    for (i=0; i<count; i++) {
      proto_tree *node_item,*node_tree;
      int soffset = offset;

      node_item = proto_tree_add_text(nbp_tree, tvb, offset, -1,
			"Node %u", i+1);
      node_tree = proto_item_add_subtree(node_item, ett_nbp_node);

      proto_tree_add_item(node_tree, hf_nbp_node_net, tvb, offset, 2, FALSE);
      offset += 2;
      proto_tree_add_item(node_tree, hf_nbp_node_node, tvb, offset, 1, FALSE);
      offset++;
      proto_tree_add_item(node_tree, hf_nbp_node_port, tvb, offset, 1, FALSE);
      offset++;
      proto_tree_add_item(node_tree, hf_nbp_node_enum, tvb, offset, 1, FALSE);
      offset++;

      offset = dissect_pascal_string(tvb, offset, node_tree, hf_nbp_node_object);
      offset = dissect_pascal_string(tvb, offset, node_tree, hf_nbp_node_type);
      offset = dissect_pascal_string(tvb, offset, node_tree, hf_nbp_node_zone);

      proto_item_set_len(node_item, offset-soffset);
    }
  }

  return;
}

/* -----------------------------
   ATP protocol cf. inside appletalk chap. 9
   desegmentation from packet-ieee80211.c
*/
static void
dissect_atp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) {
  proto_tree *atp_tree = NULL;
  proto_item *ti;
  proto_tree *atp_info_tree;
  proto_item *info_item;
  int offset = 0;
  guint8 ctrlinfo;
  guint8 frag_number = 0;
  guint op;
  guint16 tid;
  guint8 query;
  struct aspinfo aspinfo;
  tvbuff_t   *new_tvb = NULL;
  gboolean save_fragmented;
  gboolean more_fragment = FALSE;
  int len;
  guint8 bitmap;
  guint8 nbe = 0;
  guint8 t = 0;
  conversation_t  *conversation;
  asp_request_val *request_val = NULL;
  
  if (check_col(pinfo->cinfo, COL_PROTOCOL))
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "ATP");

  ctrlinfo = tvb_get_guint8(tvb, offset);
  bitmap   = tvb_get_guint8(tvb, offset +1);
  tid      = tvb_get_ntohs(tvb, offset +2);

  t = bitmap;
  while(t) {
	nbe++;
	t >>= 1;
  }

  op = ctrlinfo >> 6;

  aspinfo.reply   = (0x80 == (ctrlinfo & ATP_FUNCMASK))?1:0;
  aspinfo.release = (0xC0 == (ctrlinfo & ATP_FUNCMASK))?1:0;
  aspinfo.seq = tid;
  aspinfo.code = 0;
  query = (!aspinfo.reply && !aspinfo.release);

  conversation = find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype,
		pinfo->srcport, pinfo->destport, 0);
		
  if (conversation == NULL)
  {
	conversation = conversation_new(pinfo->fd->num, &pinfo->src, &pinfo->dst,
			pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
  }

  if (atp_defragment) {
  	asp_request_key request_key;

  	request_key.conversation = conversation->index;
  	memcpy(request_key.src, (!aspinfo.reply)?pinfo->src.data:pinfo->dst.data, 4);
  	request_key.seq = aspinfo.seq;

  	request_val = (asp_request_val *) g_hash_table_lookup(atp_request_hash, &request_key);

  	if (!request_val && query && nbe > 1)  {
  		asp_request_key *new_request_key;

  		/* only save nbe packets if more than 1 requested
  	   	   save some memory and help the defragmentation if tid wraparound, ie
  	   	   we have both a request for 1 packet and a request for n packets,
  	   	   hopefully most of the time ATP_EOM will be set in the last packet.
  	    */
	 	new_request_key = se_alloc(sizeof(asp_request_key));
	 	*new_request_key = request_key;

	    request_val = se_alloc(sizeof(asp_request_val));
	    request_val->value = nbe;

	 	g_hash_table_insert(atp_request_hash, new_request_key,request_val);
	}
  }

  /*
     ATP_EOM is not mandatory. Some implementations don't always set it
     if the query is only one packet.

     So it needs to keep the number of packets asked in request.
    */

  if (aspinfo.reply) {
     more_fragment = !(ATP_EOM & ctrlinfo) && request_val;
     frag_number = bitmap;
  }

  if (check_col(pinfo->cinfo, COL_INFO)) {
    col_clear(pinfo->cinfo, COL_INFO);
    col_add_fstr(pinfo->cinfo, COL_INFO, "%s transaction %u",
    	val_to_str(op, atp_function_vals, "Unknown (0x%01x)"),tid);
    if (more_fragment)
	col_append_fstr(pinfo->cinfo, COL_INFO, " [fragment]");
  }

  if (tree) {
    ti = proto_tree_add_item(tree, proto_atp, tvb, offset, -1, FALSE);
    atp_tree = proto_item_add_subtree(ti, ett_atp);
    proto_item_set_len(atp_tree, aspinfo.release?8:ATP_HDRSIZE -1);

    info_item = proto_tree_add_item(atp_tree, hf_atp_ctrlinfo, tvb, offset, 1, FALSE);
    atp_info_tree = proto_item_add_subtree(info_item, ett_atp_info);

    proto_tree_add_item(atp_info_tree, hf_atp_function, tvb, offset, 1, FALSE);
    proto_tree_add_item(atp_info_tree, hf_atp_xo, tvb, offset, 1, FALSE);
    proto_tree_add_item(atp_info_tree, hf_atp_eom, tvb, offset, 1, FALSE);
    proto_tree_add_item(atp_info_tree, hf_atp_sts, tvb, offset, 1, FALSE);
    if ((ctrlinfo & (ATP_FUNCMASK|ATP_XO)) == (0x40|ATP_XO)) {
      /* TReq with XO set */
      proto_tree_add_item(atp_info_tree, hf_atp_treltimer, tvb, offset, 1, FALSE);
    }
    if (query) {
      proto_tree_add_text(atp_tree, tvb, offset +1, 1,
			  "Bitmap: 0x%02x  %u packet(s) max", bitmap, nbe);
    }
    else {
      proto_tree_add_item(atp_tree, hf_atp_bitmap, tvb, offset +1, 1, FALSE);
    }
    proto_tree_add_item(atp_tree, hf_atp_tid, tvb, offset +2, 2, FALSE);

    if (aspinfo.release)
    	proto_tree_add_item(atp_tree, hf_atp_user_bytes, tvb, offset +4, 4, FALSE);

  }

  if (aspinfo.release)
  	return;

  save_fragmented = pinfo->fragmented;

  /* FIXME
     asp doesn't fit very well here
     move asp back in atp?
  */
  if (atp_defragment && aspinfo.reply && (more_fragment || frag_number != 0)) {
     fragment_data *fd_head;
     int hdr;

     hdr = ATP_HDRSIZE -1;
     if (frag_number != 0)
     	hdr += 4;	/* asp header */
     len = tvb_reported_length_remaining(tvb, hdr);
     fd_head = fragment_add_seq_check(tvb, hdr, pinfo, tid,
				     atp_fragment_table,
				     atp_reassembled_table,
				     frag_number,
				     len,
				     more_fragment);
     new_tvb = process_reassembled_data(tvb, ATP_HDRSIZE -1, pinfo,
				"Reassembled ATP", fd_head, &atp_frag_items,
				NULL, atp_tree);
  }
  else {
      /* full packet */
     new_tvb = tvb_new_subset(tvb, ATP_HDRSIZE -1, -1,- 1);
  }

  if (new_tvb) {
     pinfo->private_data = &aspinfo;
     /* if port == 6 it's not an ASP packet but a ZIP packet */
     if (pinfo->srcport == 6 || pinfo->destport == 6 )
    	call_dissector(zip_atp_handle, new_tvb, pinfo, tree);
     else {
        /* XXX need a conversation_get_dissector function ? */
        if (!aspinfo.reply && !conversation->dissector_handle) {
        	dissector_handle_t sub;

            /* if it's a known ASP function call ASP dissector
               else assume it's a PAP connection ID.
               the test is wrong because PAP conn IDs overlapped with ASP fn 
               but I don't want to keep track of NBP msgs and open connection
               port allocation.
            */
            guint8 fn = tvb_get_guint8(new_tvb, 0);

            if (!fn || fn > ASPFUNC_ATTN) {
     	        sub = pap_handle;
            }
            else {
     	        sub = asp_handle;
     	    }
     	    call_dissector(sub, new_tvb, pinfo, tree);
     	    conversation_set_dissector(conversation, sub);
        }
        else if (!try_conversation_dissector(&pinfo->src, &pinfo->dst, pinfo->ptype, 
        						pinfo->srcport, pinfo->destport, new_tvb,pinfo, tree)) {
    		call_dissector(data_handle, new_tvb, pinfo, tree);
        
        }
     }
  }
  else {
    /* Just show this as a fragment. */
    new_tvb = tvb_new_subset (tvb, ATP_HDRSIZE -1, -1, -1);
    call_dissector(data_handle, new_tvb, pinfo, tree);
  }
  pinfo->fragmented = save_fragmented;
  return;
}

/*
	copy and paste from dsi
	XXX - is the format of this reply dependent on the type of server,
	with this format being the format for AFP servers?
*/
static gint
dissect_asp_reply_get_status(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, gint offset)
{
        proto_tree      *sub_tree;
	proto_item	*ti;

	guint16 ofs;
	guint16 flag;
	guint16 machine_ofs;
	guint16 sign_ofs = 0;
	guint16 adr_ofs = 0;
	guint16 dir_ofs = 0;
	guint16 utf_ofs = 0;
	guint8	nbe;
	guint8  len;
	guint8  i;

	if (!tree)
		return offset;

	ti = proto_tree_add_text(tree, tvb, offset, -1, "Get Status");
	tree = proto_item_add_subtree(ti, ett_asp_status);

	machine_ofs = tvb_get_ntohs(tvb, offset +AFPSTATUS_MACHOFF);
	proto_tree_add_text(tree, tvb, offset +AFPSTATUS_MACHOFF, 2, "Machine offset: %u", machine_ofs);
	if (machine_ofs)
		machine_ofs += offset;

	ofs = tvb_get_ntohs(tvb, offset +AFPSTATUS_VERSOFF);
	proto_tree_add_text(tree, tvb, offset +AFPSTATUS_VERSOFF, 2, "Version offset: %u", ofs);

	ofs = tvb_get_ntohs(tvb, offset +AFPSTATUS_UAMSOFF);
	proto_tree_add_text(tree, tvb, offset +AFPSTATUS_UAMSOFF, 2, "UAMS offset: %u", ofs);

	ofs = tvb_get_ntohs(tvb, offset +AFPSTATUS_ICONOFF);
	proto_tree_add_text(tree, tvb, offset +AFPSTATUS_ICONOFF, 2, "Icon offset: %u", ofs);

	ofs = offset +AFPSTATUS_FLAGOFF;
	ti = proto_tree_add_item(tree, hf_asp_server_flag, tvb, ofs, 2, FALSE);
	sub_tree = proto_item_add_subtree(ti, ett_asp_status_server_flag);
	proto_tree_add_item(sub_tree, hf_asp_server_flag_copyfile      , tvb, ofs, 2, FALSE);
	proto_tree_add_item(sub_tree, hf_asp_server_flag_passwd        , tvb, ofs, 2, FALSE);
	proto_tree_add_item(sub_tree, hf_asp_server_flag_no_save_passwd, tvb, ofs, 2, FALSE);
	proto_tree_add_item(sub_tree, hf_asp_server_flag_srv_msg       , tvb, ofs, 2, FALSE);
	proto_tree_add_item(sub_tree, hf_asp_server_flag_srv_sig       , tvb, ofs, 2, FALSE);
	proto_tree_add_item(sub_tree, hf_asp_server_flag_tcpip         , tvb, ofs, 2, FALSE);
	proto_tree_add_item(sub_tree, hf_asp_server_flag_notify        , tvb, ofs, 2, FALSE);
	proto_tree_add_item(sub_tree, hf_asp_server_flag_reconnect     , tvb, ofs, 2, FALSE);
	proto_tree_add_item(sub_tree, hf_asp_server_flag_directory     , tvb, ofs, 2, FALSE);
	proto_tree_add_item(sub_tree, hf_asp_server_flag_utf8_name     , tvb, ofs, 2, FALSE);
	proto_tree_add_item(sub_tree, hf_asp_server_flag_fast_copy     , tvb, ofs, 2, FALSE);

	proto_tree_add_item(tree, hf_asp_server_name, tvb, offset +AFPSTATUS_PRELEN, 1, FALSE);

	flag = tvb_get_ntohs(tvb, ofs);
	if ((flag & AFPSRVRINFO_SRVSIGNATURE)) {
		ofs = offset +AFPSTATUS_PRELEN +tvb_get_guint8(tvb, offset +AFPSTATUS_PRELEN) +1;
		if ((ofs & 1))
			ofs++;

		sign_ofs = tvb_get_ntohs(tvb, ofs);
		proto_tree_add_text(tree, tvb, ofs, 2, "Signature offset: %u", sign_ofs);
		sign_ofs += offset;
		ofs += 2;

		if ((flag & AFPSRVRINFO_TCPIP) && ofs < machine_ofs ) {
			adr_ofs =  tvb_get_ntohs(tvb, ofs);
			proto_tree_add_text(tree, tvb, ofs, 2, "Network address offset: %u", adr_ofs);
			adr_ofs += offset;
			ofs += 2;
		}

		if ((flag & AFPSRVRINFO_SRVDIRECTORY) && ofs < machine_ofs) {
			dir_ofs =  tvb_get_ntohs(tvb, ofs);
			proto_tree_add_text(tree, tvb, ofs, 2, "Directory services offset: %u", dir_ofs);
			dir_ofs += offset;
			ofs += 2;
		}

		if ((flag & AFPSRVRINFO_SRVUTF8) && ofs < machine_ofs) {
			utf_ofs =  tvb_get_ntohs(tvb, ofs);
			proto_tree_add_text(tree, tvb, ofs, 2, "UTF-8 Server name offset: %u", utf_ofs);
			utf_ofs += offset;
		}
	}

	if (machine_ofs)
		proto_tree_add_item(tree, hf_asp_server_type, tvb, machine_ofs, 1, FALSE);

	ofs = offset +tvb_get_ntohs(tvb, offset +AFPSTATUS_VERSOFF);
	if (ofs) {
		nbe = tvb_get_guint8(tvb, ofs);
		ti = proto_tree_add_text(tree, tvb, ofs, 1, "Version list: %u", nbe);
		ofs++;
		sub_tree = proto_item_add_subtree(ti, ett_asp_vers);
		for (i = 0; i < nbe; i++) {
			len = tvb_get_guint8(tvb, ofs);
			proto_tree_add_item(sub_tree, hf_asp_server_vers, tvb, ofs, 1, FALSE);
			ofs += len + 1;
		}
	}

	ofs = offset +tvb_get_ntohs(tvb, offset +AFPSTATUS_UAMSOFF);
	if (ofs) {
		nbe = tvb_get_guint8(tvb, ofs);
		ti = proto_tree_add_text(tree, tvb, ofs, 1, "UAMS list: %u", nbe);
		ofs++;
		sub_tree = proto_item_add_subtree(ti, ett_asp_uams);
		for (i = 0; i < nbe; i++) {
			len = tvb_get_guint8(tvb, ofs);
			proto_tree_add_item(sub_tree, hf_asp_server_uams, tvb, ofs, 1, FALSE);
			ofs += len + 1;
		}
	}

	ofs = offset +tvb_get_ntohs(tvb, offset +AFPSTATUS_ICONOFF);
	if (ofs)
		proto_tree_add_item(tree, hf_asp_server_icon, tvb, ofs, 256, FALSE);

	if (sign_ofs) {
		proto_tree_add_item(tree, hf_asp_server_signature, tvb, sign_ofs, 16, FALSE);
	}

	if (adr_ofs) {
        	proto_tree *adr_tree;
		char *tmp;
        	const guint8 *ip;
		guint16 net;
		guint8  node;
        	guint16 port;

		ofs = adr_ofs;
		nbe = tvb_get_guint8(tvb, ofs);
		ti = proto_tree_add_text(tree, tvb, ofs, 1, "Address list: %u", nbe);
		ofs++;
		adr_tree = proto_item_add_subtree(ti, ett_asp_addr);
		for (i = 0; i < nbe; i++) {
			guint8 type;

			len = tvb_get_guint8(tvb, ofs);
			type =  tvb_get_guint8(tvb, ofs +1);
			switch (type) {
			case 1:	/* IP */
				ip = tvb_get_ptr(tvb, ofs+2, 4);
				ti = proto_tree_add_text(adr_tree, tvb, ofs, len, "ip %s", ip_to_str(ip));
				break;
			case 2: /* IP + port */
				ip = tvb_get_ptr(tvb, ofs+2, 4);
				port = tvb_get_ntohs(tvb, ofs+6);
				ti = proto_tree_add_text(adr_tree, tvb, ofs, len, "ip %s:%u",ip_to_str(ip),port);
				break;
			case 3: /* DDP, atalk_addr_to_str want host order not network */
				net  = tvb_get_ntohs(tvb, ofs+2);
				node = tvb_get_guint8(tvb, ofs +4);
				port = tvb_get_guint8(tvb, ofs +5);
				ti = proto_tree_add_text(adr_tree, tvb, ofs, len, "ddp %u.%u:%u",
					net, node, port);
				break;
			case 5: /* IP + port ssh tunnel */
				ip = tvb_get_ptr(tvb, ofs+2, 4);
				port = tvb_get_ntohs(tvb, ofs+6);
				ti = proto_tree_add_text(adr_tree, tvb, ofs, len, "ip (ssh tunnel) %s:%u",ip_to_str(ip),port);
				break;
			case 4: /* DNS */
				if (len > 2) {
					tmp = tvb_get_ephemeral_string(tvb, ofs +2, len -2);
					ti = proto_tree_add_text(adr_tree, tvb, ofs, len, "dns %s", tmp);
					break;
				}
				/* else fall to default malformed record */
			default:
				ti = proto_tree_add_text(adr_tree, tvb, ofs, len,"Unknow type : %u", type);
				break;
			}
			len -= 2;
			sub_tree = proto_item_add_subtree(ti,ett_asp_addr_line);
			proto_tree_add_item(sub_tree, hf_asp_server_addr_len, tvb, ofs, 1, FALSE);
			ofs++;
			proto_tree_add_item(sub_tree, hf_asp_server_addr_type, tvb, ofs, 1, FALSE);
			ofs++;
			proto_tree_add_item(sub_tree, hf_asp_server_addr_value,tvb, ofs, len, FALSE);
			ofs += len;
		}
	}

	if (dir_ofs) {
		ofs = dir_ofs;
		nbe = tvb_get_guint8(tvb, ofs);
		ti = proto_tree_add_text(tree, tvb, ofs, 1, "Directory services list: %u", nbe);
		ofs++;
		sub_tree = proto_item_add_subtree(ti, ett_asp_directory);
		for (i = 0; i < nbe; i++) {
			len = tvb_get_guint8(tvb, ofs);
			proto_tree_add_item(sub_tree, hf_asp_server_directory, tvb, ofs, 1, FALSE);
			ofs += len + 1;
		}
	}
	if (utf_ofs) {
		guint16 ulen;
		char *tmp;

		ofs = utf_ofs;
		ulen = tvb_get_ntohs(tvb, ofs);
		tmp = tvb_get_ephemeral_string(tvb, ofs + 2, ulen);
		ti = proto_tree_add_text(tree, tvb, ofs, ulen +2, "UTF8 server name: %s", tmp);
		sub_tree = proto_item_add_subtree(ti, ett_asp_utf8_name);
		proto_tree_add_uint(sub_tree, hf_asp_server_utf8_name_len, tvb, ofs, 2, ulen);
		ofs += 2;		
		proto_tree_add_string(sub_tree, hf_asp_server_utf8_name, tvb, ofs, ulen, tmp);
		ofs += ulen;
	}
	/* FIXME: offset is not updated */
	return offset;
}

/* -----------------------------
   PAP protocol cf. inside appletalk chap. 10
*/
#define PAD(x)      { proto_tree_add_item(pap_tree, hf_pap_pad, tvb, offset,  x, FALSE); offset += x; }

static void
dissect_pap(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
  int offset = 0;
  guint8 fn;
  guint8 connID;
  proto_tree *pap_tree = NULL;
  proto_item *ti;
  int len;

  if (check_col(pinfo->cinfo, COL_PROTOCOL))
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "PAP");
  if (check_col(pinfo->cinfo, COL_INFO))
    col_clear(pinfo->cinfo, COL_INFO);

  if (tree) {
    ti = proto_tree_add_item(tree, proto_pap, tvb, offset, -1, FALSE);
    pap_tree = proto_item_add_subtree(ti, ett_pap);
  }

  connID = tvb_get_guint8(tvb, offset);
  proto_tree_add_item(pap_tree, hf_pap_connid, tvb, offset, 1, FALSE);
  offset++;

  fn = tvb_get_guint8(tvb, offset);
  proto_tree_add_item(pap_tree, hf_pap_function, tvb, offset, 1, FALSE);
  offset++;

  if (check_col(pinfo->cinfo, COL_INFO)) {
	 col_add_fstr(pinfo->cinfo, COL_INFO, "%s  ID: %d",
      				val_to_str(fn, pap_function_vals, "Unknown (0x%01x)"), connID);
  }
  switch(fn) {
  case PAPOpenConn:
  	PAD(2);
  	proto_tree_add_item(pap_tree, hf_pap_socket, tvb, offset, 1, FALSE);
  	offset++;
  	proto_tree_add_item(pap_tree, hf_pap_quantum, tvb, offset, 1, FALSE);
  	offset++;
  	proto_tree_add_item(pap_tree, hf_pap_waittime, tvb, offset, 2, FALSE);
  	offset += 2;
  	break;

  case PAPOpenConnReply:
  	PAD(2);
  	proto_tree_add_item(pap_tree, hf_pap_socket, tvb, offset, 1, FALSE);
  	offset++;
  	proto_tree_add_item(pap_tree, hf_pap_quantum, tvb, offset, 1, FALSE);
  	offset++;
  	proto_tree_add_item(pap_tree, hf_pap_result, tvb, offset, 2, FALSE);
  	offset += 2;
    offset = dissect_pascal_string(tvb, offset, pap_tree, hf_pap_status);
  	break;

  case PAPSendData:
  	proto_tree_add_item(pap_tree, hf_pap_seq, tvb, offset, 2, FALSE);
  	offset += 2;
  	break;

  case PAPData:
  	proto_tree_add_item(pap_tree, hf_pap_eof, tvb, offset, 1, FALSE);
  	offset++;
  	PAD(1);
  	/* follow by data */
	len = tvb_reported_length_remaining(tvb,offset);
	call_dissector(data_handle,tvb_new_subset(tvb, offset,-1,len), pinfo, tree);
  	break;

  case PAPTickle:
  case PAPCloseConn:
  case PAPCloseConnReply:
  	PAD(2);
  	break;

  case PAPSendStatus:
  	PAD(2);
  	break;
  	
  case PAPStatus:
  	PAD(2);
  	PAD(4);
    offset = dissect_pascal_string(tvb, offset, pap_tree, hf_pap_status);
  	break;

  default:  /* unknown */
    break;
  }
}

/* -----------------------------
   ASP protocol cf. inside appletalk chap. 11
*/
static struct aspinfo *
get_transaction(tvbuff_t *tvb, packet_info *pinfo)
{
  struct aspinfo *aspinfo = pinfo->private_data;
  conversation_t	*conversation;
  asp_request_key request_key, *new_request_key;
  asp_request_val *request_val;
  guint8 fn;

  conversation = find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype,
		pinfo->srcport, pinfo->destport, 0);

  if (conversation == NULL)
  {
	conversation = conversation_new(pinfo->fd->num, &pinfo->src, &pinfo->dst,
			pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
  }

  request_key.conversation = conversation->index;
  memcpy(request_key.src, (!aspinfo->reply)?pinfo->src.data:pinfo->dst.data, 4);
  request_key.seq = aspinfo->seq;

  request_val = (asp_request_val *) g_hash_table_lookup(
								asp_request_hash, &request_key);
  if (!request_val && !aspinfo->reply )  {
	 fn = tvb_get_guint8(tvb, 0);
	 new_request_key = se_alloc(sizeof(asp_request_key));
	 *new_request_key = request_key;

	 request_val = se_alloc(sizeof(asp_request_val));
	 request_val->value = fn;

	 g_hash_table_insert(asp_request_hash, new_request_key,
								request_val);
  }

  if (!request_val)
	return NULL;

  aspinfo->command = request_val->value;
  return aspinfo;
}


static void
dissect_asp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
  struct aspinfo *aspinfo;
  int offset = 0;
  proto_tree *asp_tree = NULL;
  proto_item *ti;
  guint8 fn;
  int len;

  if (check_col(pinfo->cinfo, COL_PROTOCOL))
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "ASP");
  if (check_col(pinfo->cinfo, COL_INFO))
    col_clear(pinfo->cinfo, COL_INFO);

  aspinfo = get_transaction(tvb, pinfo);
  if (!aspinfo)
     return;

  fn = (guint8) aspinfo->command;

  if (check_col(pinfo->cinfo, COL_INFO)) {
	if (aspinfo->reply)
		col_add_fstr(pinfo->cinfo, COL_INFO, "Reply tid %u",aspinfo->seq);
	else
		col_add_fstr(pinfo->cinfo, COL_INFO, "Function: %s  tid %u",
      				val_to_str(fn, asp_func_vals, "Unknown (0x%01x)"), aspinfo->seq);
  }

  if (tree) {
    ti = proto_tree_add_item(tree, proto_asp, tvb, offset, -1, FALSE);
    asp_tree = proto_item_add_subtree(ti, ett_asp);
  }
  if (!aspinfo->reply) {
	tvbuff_t   *new_tvb;
	/* let the called deal with asp_tree == NULL */

      	proto_tree_add_item(asp_tree, hf_asp_func, tvb, offset, 1, FALSE);
	offset++;
  	switch(fn) {
  	case ASPFUNC_OPEN:
		proto_tree_add_item(asp_tree, hf_asp_socket, tvb, offset, 1, FALSE);
		offset++;
		proto_tree_add_item(asp_tree, hf_asp_version, tvb, offset, 2, FALSE);
		offset += 2;
		break;
	case ASPFUNC_TICKLE:
  	case ASPFUNC_CLOSE:
		proto_tree_add_item(asp_tree, hf_asp_session_id, tvb, offset, 1, FALSE);
		offset++;
		proto_tree_add_item(asp_tree, hf_asp_zero_value, tvb, offset, 2, FALSE);
		offset +=2;
  		break;
	case ASPFUNC_STAT:
		proto_tree_add_item(asp_tree, hf_asp_zero_value, tvb, offset, 1, FALSE);
		offset++;
		proto_tree_add_item(asp_tree, hf_asp_zero_value, tvb, offset, 2, FALSE);
		offset += 2;
		break;
	case ASPFUNC_ATTN:
		proto_tree_add_item(asp_tree, hf_asp_session_id, tvb, offset, 1, FALSE);
		offset++;
		proto_tree_add_item(asp_tree, hf_asp_attn_code, tvb, offset, 2, FALSE);
		offset +=2;
  		break;
  	case ASPFUNC_CMD:
  	case ASPFUNC_WRITE:
    		proto_item_set_len(asp_tree, 4);
		proto_tree_add_item(asp_tree, hf_asp_session_id, tvb, offset, 1, FALSE);
		offset++;
		proto_tree_add_item(asp_tree, hf_asp_seq, tvb, offset, 2, FALSE);
		offset += 2;
		len = tvb_reported_length_remaining(tvb,offset);
		new_tvb = tvb_new_subset(tvb, offset,-1,len);
		call_dissector(afp_handle, new_tvb, pinfo, tree);
  		break;
  	case ASPFUNC_WRTCONT:
		proto_tree_add_item(asp_tree, hf_asp_session_id, tvb, offset, 1, FALSE);
		offset++;
		proto_tree_add_item(asp_tree, hf_asp_seq, tvb, offset, 2, FALSE);
		offset += 2;
		proto_tree_add_item(asp_tree, hf_asp_size, tvb, offset, 2, FALSE);
		offset += 2;
		break;
  	default:
    		proto_item_set_len(asp_tree, 4);
    		offset += 3;
		len = tvb_reported_length_remaining(tvb,offset);
		call_dissector(data_handle,tvb_new_subset(tvb, offset,-1,len), pinfo, tree);
		break;
  	}
  }
  else {
	tvbuff_t   *new_tvb;

	proto_tree_add_uint(asp_tree, hf_asp_func, tvb, 0, 0, fn);
  	switch(fn) {
  	case ASPFUNC_OPEN:
		proto_tree_add_item(asp_tree, hf_asp_socket, tvb, offset, 1, FALSE);
		offset++;
		proto_tree_add_item(asp_tree, hf_asp_session_id, tvb, offset, 1, FALSE);
		offset++;
		proto_tree_add_item(asp_tree, hf_asp_init_error, tvb, offset, 2, FALSE);
		offset += 2;
  		break;
  	case ASPFUNC_CLOSE:
		proto_tree_add_item(asp_tree, hf_asp_zero_value, tvb, offset, 1, FALSE);
		offset++;
		proto_tree_add_item(asp_tree, hf_asp_zero_value, tvb, offset, 1, FALSE);
		offset++;
		proto_tree_add_item(asp_tree, hf_asp_zero_value, tvb, offset, 2, FALSE);
		offset += 2;
		break;
	case ASPFUNC_STAT:
		proto_tree_add_item(asp_tree, hf_asp_zero_value, tvb, offset, 4, FALSE);
		offset += 4;
		dissect_asp_reply_get_status(tvb, pinfo, asp_tree, offset);
		break;
	case ASPFUNC_CMD:
  	case ASPFUNC_WRITE:
    		proto_item_set_len(asp_tree, 4);
		aspinfo->code = tvb_get_ntohl(tvb, offset);
		proto_tree_add_item(asp_tree, hf_asp_error, tvb, offset, 4, FALSE);
		offset += 4;
		len = tvb_reported_length_remaining(tvb,offset);
		new_tvb = tvb_new_subset(tvb, offset,-1,len);
		call_dissector(afp_handle, new_tvb, pinfo, tree);
  		break;
	case ASPFUNC_TICKLE:
  	case ASPFUNC_WRTCONT:
		proto_tree_add_item(asp_tree, hf_asp_zero_value, tvb, offset, 4, FALSE);
		/* fall */
	case ASPFUNC_ATTN:	/* FIXME capture and spec disagree */
  	default:
    		proto_item_set_len(asp_tree, 4);
    		offset += 4;
		len = tvb_reported_length_remaining(tvb,offset);
		call_dissector(data_handle,tvb_new_subset(tvb, offset,-1,len), pinfo, tree);
		break;
  	}
  }
}

/* -----------------------------
   ZIP protocol cf. inside appletalk chap. 8
*/
static void
dissect_atp_zip(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
  struct aspinfo *aspinfo;
  int offset = 0;
  proto_tree *zip_tree;
  proto_tree *sub_tree;
  proto_item *ti;
  guint8 fn;
  guint16 count;
  guint8 len;

  if (check_col(pinfo->cinfo, COL_PROTOCOL))
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "ZIP");
  if (check_col(pinfo->cinfo, COL_INFO))
    col_clear(pinfo->cinfo, COL_INFO);

  aspinfo = get_transaction(tvb, pinfo);
  if (!aspinfo)
     return;

  fn = (guint8) aspinfo->command;

  if (check_col(pinfo->cinfo, COL_INFO)) {
	if (aspinfo->reply)
		col_add_fstr(pinfo->cinfo, COL_INFO, "Reply tid %u",aspinfo->seq);
	else
		col_add_fstr(pinfo->cinfo, COL_INFO, "Function: %s  tid %u",
      				val_to_str(fn, zip_atp_function_vals, "Unknown (0x%01x)"), aspinfo->seq);
  }

  if (!tree)
     return;

  ti = proto_tree_add_item(tree, proto_zip, tvb, offset, -1, FALSE);
  zip_tree = proto_item_add_subtree(ti, ett_zip);

  if (!aspinfo->reply) {
     proto_tree_add_item(zip_tree, hf_zip_atp_function, tvb, offset, 1, FALSE);
     offset++;
     switch(fn) {
     case 7:	/* start_index = 0 */
     case 8:
     case 9:
         proto_tree_add_item(zip_tree, hf_zip_zero_value, tvb, offset, 1, FALSE);
         offset++;
         proto_tree_add_item(zip_tree, hf_zip_start_index, tvb, offset, 2, FALSE);
         break;
     }
  }
  else {
  guint8 i;

     proto_tree_add_uint(zip_tree, hf_zip_atp_function, tvb, 0, 0, fn);
     switch(fn) {
     case 7:
     case 8:
     case 9:
         proto_tree_add_item(zip_tree, hf_zip_last_flag, tvb, offset, 1, FALSE);
         offset++;

         proto_tree_add_item(zip_tree, hf_zip_zero_value, tvb, offset, 1, FALSE);
         offset++;
	 count = tvb_get_ntohs(tvb, offset);
         ti = proto_tree_add_item(zip_tree, hf_zip_count, tvb, offset, 2, FALSE);
         offset += 2;
      	 sub_tree = proto_item_add_subtree(ti, ett_zip_zones_list);
	 for (i= 1; i <= count; i++) {
	     len = tvb_get_guint8(tvb, offset);
             proto_tree_add_item(sub_tree, hf_zip_zone_name, tvb, offset, 1,FALSE);
	     offset += len +1;
	 }
         break;
     }
  }
}

static void
dissect_ddp_zip(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
  proto_tree *zip_tree = NULL;
  proto_item *ti;
  guint8  fn;
  guint8  len;
  gint    offset = 0;
  proto_tree *flag_tree;
  proto_tree *sub_tree;
  proto_tree *net_tree;
  guint8 flag;
  guint16  net;
  guint8 i;
  guint8 count;

  if (check_col(pinfo->cinfo, COL_PROTOCOL))
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "ZIP");
  if (check_col(pinfo->cinfo, COL_INFO))
    col_clear(pinfo->cinfo, COL_INFO);

  fn = tvb_get_guint8(tvb, 0);
  if (check_col(pinfo->cinfo, COL_INFO)) {
    col_add_str(pinfo->cinfo, COL_INFO,
      val_to_str(fn, zip_function_vals, "Unknown ZIP function (%02x)"));
  }

  if (!tree)
     return;

  ti = proto_tree_add_item(tree, proto_zip, tvb, 0, -1, FALSE);
  zip_tree = proto_item_add_subtree(ti, ett_zip);

  proto_tree_add_item(zip_tree, hf_zip_function, tvb, offset, 1,FALSE);
  offset++;
  /* fn 1,7,2,8 are not tested */
  switch (fn) {
  case 1: /* Query */
      count = tvb_get_guint8(tvb, offset);
      ti = proto_tree_add_item(zip_tree, hf_zip_network_count, tvb, offset, 1, FALSE);
      offset++;
      sub_tree = proto_item_add_subtree(ti, ett_zip_network_list);
      for (i= 1; i <= count; i++) {
          proto_tree_add_item(sub_tree, hf_zip_network, tvb, offset, 2, FALSE);
          offset += 2;
      }
      break;
  case 7: /* Notify */
      flag = tvb_get_guint8(tvb, offset);
      ti = proto_tree_add_text(zip_tree, tvb, offset , 1,"Flags : 0x%02x", flag);
      flag_tree = proto_item_add_subtree(ti, ett_zip_flags);
      proto_tree_add_item(flag_tree, hf_zip_flags_zone_invalid, tvb, offset, 1,FALSE);
      proto_tree_add_item(flag_tree, hf_zip_flags_use_broadcast,tvb, offset, 1,FALSE);
      proto_tree_add_item(flag_tree, hf_zip_flags_only_one_zone,tvb, offset, 1,FALSE);
      offset++;

      proto_tree_add_item(zip_tree, hf_zip_zero_value, tvb, offset, 4, FALSE);
      offset += 4;

      len = tvb_get_guint8(tvb, offset);
      proto_tree_add_item(zip_tree, hf_zip_zone_name, tvb, offset, 1,FALSE);
      offset += len +1;

      len = tvb_get_guint8(tvb, offset);
      proto_tree_add_item(zip_tree, hf_zip_multicast_length,tvb, offset, 1,FALSE);
      offset++;
      proto_tree_add_item(zip_tree, hf_zip_multicast_address,tvb, offset, len,FALSE);
      offset += len;

      proto_tree_add_item(zip_tree, hf_zip_zone_name, tvb, offset, 1,FALSE);
      break;

  case 2: /* Reply */
  case 8: /* Extended Reply */
      count = tvb_get_guint8(tvb, offset);
      ti = proto_tree_add_item(zip_tree, hf_zip_network_count, tvb, offset, 1, FALSE);
      offset++;
      sub_tree = proto_item_add_subtree(ti, ett_zip_network_list);
      for (i= 1; i <= count; i++) {
          net = tvb_get_ntohs(tvb, offset);
          ti = proto_tree_add_text(zip_tree, tvb, offset , 2, "Zone for network : %u", net);
          net_tree = proto_item_add_subtree(ti, ett_zip_network_list);
          proto_tree_add_item(net_tree, hf_zip_network, tvb, offset, 2, FALSE);
          offset += 2;
          len = tvb_get_guint8(tvb, offset);
          proto_tree_add_item(net_tree, hf_zip_zone_name, tvb, offset, 1,FALSE);
          offset += len +1;
      }
      break;

  case 5 :  /* GetNetInfo request */
      proto_tree_add_item(zip_tree, hf_zip_zero_value, tvb, offset, 1, FALSE);
      offset++;
      proto_tree_add_item(zip_tree, hf_zip_zero_value, tvb, offset, 4, FALSE);
      offset += 4;
      proto_tree_add_item(zip_tree, hf_zip_zone_name, tvb, offset, 1,FALSE);
      break;

  case 6 :  /* GetNetInfo reply */
      flag = tvb_get_guint8(tvb, offset);
      ti = proto_tree_add_text(zip_tree, tvb, offset , 1,"Flags : 0x%02x", flag);
      flag_tree = proto_item_add_subtree(ti, ett_zip_flags);
      proto_tree_add_item(flag_tree, hf_zip_flags_zone_invalid, tvb, offset, 1,FALSE);
      proto_tree_add_item(flag_tree, hf_zip_flags_use_broadcast,tvb, offset, 1,FALSE);
      proto_tree_add_item(flag_tree, hf_zip_flags_only_one_zone,tvb, offset, 1,FALSE);
      offset++;

      proto_tree_add_item(zip_tree, hf_zip_network_start, tvb, offset, 2, FALSE);
      offset += 2;

      proto_tree_add_item(zip_tree, hf_zip_network_end, tvb, offset, 2, FALSE);
      offset += 2;

      len = tvb_get_guint8(tvb, offset);
      proto_tree_add_item(zip_tree, hf_zip_zone_name, tvb, offset, 1,FALSE);
      offset += len +1;

      len = tvb_get_guint8(tvb, offset);
      proto_tree_add_item(zip_tree, hf_zip_multicast_length,tvb, offset, 1,FALSE);
      offset++;
      proto_tree_add_item(zip_tree, hf_zip_multicast_address,tvb, offset, len,FALSE);
      offset += len;
      if ((flag & 0x80) != 0)
         proto_tree_add_item(zip_tree, hf_zip_default_zone, tvb, offset, 1,FALSE);
      break;

  default:
      break;
  }
}

static void
dissect_ddp_short(tvbuff_t *tvb, packet_info *pinfo, guint8 dnode,
		  guint8 snode, proto_tree *tree)
{
  guint16 len;
  guint8  dport;
  guint8  sport;
  guint8  type;
  proto_tree *ddp_tree = NULL;
  proto_item *ti;
  static struct atalk_ddp_addr src, dst;
  tvbuff_t   *new_tvb;

  if (check_col(pinfo->cinfo, COL_PROTOCOL))
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "DDP");
  if (check_col(pinfo->cinfo, COL_INFO))
    col_clear(pinfo->cinfo, COL_INFO);

  if (tree) {
    ti = proto_tree_add_item(tree, proto_ddp, tvb, 0, DDP_SHORT_HEADER_SIZE,
			     FALSE);
    ddp_tree = proto_item_add_subtree(ti, ett_ddp);
  }
  len = tvb_get_ntohs(tvb, 0);
  if (tree)
      proto_tree_add_uint(ddp_tree, hf_ddp_len, tvb, 0, 2, len);
  dport = tvb_get_guint8(tvb, 2);
  if (tree)
    proto_tree_add_uint(ddp_tree, hf_ddp_dst_socket, tvb, 2, 1, dport);
  sport = tvb_get_guint8(tvb, 3);
  if (tree)
    proto_tree_add_uint(ddp_tree, hf_ddp_src_socket, tvb, 3, 1, sport);
  type = tvb_get_guint8(tvb, 4);

  src.net = 0;
  src.node = snode;
  dst.net = 0;
  dst.node = dnode;
  SET_ADDRESS(&pinfo->net_src, AT_ATALK, sizeof src, (guint8 *)&src);
  SET_ADDRESS(&pinfo->src, AT_ATALK, sizeof src, (guint8 *)&src);
  SET_ADDRESS(&pinfo->net_dst, AT_ATALK, sizeof dst, (guint8 *)&dst);
  SET_ADDRESS(&pinfo->dst, AT_ATALK, sizeof dst, (guint8 *)&dst);

  pinfo->ptype = PT_DDP;
  pinfo->destport = dport;
  pinfo->srcport = sport;

  if (check_col(pinfo->cinfo, COL_INFO)) {
    col_add_str(pinfo->cinfo, COL_INFO,
      val_to_str(type, op_vals, "Unknown DDP protocol (%02x)"));
  }
  if (tree) {
    proto_tree_add_string_hidden(ddp_tree, hf_ddp_src, tvb,
				 4, 3, atalk_addr_to_str(&src));
    proto_tree_add_string_hidden(ddp_tree, hf_ddp_dst, tvb,
				 6, 3, atalk_addr_to_str(&dst));

    proto_tree_add_uint(ddp_tree, hf_ddp_type, tvb, 4, 1, type);
  }
  new_tvb = tvb_new_subset(tvb, DDP_SHORT_HEADER_SIZE, -1, -1);

  if (!dissector_try_port(ddp_dissector_table, type, new_tvb, pinfo, tree))
    call_dissector(data_handle,new_tvb, pinfo, tree);
}

static void
dissect_ddp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
  e_ddp       ddp;
  proto_tree *ddp_tree;
  proto_item *ti;
  static struct atalk_ddp_addr src, dst;
  tvbuff_t   *new_tvb;

  if (check_col(pinfo->cinfo, COL_PROTOCOL))
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "DDP");
  if (check_col(pinfo->cinfo, COL_INFO))
    col_clear(pinfo->cinfo, COL_INFO);

  tvb_memcpy(tvb, (guint8 *)&ddp, 0, sizeof(e_ddp));
  ddp.dnet=g_ntohs(ddp.dnet);
  ddp.snet=g_ntohs(ddp.snet);
  ddp.sum=g_ntohs(ddp.sum);
  ddp.hops_len=g_ntohs(ddp.hops_len);

  src.net = ddp.snet;
  src.node = ddp.snode;
  dst.net = ddp.dnet;
  dst.node = ddp.dnode;
  SET_ADDRESS(&pinfo->net_src, AT_ATALK, sizeof src, (guint8 *)&src);
  SET_ADDRESS(&pinfo->src, AT_ATALK, sizeof src, (guint8 *)&src);
  SET_ADDRESS(&pinfo->net_dst, AT_ATALK, sizeof dst, (guint8 *)&dst);
  SET_ADDRESS(&pinfo->dst, AT_ATALK, sizeof dst, (guint8 *)&dst);

  pinfo->ptype = PT_DDP;
  pinfo->destport = ddp.dport;
  pinfo->srcport = ddp.sport;

  if (check_col(pinfo->cinfo, COL_INFO))
    col_add_str(pinfo->cinfo, COL_INFO,
      val_to_str(ddp.type, op_vals, "Unknown DDP protocol (%02x)"));

  if (tree) {
    ti = proto_tree_add_item(tree, proto_ddp, tvb, 0, DDP_HEADER_SIZE,
			     FALSE);
    ddp_tree = proto_item_add_subtree(ti, ett_ddp);

    proto_tree_add_string_hidden(ddp_tree, hf_ddp_src, tvb,
				 4, 3, atalk_addr_to_str(&src));
    proto_tree_add_string_hidden(ddp_tree, hf_ddp_dst, tvb,
				 6, 3, atalk_addr_to_str(&dst));

    proto_tree_add_uint(ddp_tree, hf_ddp_hopcount,   tvb, 0, 1,
			ddp_hops(ddp.hops_len));
    proto_tree_add_uint(ddp_tree, hf_ddp_len,        tvb, 0, 2,
			ddp_len(ddp.hops_len));
    proto_tree_add_uint(ddp_tree, hf_ddp_checksum,   tvb, 2,  2,
			ddp.sum);
    proto_tree_add_uint(ddp_tree, hf_ddp_dst_net,    tvb, 4,  2,
			ddp.dnet);
    proto_tree_add_uint(ddp_tree, hf_ddp_src_net,    tvb, 6,  2,
			ddp.snet);
    proto_tree_add_uint(ddp_tree, hf_ddp_dst_node,   tvb, 8,  1,
			ddp.dnode);
    proto_tree_add_uint(ddp_tree, hf_ddp_src_node,   tvb, 9,  1,
			ddp.snode);
    proto_tree_add_uint(ddp_tree, hf_ddp_dst_socket, tvb, 10, 1,
			ddp.dport);
    proto_tree_add_uint(ddp_tree, hf_ddp_src_socket, tvb, 11, 1,
			ddp.sport);
    proto_tree_add_uint(ddp_tree, hf_ddp_type,       tvb, 12, 1,
			ddp.type);
  }

  new_tvb = tvb_new_subset(tvb, DDP_HEADER_SIZE, -1, -1);

  if (!dissector_try_port(ddp_dissector_table, ddp.type, new_tvb, pinfo, tree))
    call_dissector(data_handle,new_tvb, pinfo, tree);
}

static const value_string llap_type_vals[] = {
  {0x01, "Short DDP"},
  {0x02, "DDP" },
  {0x81, "Enquiry"},
  {0x82, "Acknowledgement"},
  {0x84, "RTS"},
  {0x85, "CTS"},
  {0, NULL}
};

void
capture_llap(packet_counts *ld)
{
  ld->other++;
}

static void
dissect_llap(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
  guint8 dnode;
  guint8 snode;
  guint8 type;
  proto_tree *llap_tree = NULL;
  proto_item *ti;
  tvbuff_t   *new_tvb;

  if (check_col(pinfo->cinfo, COL_PROTOCOL))
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "LLAP");
  if (check_col(pinfo->cinfo, COL_INFO))
    col_clear(pinfo->cinfo, COL_INFO);

  if (tree) {
    ti = proto_tree_add_item(tree, proto_llap, tvb, 0, 3, FALSE);
    llap_tree = proto_item_add_subtree(ti, ett_llap);
  }

  dnode = tvb_get_guint8(tvb, 0);
  if (tree)
    proto_tree_add_uint(llap_tree, hf_llap_dst, tvb, 0, 1, dnode);
  snode = tvb_get_guint8(tvb, 1);
  if (tree)
    proto_tree_add_uint(llap_tree, hf_llap_src, tvb, 1, 1, snode);
  type = tvb_get_guint8(tvb, 2);
  if (check_col(pinfo->cinfo, COL_INFO)) {
    col_add_str(pinfo->cinfo, COL_INFO,
      val_to_str(type, llap_type_vals, "Unknown LLAP type (%02x)"));
  }
  if (tree)
    proto_tree_add_uint(llap_tree, hf_llap_type, tvb, 2, 1, type);

  new_tvb = tvb_new_subset(tvb, 3, -1, -1);

  switch (type) {

  case 0x01:
    if (proto_is_protocol_enabled(find_protocol_by_id(proto_ddp))) {
      pinfo->current_proto = "DDP";
      dissect_ddp_short(new_tvb, pinfo, dnode, snode, tree);
      return;
    }

  case 0x02:
    if (call_dissector(ddp_handle, new_tvb, pinfo, tree))
      return;
  }
  call_dissector(data_handle,new_tvb, pinfo, tree);
}

static void
atp_init(void)
{
  	/* fragment */
  	fragment_table_init(&atp_fragment_table);
  	reassembled_table_init(&atp_reassembled_table);
  	/* bitmap */
	if (atp_request_hash)
		g_hash_table_destroy(atp_request_hash);

	atp_request_hash = g_hash_table_new(asp_hash, asp_equal);

}

static void
asp_reinit( void)
{

	if (asp_request_hash)
		g_hash_table_destroy(asp_request_hash);

	asp_request_hash = g_hash_table_new(asp_hash, asp_equal);

}

void
proto_register_atalk(void)
{
  static hf_register_info hf_llap[] = {
    { &hf_llap_dst,
      { "Destination Node",	"llap.dst",	FT_UINT8,  BASE_DEC, NULL, 0x0,
      	"", HFILL }},

    { &hf_llap_src,
      { "Source Node",		"llap.src",	FT_UINT8,  BASE_DEC, NULL, 0x0,
      	"", HFILL }},

    { &hf_llap_type,
      { "Type",			"llap.type",	FT_UINT8,  BASE_HEX, VALS(llap_type_vals), 0x0,
      	"", HFILL }},
  };

  static hf_register_info hf_ddp[] = {
    { &hf_ddp_hopcount,
      { "Hop count",		"ddp.hopcount",	FT_UINT8,  BASE_DEC, NULL, 0x0,
      	"", HFILL }},

    { &hf_ddp_len,
      { "Datagram length",	"ddp.len",	FT_UINT16, BASE_DEC, NULL, 0x0,
      	"", HFILL }},

    { &hf_ddp_checksum,
      { "Checksum",		"ddp.checksum",	FT_UINT16, BASE_DEC, NULL, 0x0,
      	"", HFILL }},

    { &hf_ddp_dst,
      { "Destination address",	"ddp.dst",	FT_STRING, BASE_NONE, NULL, 0x0,
      	"", HFILL }},

    { &hf_ddp_dst_net,
      { "Destination Net",	"ddp.dst.net",	FT_UINT16, BASE_DEC, NULL, 0x0,
      	"", HFILL }},

    { &hf_ddp_src,
      { "Source address",	"ddp.src",	FT_STRING, BASE_NONE, NULL, 0x0,
      	"", HFILL }},

    { &hf_ddp_src_net,
      { "Source Net",		"ddp.src.net",	FT_UINT16, BASE_DEC, NULL, 0x0,
      	"", HFILL }},

    { &hf_ddp_dst_node,
      { "Destination Node",	"ddp.dst.node",	FT_UINT8,  BASE_DEC, NULL, 0x0,
      	"", HFILL }},

    { &hf_ddp_src_node,
      { "Source Node",		"ddp.src.node",	FT_UINT8,  BASE_DEC, NULL, 0x0,
      	"", HFILL }},

    { &hf_ddp_dst_socket,
      { "Destination Socket",	"ddp.dst_socket", FT_UINT8,  BASE_DEC, NULL, 0x0,
      	"", HFILL }},

    { &hf_ddp_src_socket,
      { "Source Socket",       	"ddp.src_socket", FT_UINT8,  BASE_DEC, NULL, 0x0,
      	"", HFILL }},

    { &hf_ddp_type,
      { "Protocol type",       	"ddp.type",	FT_UINT8,  BASE_DEC, VALS(op_vals), 0x0,
      	"", HFILL }},
  };

  static hf_register_info hf_nbp[] = {
    { &hf_nbp_op,
      { "Operation",		"nbp.op",	FT_UINT8,  BASE_DEC,
		VALS(nbp_op_vals), 0xF0, "Operation", HFILL }},
    { &hf_nbp_info,
      { "Info",		"nbp.info",	FT_UINT8,  BASE_HEX,
		NULL, 0x0, "Info", HFILL }},
    { &hf_nbp_count,
      { "Count",		"nbp.count",	FT_UINT8,  BASE_DEC,
		NULL, 0x0F, "Count", HFILL }},
    { &hf_nbp_node_net,
      { "Network",		"nbp.net",	FT_UINT16,  BASE_DEC,
		NULL, 0x0, "Network", HFILL }},
    { &hf_nbp_node_node,
      { "Node",		"nbp.node",	FT_UINT8,  BASE_DEC,
		NULL, 0x0, "Node", HFILL }},
    { &hf_nbp_node_port,
      { "Port",		"nbp.port",	FT_UINT8,  BASE_DEC,
		NULL, 0x0, "Port", HFILL }},
    { &hf_nbp_node_enum,
      { "Enumerator",		"nbp.enum",	FT_UINT8,  BASE_DEC,
		NULL, 0x0, "Enumerator", HFILL }},
    { &hf_nbp_node_object,
      { "Object",		"nbp.object",	FT_STRING,  BASE_DEC,
		NULL, 0x0, "Object", HFILL }},
    { &hf_nbp_node_type,
      { "Type",		"nbp.type",	FT_STRING,  BASE_DEC,
		NULL, 0x0, "Type", HFILL }},
    { &hf_nbp_node_zone,
      { "Zone",		"nbp.zone",	FT_STRING,  BASE_DEC,
		NULL, 0x0, "Zone", HFILL }},
    { &hf_nbp_tid,
      { "Transaction ID",		"nbp.tid",	FT_UINT8,  BASE_DEC,
		NULL, 0x0, "Transaction ID", HFILL }}
  };

  static hf_register_info hf_rtmp[] = {
    { &hf_rtmp_net,
      { "Net",		"rtmp.net",	FT_UINT16,  BASE_DEC,
		NULL, 0x0, "Net", HFILL }},
    { &hf_rtmp_node,
      { "Node",		"nbp.nodeid",	FT_UINT8,  BASE_DEC,
		NULL, 0x0, "Node", HFILL }},
    { &hf_rtmp_node_len,
      { "Node Length",		"nbp.nodeid.length",	FT_UINT8,  BASE_DEC,
		NULL, 0x0, "Node Length", HFILL }},
    { &hf_rtmp_tuple_net,
      { "Net",		"rtmp.tuple.net",	FT_UINT16,  BASE_DEC,
		NULL, 0x0, "Net", HFILL }},
    { &hf_rtmp_tuple_range_start,
      { "Range Start",		"rtmp.tuple.range_start",	FT_UINT16,  BASE_DEC,
		NULL, 0x0, "Range Start", HFILL }},
    { &hf_rtmp_tuple_range_end,
      { "Range End",		"rtmp.tuple.range_end",	FT_UINT16,  BASE_DEC,
		NULL, 0x0, "Range End", HFILL }},
    { &hf_rtmp_tuple_dist,
      { "Distance",		"rtmp.tuple.dist",	FT_UINT16,  BASE_DEC,
		NULL, 0x0, "Distance", HFILL }},
    { &hf_rtmp_function,
      { "Function",		"rtmp.function",	FT_UINT8,  BASE_DEC,
		VALS(rtmp_function_vals), 0x0, "Request Function", HFILL }}
  };

  static hf_register_info hf_atp[] = {
    { &hf_atp_ctrlinfo,
      { "Control info",		"atp.ctrlinfo",	FT_UINT8,  BASE_HEX,
		NULL, 0, "control info", HFILL }},

    { &hf_atp_function,
      { "Function",		"atp.function",	FT_UINT8,  BASE_DEC,
		VALS(atp_function_vals), ATP_FUNCMASK, "function code", HFILL }},


    { &hf_atp_xo,
      { "XO",		"atp.xo",	FT_BOOLEAN,  8,
		NULL, ATP_XO, "Exactly-once flag", HFILL }},

    { &hf_atp_eom,
      { "EOM",		"atp.eom",	FT_BOOLEAN,  8,
		NULL, ATP_EOM, "End-of-message", HFILL }},

    { &hf_atp_sts,
      { "STS",		"atp.sts",	FT_BOOLEAN,  8,
		NULL, ATP_STS, "Send transaction status", HFILL }},

    { &hf_atp_treltimer,
      { "TRel timer",		"atp.treltimer",	FT_UINT8,  BASE_DEC,
		VALS(atp_trel_timer_vals), 0x07, "TRel timer", HFILL }},

    { &hf_atp_bitmap,
      { "Bitmap",		"atp.bitmap",	FT_UINT8,  BASE_HEX,
		NULL, 0x0, "Bitmap or sequence number", HFILL }},

    { &hf_atp_tid,
      { "TID",			"atp.tid",	FT_UINT16,  BASE_DEC,
		NULL, 0x0, "Transaction id", HFILL }},
    { &hf_atp_user_bytes,
      { "User bytes",			"atp.user_bytes",	FT_UINT32,  BASE_HEX,
		NULL, 0x0, "User bytes", HFILL }},

    { &hf_atp_segment_overlap,
      { "Segment overlap",	"atp.segment.overlap", FT_BOOLEAN, BASE_NONE,
		NULL, 0x0, "Segment overlaps with other segments", HFILL }},

    { &hf_atp_segment_overlap_conflict,
      { "Conflicting data in segment overlap", "atp.segment.overlap.conflict",
	FT_BOOLEAN, BASE_NONE,
		NULL, 0x0, "Overlapping segments contained conflicting data", HFILL }},

    { &hf_atp_segment_multiple_tails,
      { "Multiple tail segments found", "atp.segment.multipletails",
	FT_BOOLEAN, BASE_NONE,
		NULL, 0x0, "Several tails were found when desegmenting the packet", HFILL }},

    { &hf_atp_segment_too_long_segment,
      { "Segment too long",	"atp.segment.toolongsegment", FT_BOOLEAN, BASE_NONE,
		NULL, 0x0, "Segment contained data past end of packet", HFILL }},

    { &hf_atp_segment_error,
      {"Desegmentation error",	"atp.segment.error", FT_FRAMENUM, BASE_NONE,
		NULL, 0x0, "Desegmentation error due to illegal segments", HFILL }},

    { &hf_atp_segment,
      { "ATP Fragment",		"atp.fragment", FT_FRAMENUM, BASE_NONE,
		NULL, 0x0, "ATP Fragment", HFILL }},

    { &hf_atp_segments,
      { "ATP Fragments",	"atp.fragments", FT_NONE, BASE_NONE,
		NULL, 0x0, "ATP Fragments", HFILL }},

    { &hf_atp_reassembled_in,
      { "Reassembled ATP in frame", "atp.reassembled_in", FT_FRAMENUM, BASE_NONE, NULL, 0x0,
       "This ATP packet is reassembled in this frame", HFILL }}
  };

  static hf_register_info hf_asp[] = {
    { &hf_asp_func,
      { "asp function",		"asp.function",	FT_UINT8,  BASE_DEC,
		VALS(asp_func_vals), 0, "asp function", HFILL }},

    { &hf_asp_error,
      { "asp error",		"asp.error",	FT_INT32,  BASE_DEC,
		VALS(asp_error_vals), 0, "return error code", HFILL }},

    { &hf_asp_version,
      { "Version",		"asp.version",	FT_UINT16,  BASE_HEX,
		NULL, 0, "asp version", HFILL }},

    { &hf_asp_attn_code,
      { "Attn code",		"asp.attn_code",	FT_UINT16,  BASE_HEX,
		NULL, 0, "asp attention code", HFILL }},

    { &hf_asp_init_error,
      { "Error",		"asp.init_error",	FT_UINT16,  BASE_DEC,
		NULL, 0, "asp init error", HFILL }},

    { &hf_asp_session_id,
      { "Session ID",		"asp.session_id", FT_UINT8,  BASE_DEC,
		NULL, 0, "asp session id", HFILL }},

    { &hf_asp_socket,
      { "Socket",		"asp.socket",	FT_UINT8,  BASE_DEC,
		NULL, 0, "asp socket", HFILL }},

    { &hf_asp_seq,
      { "Sequence",		"asp.seq",	FT_UINT16,  BASE_DEC,
		NULL, 0, "asp sequence number", HFILL }},

    { &hf_asp_size,
      { "size",		"asp.size",	FT_UINT16,  BASE_DEC,
		NULL, 0, "asp available size for reply", HFILL }},

    { &hf_asp_zero_value,
      { "Pad (0)",         "asp.zero_value",
	FT_BYTES, BASE_HEX, NULL, 0x0,
      	"Pad", HFILL }},

	/* asp ,dsi, afp */
    { &hf_asp_server_name,
      { "Server name",         "asp.server_name",
	FT_UINT_STRING, BASE_NONE, NULL, 0x0,
      	"Server name", HFILL }},

    { &hf_asp_server_type,
      { "Server type",         "asp.server_type",
	FT_UINT_STRING, BASE_NONE, NULL, 0x0,
      	"Server type", HFILL }},

    { &hf_asp_server_vers,
      { "AFP version",         "asp.server_vers",
	FT_UINT_STRING, BASE_NONE, NULL, 0x0,
      	"AFP version", HFILL }},

    { &hf_asp_server_uams,
      { "UAM",         "asp.server_uams",
	FT_UINT_STRING, BASE_NONE, NULL, 0x0,
      	"UAM", HFILL }},

    { &hf_asp_server_icon,
      { "Icon bitmap",         "asp.server_icon",
	FT_BYTES, BASE_HEX, NULL, 0x0,
      	"Server icon bitmap", HFILL }},

    { &hf_asp_server_directory,
      { "Directory service",         "asp.server_directory",
	FT_UINT_STRING, BASE_NONE, NULL, 0x0,
      	"Server directory service", HFILL }},

    { &hf_asp_server_signature,
      { "Server signature",         "asp.server_signature",
	FT_BYTES, BASE_HEX, NULL, 0x0,
      	"Server signature", HFILL }},

    { &hf_asp_server_flag,
      { "Flag",         "asp.server_flag",
	FT_UINT16, BASE_HEX, NULL, 0x0,
      	"Server capabilities flag", HFILL }},
    { &hf_asp_server_flag_copyfile,
      { "Support copyfile",      "asp.server_flag.copyfile",
		FT_BOOLEAN, 16, NULL, AFPSRVRINFO_COPY,
      	"Server support copyfile", HFILL }},
    { &hf_asp_server_flag_passwd,
      { "Support change password",      "asp.server_flag.passwd",
		FT_BOOLEAN, 16, NULL, AFPSRVRINFO_PASSWD,
      	"Server support change password", HFILL }},
    { &hf_asp_server_flag_no_save_passwd,
      { "Don't allow save password",      "asp.server_flag.no_save_passwd",
		FT_BOOLEAN, 16, NULL, AFPSRVRINFO_NOSAVEPASSWD,
      	"Don't allow save password", HFILL }},
    { &hf_asp_server_flag_srv_msg,
      { "Support server message",      "asp.server_flag.srv_msg",
		FT_BOOLEAN, 16, NULL, AFPSRVRINFO_SRVMSGS,
      	"Support server message", HFILL }},
    { &hf_asp_server_flag_srv_sig,
      { "Support server signature",      "asp.server_flag.srv_sig",
		FT_BOOLEAN, 16, NULL, AFPSRVRINFO_SRVSIGNATURE,
      	"Support server signature", HFILL }},
    { &hf_asp_server_flag_tcpip,
      { "Support TCP/IP",      "asp.server_flag.tcpip",
		FT_BOOLEAN, 16, NULL, AFPSRVRINFO_TCPIP,
      	"Server support TCP/IP", HFILL }},
    { &hf_asp_server_flag_notify,
      { "Support server notifications",      "asp.server_flag.notify",
		FT_BOOLEAN, 16, NULL, AFPSRVRINFO_SRVNOTIFY,
      	"Server support notifications", HFILL }},
    { &hf_asp_server_flag_reconnect,
      { "Support server reconnect",      "asp.server_flag.reconnect",
		FT_BOOLEAN, 16, NULL, AFPSRVRINFO_SRVRECONNECT,
      	"Server support reconnect", HFILL }},
    { &hf_asp_server_flag_directory,
      { "Support directory services",      "asp.server_flag.directory",
		FT_BOOLEAN, 16, NULL, AFPSRVRINFO_SRVDIRECTORY,
      	"Server support directory services", HFILL }},
    { &hf_asp_server_flag_utf8_name,
      { "Support UTF8 server name",      "asp.server_flag.utf8_name",
		FT_BOOLEAN, 16, NULL, AFPSRVRINFO_SRVUTF8,
      	"Server support UTF8 server name", HFILL }},
    { &hf_asp_server_flag_fast_copy,
      { "Support fast copy",      "asp.server_flag.fast_copy",
		FT_BOOLEAN, 16, NULL, AFPSRVRINFO_FASTBOZO,
      	"Server support fast copy", HFILL }},

    { &hf_asp_server_addr_len,
      { "Length",          "asp.server_addr.len",
	FT_UINT8, BASE_DEC, NULL, 0x0,
      	"Address length.", HFILL }},

    { &hf_asp_server_addr_type,
      { "Type",          "asp.server_addr.type",
	FT_UINT8, BASE_DEC, VALS(afp_server_addr_type_vals), 0x0,
      	"Address type.", HFILL }},

    { &hf_asp_server_addr_value,
      { "Value",          "asp.server_addr.value",
	FT_BYTES, BASE_HEX, NULL, 0x0,
      	"Address value", HFILL }},

    { &hf_asp_server_utf8_name_len,
      { "Server name length",         "asp.server_utf8_name_len",
	FT_UINT16, BASE_DEC, NULL, 0x0,
      	"UTF8 server name length", HFILL }},

    { &hf_asp_server_utf8_name,
      { "Server name (UTF8)",         "asp.server_utf8_name",
	FT_STRING, BASE_NONE, NULL, 0x0,
      	"Server name (UTF8)", HFILL }},
  };

  static hf_register_info hf_zip[] = {
    { &hf_zip_function,
      { "Function",	"zip.function",	FT_UINT8,  BASE_DEC, VALS(zip_function_vals), 0x0,
      	"ZIP function", HFILL }},

    { &hf_zip_zero_value,
      { "Pad (0)",      "zip.zero_value",FT_BYTES, BASE_HEX, NULL, 0x0,
      	"Pad", HFILL }},

    { &hf_zip_atp_function,
      { "Function",	"zip.atp_function", FT_UINT8,  BASE_DEC, VALS(zip_atp_function_vals), 0x0,
      	"", HFILL }},

    { &hf_zip_start_index,
      { "Start index",	"zip.start_index", FT_UINT16, BASE_DEC, NULL, 0x0,
      	"", HFILL }},

    { &hf_zip_count,
      { "Count",	"zip.count", FT_UINT16, BASE_DEC, NULL, 0x0,
      	"", HFILL }},

    { &hf_zip_network_count,
      { "Count",	"zip.network_count", FT_UINT8, BASE_DEC, NULL, 0x0,
      	"", HFILL }},
    { &hf_zip_network,
      { "Network","zip.network", FT_UINT16, BASE_DEC, NULL, 0x0,
      	"", HFILL }},
    { &hf_zip_network_start,
      { "Network start","zip.network_start", FT_UINT16, BASE_DEC, NULL, 0x0,
      	"", HFILL }},
    { &hf_zip_network_end,
      { "Network end",	"zip.network_end", FT_UINT16, BASE_DEC, NULL, 0x0,
      	"", HFILL }},

    { &hf_zip_flags,
      { "Flags",	"zip.flags", FT_BOOLEAN, 8, NULL, 0xC0,
      	"", HFILL }},

    { &hf_zip_last_flag,
      { "Last Flag",	"zip.last_flag", FT_BOOLEAN, 8, NULL, 0,
      	"Non zero if contains last zone name in the zone list", HFILL }},

    { &hf_zip_flags_zone_invalid,
      { "Zone invalid",	"zip.flags.zone_invalid", FT_BOOLEAN, 8, NULL, 0x80,
      	"", HFILL }},

    { &hf_zip_flags_use_broadcast,
      { "Use broadcast","zip.flags.use_broadcast", FT_BOOLEAN, 8, NULL, 0x40,
      	"", HFILL }},

    { &hf_zip_flags_only_one_zone,
      { "Only one zone","zip.flags.only_one_zone", FT_BOOLEAN, 8, NULL, 0x20,
      	"", HFILL }},

    { &hf_zip_zone_name,
      { "Zone",         "zip.zone_name", FT_UINT_STRING, BASE_NONE, NULL, 0x0,
      	"", HFILL }},

    { &hf_zip_default_zone,
      { "Default zone", "zip.default_zone",FT_UINT_STRING, BASE_NONE, NULL, 0x0,
      	"", HFILL }},

    { &hf_zip_multicast_length,
      { "Multicast length",	"zip.multicast_length", FT_UINT8,  BASE_DEC, NULL, 0x0,
      	"Multicast address length", HFILL }},

    { &hf_zip_multicast_address,
      { "Multicast address", "zip.multicast_address",FT_BYTES, BASE_HEX, NULL, 0x0,
      	"Multicast address", HFILL }},

  };

  static hf_register_info hf_pap[] = {
    { &hf_pap_connid,
      { "ConnID",	"pap.connid",	FT_UINT8,  BASE_DEC, NULL, 0x0,
      	"PAP connection ID", HFILL }},

    { &hf_pap_function,
      { "Function",	"pap.function",	FT_UINT8,  BASE_DEC, VALS(pap_function_vals), 0x0,
      	"PAP function", HFILL }},

    { &hf_pap_socket,
      { "Socket",	"pap.socket",	FT_UINT8,  BASE_DEC, NULL, 0x0,
      	"ATP responding socket number", HFILL }},

    { &hf_pap_quantum,
      { "Quantum",	"pap.quantum",	FT_UINT8,  BASE_DEC, NULL, 0x0,
      	"Flow quantum", HFILL }},

    { &hf_pap_waittime,
      { "Wait time",	"pap.quantum",	FT_UINT16,  BASE_DEC, NULL, 0x0,
      	"Wait time", HFILL }},

    { &hf_pap_result,
      { "Result",	"pap.quantum",	FT_UINT16,  BASE_DEC, NULL, 0x0,
      	"Result", HFILL }},

    { &hf_pap_seq,
      { "Sequence",	"pap.seq",	FT_UINT16,  BASE_DEC, NULL, 0x0,
      	"Sequence number", HFILL }},

    { &hf_pap_status,
      { "Status",	"pap.status",	FT_STRING,  BASE_DEC, NULL, 0x0, 
		"Printer status", HFILL }},
		
    { &hf_pap_eof,
      { "EOF",	"pap.eof", FT_BOOLEAN, BASE_NONE,
		NULL, 0x0, "EOF", HFILL }},
		
    { &hf_pap_pad,
      { "Pad",    	"pad.pad", 		FT_NONE,   BASE_NONE, NULL, 0,
		"Pad Byte",	HFILL }},

  };

  static gint *ett[] = {
  	&ett_llap,
	&ett_ddp,
	&ett_atp,
	&ett_atp_info,
	&ett_atp_segments,
	&ett_atp_segment,
	&ett_asp,
	&ett_pap,

	/* asp dsi afp */
	&ett_asp_status,
	&ett_asp_status_server_flag,
	&ett_asp_vers,
	&ett_asp_uams,
	&ett_asp_addr,
	&ett_asp_addr_line,
	&ett_asp_directory,
	&ett_asp_utf8_name,

	&ett_nbp,
	&ett_nbp_info,
	&ett_nbp_node,
	&ett_pstring,
	&ett_rtmp,
	&ett_rtmp_tuple,

	&ett_zip,
	&ett_zip_flags,
        &ett_zip_zones_list,
        &ett_zip_network_list,
  };
  module_t *atp_module;

  proto_llap = proto_register_protocol("LocalTalk Link Access Protocol", "LLAP", "llap");
  proto_register_field_array(proto_llap, hf_llap, array_length(hf_llap));

  proto_ddp = proto_register_protocol("Datagram Delivery Protocol", "DDP", "ddp");
  proto_register_field_array(proto_ddp, hf_ddp, array_length(hf_ddp));

  proto_nbp = proto_register_protocol("Name Binding Protocol", "NBP", "nbp");
  proto_register_field_array(proto_nbp, hf_nbp, array_length(hf_nbp));

  proto_atp = proto_register_protocol("AppleTalk Transaction Protocol packet", "ATP", "atp");
  proto_register_field_array(proto_atp, hf_atp, array_length(hf_atp));

  proto_asp = proto_register_protocol("AppleTalk Session Protocol", "ASP", "asp");
  proto_register_field_array(proto_asp, hf_asp, array_length(hf_asp));

  proto_pap = proto_register_protocol("Printer Access Protocol", "PAP", "apap");
  proto_register_field_array(proto_pap, hf_pap, array_length(hf_pap));

  proto_zip = proto_register_protocol("Zone Information Protocol", "ZIP", "zip");
  proto_register_field_array(proto_zip, hf_zip, array_length(hf_zip));

  atp_module = prefs_register_protocol(proto_atp, NULL);
  prefs_register_bool_preference(atp_module, "desegment",
    "Reassemble ATP messages spanning multiple DDP packets",
    "Whether the ATP dissector should reassemble messages spanning multiple DDP packets",
    &atp_defragment);

  proto_rtmp = proto_register_protocol("Routing Table Maintenance Protocol",
				       "RTMP", "rtmp");
  proto_register_field_array(proto_rtmp, hf_rtmp, array_length(hf_rtmp));

  proto_register_subtree_array(ett, array_length(ett));

  /* subdissector code */
  ddp_dissector_table = register_dissector_table("ddp.type", "DDP packet type",
						 FT_UINT8, BASE_HEX);
}

void
proto_reg_handoff_atalk(void)
{
  dissector_handle_t nbp_handle, rtmp_request_handle;
  dissector_handle_t atp_handle;
  dissector_handle_t zip_ddp_handle;
  dissector_handle_t rtmp_data_handle, llap_handle;

  ddp_handle = create_dissector_handle(dissect_ddp, proto_ddp);
  dissector_add("ethertype", ETHERTYPE_ATALK, ddp_handle);
  dissector_add("chdlctype", ETHERTYPE_ATALK, ddp_handle);
  dissector_add("ppp.protocol", PPP_AT, ddp_handle);
  dissector_add("null.type", BSD_AF_APPLETALK, ddp_handle);
  dissector_add("arcnet.protocol_id", ARCNET_PROTO_APPLETALK, ddp_handle);

  nbp_handle = create_dissector_handle(dissect_nbp, proto_nbp);
  dissector_add("ddp.type", DDP_NBP, nbp_handle);

  atp_handle = create_dissector_handle(dissect_atp, proto_atp);
  dissector_add("ddp.type", DDP_ATP, atp_handle);

  asp_handle = create_dissector_handle(dissect_asp, proto_asp);
  pap_handle = create_dissector_handle(dissect_pap, proto_pap);

  rtmp_request_handle = create_dissector_handle(dissect_rtmp_request, proto_rtmp);
  rtmp_data_handle = create_dissector_handle(dissect_rtmp_data, proto_rtmp);
  dissector_add("ddp.type", DDP_RTMPREQ, rtmp_request_handle);
  dissector_add("ddp.type", DDP_RTMPDATA, rtmp_data_handle);

  zip_ddp_handle = create_dissector_handle(dissect_ddp_zip, proto_zip);
  dissector_add("ddp.type", DDP_ZIP, zip_ddp_handle);

  zip_atp_handle = create_dissector_handle(dissect_atp_zip, proto_zip);

  llap_handle = create_dissector_handle(dissect_llap, proto_llap);
  dissector_add("wtap_encap", WTAP_ENCAP_LOCALTALK, llap_handle);

  register_init_routine( atp_init);
  register_init_routine( &asp_reinit);

  afp_handle = find_dissector("afp");
  data_handle = find_dissector("data");
}
