/* TODO: fixup LUN tracking so we can pass the proper LUN across to
   dissect_scsi_xxx()
*/
/* packet-ndmp.c
 * Routines for NDMP dissection
 * 2001 Ronnie Sahlberg (see AUTHORS for email)
 *
 * $Id: packet-ndmp.c 14228 2005-04-29 08:02:44Z sahlberg $
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@ethereal.com>
 * Copyright 1998 Gerald Combs
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

/* see www.ndmp.org for protocol specifications.
   this file implements version 3 of ndmp
*/

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

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

#include <epan/packet.h>
#include <epan/conversation.h>
#include "packet-rpc.h"
#include "packet-tcp.h"
#include "packet-scsi.h"
#include "packet-frame.h"
#include <epan/prefs.h>
#include <epan/reassemble.h>
#include "rpc_defrag.h"

#define TCP_PORT_NDMP 10000
#define NDMP_RM_LASTFRAG 0x80000000
#define NDMP_RM_LENGTH   0x7fffffff

static int proto_ndmp = -1;
static int hf_ndmp_lastfrag = -1;
static int hf_ndmp_fraglen = -1;
static int hf_ndmp_version = -1;
static int hf_ndmp_header = -1;
static int hf_ndmp_sequence = -1;
static int hf_ndmp_reply_sequence = -1;
static int hf_ndmp_timestamp = -1;
static int hf_ndmp_msgtype = -1;
static int hf_ndmp_msg = -1;
static int hf_ndmp_error = -1;
static int hf_ndmp_hostname = -1;
static int hf_ndmp_os_type = -1;
static int hf_ndmp_os_vers = -1;
static int hf_ndmp_hostid = -1;
static int hf_ndmp_addr_types = -1;
static int hf_ndmp_addr_type = -1;
static int hf_ndmp_auth_type = -1;
static int hf_ndmp_auth_types = -1;
static int hf_ndmp_auth_challenge = -1;
static int hf_ndmp_auth_digest = -1;
static int hf_ndmp_auth_id = -1;
static int hf_ndmp_auth_password = -1;
static int hf_ndmp_butype_info = -1;
static int hf_ndmp_butype_name = -1;
static int hf_ndmp_butype_default_env = -1;
static int hf_ndmp_butype_attr_backup_file_history = -1;
static int hf_ndmp_butype_attr_backup_filelist = -1;
static int hf_ndmp_butype_attr_recover_filelist = -1;
static int hf_ndmp_butype_attr_backup_direct = -1;
static int hf_ndmp_butype_attr_recover_direct = -1;
static int hf_ndmp_butype_attr_backup_incremental = -1;
static int hf_ndmp_butype_attr_recover_incremental = -1;
static int hf_ndmp_butype_attr_backup_utf8 = -1;
static int hf_ndmp_butype_attr_recover_utf8 = -1;
static int hf_ndmp_butype_env_name = -1;
static int hf_ndmp_butype_env_value = -1;
static int hf_ndmp_tcp_env_name = -1;
static int hf_ndmp_tcp_env_value = -1;
static int hf_ndmp_tcp_default_env = -1;
static int hf_ndmp_tcp_addr_list = -1;
static int hf_ndmp_fs_info = -1;
static int hf_ndmp_fs_invalid_total_size = -1;
static int hf_ndmp_fs_invalid_used_size = -1;
static int hf_ndmp_fs_invalid_avail_size = -1;
static int hf_ndmp_fs_invalid_total_inodes = -1;
static int hf_ndmp_fs_invalid_used_inodes = -1;
static int hf_ndmp_fs_fs_type = -1;
static int hf_ndmp_fs_logical_device = -1;
static int hf_ndmp_fs_physical_device = -1;
static int hf_ndmp_fs_total_size = -1;
static int hf_ndmp_fs_used_size = -1;
static int hf_ndmp_fs_avail_size = -1;
static int hf_ndmp_fs_total_inodes = -1;
static int hf_ndmp_fs_used_inodes = -1;
static int hf_ndmp_fs_env = -1;
static int hf_ndmp_fs_env_name = -1;
static int hf_ndmp_fs_env_value = -1;
static int hf_ndmp_fs_status = -1;
static int hf_ndmp_tape_info = -1;
static int hf_ndmp_tape_model = -1;
static int hf_ndmp_tape_dev_cap = -1;
static int hf_ndmp_tape_device = -1;
static int hf_ndmp_tape_open_mode = -1;
static int hf_ndmp_tape_attr_rewind = -1;
static int hf_ndmp_tape_attr_unload = -1;
static int hf_ndmp_tape_capability = -1;
static int hf_ndmp_tape_capability_name = -1;
static int hf_ndmp_tape_capability_value = -1;
static int hf_ndmp_scsi_info = -1;
static int hf_ndmp_scsi_model = -1;
static int hf_ndmp_server_vendor = -1;
static int hf_ndmp_server_product = -1;
static int hf_ndmp_server_revision = -1;
static int hf_ndmp_scsi_device = -1;
static int hf_ndmp_scsi_controller = -1;
static int hf_ndmp_scsi_id = -1;
static int hf_ndmp_scsi_lun = -1;
static int hf_ndmp_execute_cdb_flags_data_in = -1;
static int hf_ndmp_execute_cdb_flags_data_out = -1;
static int hf_ndmp_execute_cdb_timeout = -1;
static int hf_ndmp_execute_cdb_datain_len = -1;
static int hf_ndmp_execute_cdb_cdb_len = -1;
static int hf_ndmp_execute_cdb_dataout = -1;
static int hf_ndmp_execute_cdb_status = -1;
static int hf_ndmp_execute_cdb_dataout_len = -1;
static int hf_ndmp_execute_cdb_datain = -1;
static int hf_ndmp_execute_cdb_sns_len = -1;
static int hf_ndmp_tape_invalid_file_num = -1;
static int hf_ndmp_tape_invalid_soft_errors = -1;
static int hf_ndmp_tape_invalid_block_size = -1;
static int hf_ndmp_tape_invalid_block_no = -1;
static int hf_ndmp_tape_invalid_total_space = -1;
static int hf_ndmp_tape_invalid_space_remain = -1;
static int hf_ndmp_tape_invalid_partition = -1;
static int hf_ndmp_tape_flags_no_rewind = -1;
static int hf_ndmp_tape_flags_write_protect = -1;
static int hf_ndmp_tape_flags_error = -1;
static int hf_ndmp_tape_flags_unload = -1;
static int hf_ndmp_tape_file_num = -1;
static int hf_ndmp_tape_soft_errors = -1;
static int hf_ndmp_tape_block_size = -1;
static int hf_ndmp_tape_block_no = -1;
static int hf_ndmp_tape_total_space = -1;
static int hf_ndmp_tape_space_remain = -1;
static int hf_ndmp_tape_partition = -1;
static int hf_ndmp_tape_mtio_op = -1;
static int hf_ndmp_count = -1;
static int hf_ndmp_resid_count = -1;
static int hf_ndmp_mover_state = -1;
static int hf_ndmp_mover_pause = -1;
static int hf_ndmp_halt = -1;
static int hf_ndmp_halt_reason = -1;
static int hf_ndmp_record_size = -1;
static int hf_ndmp_record_num = -1;
static int hf_ndmp_data_written = -1;
static int hf_ndmp_seek_position = -1;
static int hf_ndmp_bytes_left_to_read = -1;
static int hf_ndmp_window_offset = -1;
static int hf_ndmp_window_length = -1;
static int hf_ndmp_addr_ip = -1;
static int hf_ndmp_addr_tcp = -1;
static int hf_ndmp_addr_fcal_loop_id = -1;
static int hf_ndmp_addr_ipc = -1;
static int hf_ndmp_mover_mode = -1;
static int hf_ndmp_file_name = -1;
static int hf_ndmp_nt_file_name = -1;
static int hf_ndmp_dos_file_name = -1;
static int hf_ndmp_log_type = -1;
static int hf_ndmp_log_message_id = -1;
static int hf_ndmp_log_message = -1;
static int hf_ndmp_connected = -1;
static int hf_ndmp_connected_reason = -1;
static int hf_ndmp_data = -1;
static int hf_ndmp_files = -1;
static int hf_ndmp_file_fs_type = -1;
static int hf_ndmp_file_names = -1;
static int hf_ndmp_file_stats = -1;
static int hf_ndmp_file_node = -1;
static int hf_ndmp_file_parent = -1;
static int hf_ndmp_file_fh_info = -1;
static int hf_ndmp_file_invalid_atime = -1;
static int hf_ndmp_file_invalid_ctime = -1;
static int hf_ndmp_file_invalid_group = -1;
static int hf_ndmp_file_type = -1;
static int hf_ndmp_file_mtime = -1;
static int hf_ndmp_file_atime = -1;
static int hf_ndmp_file_ctime = -1;
static int hf_ndmp_file_owner = -1;
static int hf_ndmp_file_group = -1;
static int hf_ndmp_file_fattr = -1;
static int hf_ndmp_file_size = -1;
static int hf_ndmp_file_links = -1;
static int hf_ndmp_dirs = -1;
static int hf_ndmp_nodes = -1;
static int hf_ndmp_nlist = -1;
static int hf_ndmp_bu_original_path = -1;
static int hf_ndmp_bu_destination_dir = -1;
static int hf_ndmp_bu_new_name = -1;
static int hf_ndmp_bu_other_name = -1;
static int hf_ndmp_state_invalid_ebr = -1;
static int hf_ndmp_state_invalid_etr = -1;
static int hf_ndmp_bu_operation = -1;
static int hf_ndmp_data_state = -1;
static int hf_ndmp_data_halted = -1;
static int hf_ndmp_data_bytes_processed = -1;
static int hf_ndmp_data_est_bytes_remain = -1;
static int hf_ndmp_data_est_time_remain = -1;

static gint ett_ndmp = -1;
static gint ett_ndmp_fraghdr = -1;
static gint ett_ndmp_header = -1;
static gint ett_ndmp_butype_attrs = -1;
static gint ett_ndmp_fs_invalid = -1;
static gint ett_ndmp_tape_attr = -1;
static gint ett_ndmp_execute_cdb_flags = -1;
static gint ett_ndmp_execute_cdb_cdb = -1;
static gint ett_ndmp_execute_cdb_sns = -1;
static gint ett_ndmp_execute_cdb_payload = -1;
static gint ett_ndmp_tape_invalid = -1;
static gint ett_ndmp_tape_flags = -1;
static gint ett_ndmp_addr = -1;
static gint ett_ndmp_file = -1;
static gint ett_ndmp_file_name = -1;
static gint ett_ndmp_file_stats = -1;
static gint ett_ndmp_file_invalids = -1;
static gint ett_ndmp_state_invalids = -1;

static struct true_false_string yesno = { "Yes", "No" };

/* XXX someone should start adding the new stuff from v3, v4 and v5*/
#define NDMP_PROTOCOL_V2	1
#define NDMP_PROTOCOL_V3	2
#define NDMP_PROTOCOL_V4	3
#define NDMP_PROTOCOL_V5	4

static enum_val_t ndmp_protocol_versions[] = {
	{ "version2",	"Version 2",	NDMP_PROTOCOL_V2 },
	{ "version3",	"Version 3",	NDMP_PROTOCOL_V3 },
	{ "version4",	"Version 4",	NDMP_PROTOCOL_V4 },
	{ "version5",	"Version 5",	NDMP_PROTOCOL_V5 },
	{ NULL, NULL, 0 }
};

static gint ndmp_protocol_version = NDMP_PROTOCOL_V2;


struct ndmp_header {
	guint32	seq;
	guint32 time;
	guint32 type;
	guint32 msg;
	guint32 rep_seq;
	guint32 err;
};

/* desegmentation of NDMP packets */
static gboolean ndmp_desegment = TRUE;

/* defragmentation of fragmented NDMP records */
static gboolean ndmp_defragment = FALSE;

#define NDMP_MESSAGE_REQUEST	0x00
#define NDMP_MESSAGE_REPLY	0x01
static const value_string msg_type_vals[] = {
	{NDMP_MESSAGE_REQUEST,		"Request"},
	{NDMP_MESSAGE_REPLY,		"Reply"},
	{0, NULL}
};

#define NDMP_NO_ERR			0x00
#define NDMP_NOT_SUPPORTED_ERR		0x01
#define NDMP_DEVICE_BUSY_ERR		0x02
#define NDMP_DEVICE_OPENED_ERR		0x03
#define NDMP_NOT_AUTHORIZED_ERR		0x04
#define NDMP_PERMISSION_ERR		0x05
#define NDMP_DEV_NOT_OPEN_ERR		0x06
#define NDMP_IO_ERR			0x07
#define NDMP_TIMEOUT_ERR		0x08
#define NDMP_ILLEGAL_ARGS_ERR		0x09
#define NDMP_NO_TAPE_LOADED_ERR		0x0a
#define NDMP_WRITE_PROTECT_ERR		0x0b
#define NDMP_EOF_ERR			0x0c
#define NDMP_EOM_ERR			0x0d
#define NDMP_FILE_NOT_FOUND_ERR		0x0e
#define NDMP_BAD_FILE_ERR		0x0f
#define NDMP_NO_DEVICE_ERR		0x10
#define NDMP_NO_BUS_ERR			0x11
#define NDMP_XDR_DECODE_ERR		0x12
#define NDMP_ILLEGAL_STATE_ERR		0x13
#define NDMP_UNDEFINED_ERR		0x14
#define NDMP_XDR_ENCODE_ERR		0x15
#define NDMP_NO_MEM_ERR			0x16
#define NDMP_CONNECT_ERR		0x17

static const value_string error_vals[] = {
	{NDMP_NO_ERR,			"NO_ERR"},
	{NDMP_NOT_SUPPORTED_ERR,	"NOT_SUPPORTED_ERR"},
	{NDMP_DEVICE_BUSY_ERR,		"DEVICE_BUSY_ERR"},
	{NDMP_DEVICE_OPENED_ERR,	"DEVICE_OPENED_ERR"},
	{NDMP_NOT_AUTHORIZED_ERR,	"NOT_AUTHORIZED_ERR"},
	{NDMP_PERMISSION_ERR,		"PERMISSION_ERR"},
	{NDMP_DEV_NOT_OPEN_ERR,		"DEV_NOT_OPEN_ERR"},
	{NDMP_IO_ERR,			"IO_ERR"},
	{NDMP_TIMEOUT_ERR,		"TIMEOUT_ERR"},
	{NDMP_ILLEGAL_ARGS_ERR,		"ILLEGAL_ARGS_ERR"},
	{NDMP_NO_TAPE_LOADED_ERR,	"NO_TAPE_LOADED_ERR"},
	{NDMP_WRITE_PROTECT_ERR,	"WRITE_PROTECT_ERR"},
	{NDMP_EOF_ERR,			"EOF_ERR"},
	{NDMP_EOM_ERR,			"EOM_ERR"},
	{NDMP_FILE_NOT_FOUND_ERR,	"FILE_NOT_FOUND_ERR"},
	{NDMP_BAD_FILE_ERR,		"BAD_FILE_ERR"},
	{NDMP_NO_DEVICE_ERR,		"NO_DEVICE_ERR"},
	{NDMP_NO_BUS_ERR,		"NO_BUS_ERR"},
	{NDMP_XDR_DECODE_ERR,		"XDR_DECODE_ERR"},
	{NDMP_ILLEGAL_STATE_ERR,	"ILLEGAL_STATE_ERR"},
	{NDMP_UNDEFINED_ERR,		"UNDEFINED_ERR"},
	{NDMP_XDR_ENCODE_ERR,		"XDR_ENCODE_ERR"},
	{NDMP_NO_MEM_ERR,		"NO_MEM_ERR"},
	{NDMP_CONNECT_ERR,		"CONNECT_ERR"},
	{0, NULL}
};



#define NDMP_CONFIG_GET_HOST_INFO 	0x100
#define NDMP_CONFIG_GET_CONNECTION_TYPE 0x102
#define NDMP_CONFIG_GET_AUTH_ATTR 	0x103
#define NDMP_CONFIG_GET_BUTYPE_INFO 	0x104
#define NDMP_CONFIG_GET_FS_INFO 	0x105
#define NDMP_CONFIG_GET_TAPE_INFO 	0x106
#define NDMP_CONFIG_GET_SCSI_INFO 	0x107
#define NDMP_CONFIG_GET_SERVER_INFO 	0x108
#define NDMP_SCSI_OPEN 			0x200
#define NDMP_SCSI_CLOSE 		0x201
#define NDMP_SCSI_GET_STATE 		0x202
#define NDMP_SCSI_SET_TARGET 		0x203
#define NDMP_SCSI_RESET_DEVICE 		0x204
#define NDMP_SCSI_RESET_BUS 		0x205
#define NDMP_SCSI_EXECUTE_CDB 		0x206
#define NDMP_TAPE_OPEN 			0x300
#define NDMP_TAPE_CLOSE 		0x301
#define NDMP_TAPE_GET_STATE 		0x302
#define NDMP_TAPE_MTIO 			0x303
#define NDMP_TAPE_WRITE 		0x304
#define NDMP_TAPE_READ 			0x305
#define NDMP_TAPE_EXECUTE_CDB 		0x307
#define NDMP_DATA_GET_STATE 		0x400
#define NDMP_DATA_START_BACKUP 		0x401
#define NDMP_DATA_START_RECOVER 	0x402
#define NDMP_DATA_ABORT 		0x403
#define NDMP_DATA_GET_ENV 		0x404
#define NDMP_DATA_STOP 			0x407
#define NDMP_DATA_LISTEN 		0x409
#define NDMP_DATA_CONNECT 		0x40a
#define NDMP_NOTIFY_DATA_HALTED 	0x501
#define NDMP_NOTIFY_CONNECTED 		0x502
#define NDMP_NOTIFY_MOVER_HALTED 	0x503
#define NDMP_NOTIFY_MOVER_PAUSED 	0x504
#define NDMP_NOTIFY_DATA_READ 		0x505
#define NDMP_LOG_FILE 			0x602
#define NDMP_LOG_MESSAGE 		0x603
#define NDMP_FH_ADD_FILE 		0x703
#define NDMP_FH_ADD_DIR 		0x704
#define NDMP_FH_ADD_NODE 		0x705
#define NDMP_CONNECT_OPEN 		0x900
#define NDMP_CONNECT_CLIENT_AUTH	0x901
#define NDMP_CONNECT_CLOSE 		0x902
#define NDMP_CONNECT_SERVER_AUTH 	0x903
#define NDMP_MOVER_GET_STATE 		0xa00
#define NDMP_MOVER_LISTEN 		0xa01
#define NDMP_MOVER_CONTINUE 		0xa02
#define NDMP_MOVER_ABORT 		0xa03
#define NDMP_MOVER_STOP 		0xa04
#define NDMP_MOVER_SET_WINDOW 		0xa05
#define NDMP_MOVER_READ 		0xa06
#define NDMP_MOVER_CLOSE 		0xa07
#define NDMP_MOVER_SET_RECORD_SIZE 	0xa08
#define NDMP_MOVER_CONNECT 		0xa09




static const value_string msg_vals[] = {
	{NDMP_CONFIG_GET_HOST_INFO, 	"CONFIG_GET_HOST_INFO"},
	{NDMP_CONFIG_GET_CONNECTION_TYPE, "CONFIG_GET_CONNECTION_TYPE"},
	{NDMP_CONFIG_GET_AUTH_ATTR, 	"CONFIG_GET_AUTH_ATTR"},
	{NDMP_CONFIG_GET_BUTYPE_INFO, 	"CONFIG_GET_BUTYPE_INFO"},
	{NDMP_CONFIG_GET_FS_INFO, 	"CONFIG_GET_FS_INFO"},
	{NDMP_CONFIG_GET_TAPE_INFO, 	"CONFIG_GET_TAPE_INFO"},
	{NDMP_CONFIG_GET_SCSI_INFO, 	"CONFIG_GET_SCSI_INFO"},
	{NDMP_CONFIG_GET_SERVER_INFO, 	"CONFIG_GET_SERVER_INFO"},
	{NDMP_SCSI_OPEN, 		"SCSI_OPEN"},
	{NDMP_SCSI_CLOSE, 		"SCSI_CLOSE"},
	{NDMP_SCSI_GET_STATE, 		"SCSI_GET_STATE"},
	{NDMP_SCSI_SET_TARGET, 		"SCSI_SET_TARGET"},
	{NDMP_SCSI_RESET_DEVICE, 	"SCSI_RESET_DEVICE"},
	{NDMP_SCSI_RESET_BUS, 		"SCSI_RESET_BUS"},
	{NDMP_SCSI_EXECUTE_CDB, 	"SCSI_EXECUTE_CDB"},
	{NDMP_TAPE_OPEN, 		"TAPE_OPEN"},
	{NDMP_TAPE_CLOSE, 		"TAPE_CLOSE"},
	{NDMP_TAPE_GET_STATE, 		"TAPE_GET_STATE"},
	{NDMP_TAPE_MTIO, 		"TAPE_MTIO"},
	{NDMP_TAPE_WRITE, 		"TAPE_WRITE"},
	{NDMP_TAPE_READ, 		"TAPE_READ"},
	{NDMP_TAPE_EXECUTE_CDB, 	"TAPE_EXECUTE_CDB"},
	{NDMP_DATA_GET_STATE, 		"DATA_GET_STATE"},
	{NDMP_DATA_START_BACKUP, 	"DATA_START_BACKUP"},
	{NDMP_DATA_START_RECOVER, 	"DATA_START_RECOVER"},
	{NDMP_DATA_ABORT, 		"DATA_ABORT"},
	{NDMP_DATA_GET_ENV, 		"DATA_GET_ENV"},
	{NDMP_DATA_STOP, 		"DATA_STOP"},
	{NDMP_DATA_LISTEN, 		"DATA_LISTEN"},
	{NDMP_DATA_CONNECT, 		"DATA_CONNECT"},
	{NDMP_NOTIFY_DATA_HALTED, 	"NOTIFY_DATA_HALTED"},
	{NDMP_NOTIFY_CONNECTED, 	"NOTIFY_CONNECTED"},
	{NDMP_NOTIFY_MOVER_HALTED, 	"NOTIFY_MOVER_HALTED"},
	{NDMP_NOTIFY_MOVER_PAUSED, 	"NOTIFY_MOVER_PAUSED"},
	{NDMP_NOTIFY_DATA_READ, 	"NOTIFY_DATA_READ"},
	{NDMP_LOG_FILE, 		"LOG_FILE"},
	{NDMP_LOG_MESSAGE, 		"LOG_MESSAGE"},
	{NDMP_FH_ADD_FILE, 		"FH_ADD_FILE"},
	{NDMP_FH_ADD_DIR, 		"FH_ADD_DIR"},
	{NDMP_FH_ADD_NODE, 		"FH_ADD_NODE"},
	{NDMP_CONNECT_OPEN, 		"CONNECT_OPEN"},
	{NDMP_CONNECT_CLIENT_AUTH, 	"CONNECT_CLIENT_AUTH"},
	{NDMP_CONNECT_CLOSE, 		"CONNECT_CLOSE"},
	{NDMP_CONNECT_SERVER_AUTH, 	"CONNECT_SERVER_AUTH"},
	{NDMP_MOVER_GET_STATE, 		"MOVER_GET_STATE"},
	{NDMP_MOVER_LISTEN, 		"MOVER_LISTEN"},
	{NDMP_MOVER_CONTINUE, 		"MOVER_CONTINUE"},
	{NDMP_MOVER_ABORT, 		"MOVER_ABORT"},
	{NDMP_MOVER_STOP, 		"MOVER_STOP"},
	{NDMP_MOVER_SET_WINDOW, 	"MOVER_SET_WINDOW"},
	{NDMP_MOVER_READ, 		"MOVER_READ"},
	{NDMP_MOVER_CLOSE, 		"MOVER_CLOSE"},
	{NDMP_MOVER_SET_RECORD_SIZE, 	"MOVER_SET_RECORD_SIZE"},
	{NDMP_MOVER_CONNECT, 		"MOVER_CONNECT"},
	{0, NULL}
};

static int
dissect_connect_open_request(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *tree, guint32 seq _U_)
{
	/* version number */
	proto_tree_add_item(tree, hf_ndmp_version, tvb, offset, 4, FALSE);
	offset += 4;

	return offset;
}

static int
dissect_error(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq _U_)
{
	guint32 err;

	/* error */
	err=tvb_get_ntohl(tvb, offset);
	proto_tree_add_item(tree, hf_ndmp_error, tvb, offset, 4, FALSE);
	if(err && check_col(pinfo->cinfo, COL_INFO)) {
		col_append_fstr(pinfo->cinfo, COL_INFO, 
			" NDMP Error:%s",
			val_to_str(err, error_vals,
			"Unknown NDMP error code %#x"));
	}
	
	offset += 4;

	return offset;
}

static int
dissect_ndmp_get_host_info_reply(tvbuff_t *tvb, int offset,
    packet_info *pinfo, proto_tree *tree, guint32 seq)
{
	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, seq);

	/* hostname */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_hostname, offset, NULL);

	/* os type */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_os_type, offset, NULL);

	/* os version */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_os_vers, offset, NULL);

	/* hostid */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_hostid, offset, NULL);

	return offset;
}

#define NDMP_ADDR_LOCAL		0
#define NDMP_ADDR_TCP		1
#define NDMP_ADDR_FC		2
#define NDMP_ADDR_IPC		3
static const value_string addr_type_vals[] = {
	{NDMP_ADDR_LOCAL,	"Local"},
	{NDMP_ADDR_TCP,		"TCP"},
	{NDMP_ADDR_FC,		"FC"},
	{NDMP_ADDR_IPC,		"IPC"},
	{0,NULL}
};

static int
dissect_ndmp_addr_type(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *tree)
{
	proto_tree_add_item(tree, hf_ndmp_addr_type, tvb, offset, 4, FALSE);
	offset += 4;

	return offset;
}

static int
dissect_ndmp_addr_msg(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq _U_)
{
	/*address type*/
	return dissect_ndmp_addr_type(tvb, offset, pinfo, tree);
}

static int
dissect_ndmp_config_get_connection_type_reply(tvbuff_t *tvb, int offset,
    packet_info *pinfo, proto_tree *tree, guint32 seq)
{
	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, seq);

	/* addr types */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_ndmp_addr_type, hf_ndmp_addr_types);

	return offset;
}

#define NDMP_AUTH_NONE		0
#define NDMP_AUTH_TEXT		1
#define NDMP_AUTH_MD5		2
static const value_string auth_type_vals[] = {
	{NDMP_AUTH_NONE,	"None"},
	{NDMP_AUTH_TEXT,	"Text"},
	{NDMP_AUTH_MD5,		"MD5"},
	{0,NULL}
};
static int
dissect_auth_type(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *tree)
{
	proto_tree_add_item(tree, hf_ndmp_auth_type, tvb, offset, 4, FALSE);
	offset += 4;

	return offset;
}

static int
dissect_get_auth_type_request(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq _U_)
{
	/* auth type */
	return dissect_auth_type(tvb, offset, pinfo, tree);
}

static int
dissect_auth_attr_msg(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *tree, guint32 seq _U_)
{
	guint type;

	type=tvb_get_ntohl(tvb,offset);

	/* auth type */
	proto_tree_add_item(tree, hf_ndmp_auth_type, tvb, offset, 4, FALSE);
	offset += 4;

	switch(type){
	case NDMP_AUTH_NONE:
		break;
	case NDMP_AUTH_TEXT:
		break;
	case NDMP_AUTH_MD5:
		proto_tree_add_item(tree, hf_ndmp_auth_challenge,
			tvb, offset, 64, FALSE);
		offset+=64;
	}

	return offset;
}

static int
dissect_default_env(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *tree)
{
	/* name */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_butype_env_name, offset, NULL);

	/* value */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_butype_env_value, offset, NULL);

	return offset;
}


static const true_false_string tfs_butype_attr_backup_file_history = {
	"Backup FILE HISTORY",
	"Do NOT backup file history"
};
static const true_false_string tfs_butype_attr_backup_filelist = {
	"Backup FILELIST",
	"Do NOT backup filelist"
};
static const true_false_string tfs_butype_attr_recover_filelist = {
	"Recover FILELIST",
	"Do NOT recover filelist"
};
static const true_false_string tfs_butype_attr_backup_direct = {
	"Perform DIRECT backup",
	"Do NOT perform direct backup"
};
static const true_false_string tfs_butype_attr_recover_direct = {
	"Perform DIRECT recovery",
	"Do NOT perform direct recovery"
};
static const true_false_string tfs_butype_attr_backup_incremental = {
	"Perform INCREMENTAL backup",
	"Perform FULL backup"
};
static const true_false_string tfs_butype_attr_recover_incremental = {
	"Perform INCREMENTAL revocery",
	"Perform FULL recovery"
};
static const true_false_string tfs_butype_attr_backup_utf8 = {
	"Backup using UTF8",
	"Normal backup. Do NOT use utf8"
};
static const true_false_string tfs_butype_attr_recover_utf8 = {
	"Recover using UTF8",
	"Normal recover. Do NOT use utf8"
};
static int
dissect_butype_attrs(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *parent_tree)
{
	proto_item* item = NULL;
	proto_tree* tree = NULL;
	guint32 flags;

	flags=tvb_get_ntohl(tvb, offset);
	if (parent_tree) {
		item = proto_tree_add_text(parent_tree, tvb, offset, 4,
				"Attributes: 0x%08x", flags);
		tree = proto_item_add_subtree(item, ett_ndmp_butype_attrs);
	}

	proto_tree_add_boolean(tree, hf_ndmp_butype_attr_recover_utf8,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_butype_attr_backup_utf8,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_butype_attr_recover_incremental,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_butype_attr_backup_incremental,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_butype_attr_recover_direct,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_butype_attr_backup_direct,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_butype_attr_recover_filelist,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_butype_attr_backup_filelist,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_butype_attr_backup_file_history,
				tvb, offset, 4, flags);

	offset += 4;
	return offset;
}

static int
dissect_butype_info(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree)
{
	/*butype name*/
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_butype_name, offset, NULL);

	/* default env */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_default_env, hf_ndmp_butype_default_env);

	/* attrs */
	offset = dissect_butype_attrs(tvb, offset, pinfo, tree);

	return offset;
}

static int
dissect_get_butype_info_reply(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq)
{
	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, seq);

	/* butype */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_butype_info, hf_ndmp_butype_info);

	return offset;
}

static const true_false_string tfs_fs_invalid_total_size = {
	"Total size is INVALID",
	"Total size is VALID"
};
static const true_false_string tfs_fs_invalid_used_size = {
	"Used size is INVALID",
	"Used size is VALID"
};
static const true_false_string tfs_fs_invalid_avail_size = {
	"Available size is INVALID",
	"Available size is VALID"
};
static const true_false_string tfs_fs_invalid_total_inodes = {
	"Total inode count is INVALID",
	"Total inode count is VALID"
};
static const true_false_string tfs_fs_invalid_used_inodes = {
	"Used inode count is INVALID",
	"Used inode count is VALID"
};
static int
dissect_fs_invalid(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *parent_tree)
{
	proto_item* item = NULL;
	proto_tree* tree = NULL;
	guint32 flags;

	flags=tvb_get_ntohl(tvb, offset);
	if (parent_tree) {
		item = proto_tree_add_text(parent_tree, tvb, offset, 4,
				"Invalids: 0x%08x", flags);
		tree = proto_item_add_subtree(item, ett_ndmp_fs_invalid);
	}

	proto_tree_add_boolean(tree, hf_ndmp_fs_invalid_used_inodes,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_fs_invalid_total_inodes,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_fs_invalid_avail_size,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_fs_invalid_used_size,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_fs_invalid_total_size,
				tvb, offset, 4, flags);

	offset+=4;
	return offset;
}

static int
dissect_fs_env(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *tree)
{
	/* name */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_fs_env_name, offset, NULL);

	/* value */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_fs_env_value, offset, NULL);

	return offset;
}

static int
dissect_fs_info(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree)
{
	/* invalid bits */
	offset=dissect_fs_invalid(tvb, offset, pinfo, tree);

	/* fs type */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_fs_fs_type, offset, NULL);

	/* fs logical device */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_fs_logical_device, offset, NULL);

	/* fs physical device */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_fs_physical_device, offset, NULL);

	/*total_size*/
	offset = dissect_rpc_uint64(tvb, tree, hf_ndmp_fs_total_size,
			offset);

	/*used_size*/
	offset = dissect_rpc_uint64(tvb, tree, hf_ndmp_fs_used_size,
			offset);

	/*avail_size*/
	offset = dissect_rpc_uint64(tvb, tree, hf_ndmp_fs_avail_size,
			offset);

	/*total_inodes*/
	offset = dissect_rpc_uint64(tvb, tree, hf_ndmp_fs_total_inodes,
			offset);

	/*used_inodes*/
	offset = dissect_rpc_uint64(tvb, tree, hf_ndmp_fs_used_inodes,
			offset);

	/* env */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_fs_env, hf_ndmp_fs_env);

	/* status */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_fs_status, offset, NULL);

	return offset;
}

static int
dissect_get_fs_info_reply(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq)
{
	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, seq);

	/* fs */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_fs_info, hf_ndmp_fs_info);

	return offset;
}

static const true_false_string tfs_tape_attr_rewind = {
	"Device supports REWIND",
	"Device does NOT support rewind"
};
static const true_false_string tfs_tape_attr_unload = {
	"Device supports UNLOAD",
	"Device does NOT support unload"
};
static int
dissect_tape_attr(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *parent_tree)
{
	proto_item* item = NULL;
	proto_tree* tree = NULL;
	guint32 flags;

	flags=tvb_get_ntohl(tvb, offset);
	if (parent_tree) {
		item = proto_tree_add_text(parent_tree, tvb, offset, 4,
				"Attributes: 0x%08x", flags);
		tree = proto_item_add_subtree(item, ett_ndmp_tape_attr);
	}

	proto_tree_add_boolean(tree, hf_ndmp_tape_attr_unload,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_tape_attr_rewind,
				tvb, offset, 4, flags);

	offset+=4;
	return offset;
}

static int
dissect_tape_capability(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *tree)
{
	/* name */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_tape_capability_name, offset, NULL);

	/* value */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_tape_capability_value, offset, NULL);

	return offset;
}

static int
dissect_tape_dev_cap(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree)
{
	/* device */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_tape_device, offset, NULL);

	/* tape attributes */
	offset = dissect_tape_attr(tvb, offset, pinfo, tree);

	/* capability */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_tape_capability, hf_ndmp_tape_capability);

	return offset;
}

static int
dissect_tape_info(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree)
{
	/* model */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_tape_model, offset, NULL);

	/* device capabilites */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_tape_dev_cap, hf_ndmp_tape_dev_cap);

	return offset;
}

static int
dissect_get_tape_info_reply(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq)
{
	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, seq);

	/* tape */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_tape_info, hf_ndmp_tape_info);

	return offset;
}

static int
dissect_scsi_info(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree)
{
	/* model */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_scsi_model, offset, NULL);

	/* device capabilites */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_tape_dev_cap, hf_ndmp_tape_dev_cap);

	return offset;
}

static int
dissect_get_scsi_info_reply(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq)
{
	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, seq);

	/* scsi */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_scsi_info, hf_ndmp_scsi_info);

	return offset;
}

static int
dissect_get_server_info_reply(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq)
{
	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, seq);

	/* vendor */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_server_vendor, offset, NULL);

	/* product */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_server_product, offset, NULL);

	/* revision */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_server_revision, offset, NULL);


	/* server */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_auth_type, hf_ndmp_auth_types);

	return offset;
}

static int
dissect_scsi_open_request(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *tree, guint32 seq _U_)
{
	/* device */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_scsi_device, offset, NULL);

	return offset;
}

static int
dissect_scsi_get_state_reply(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq)
{
	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, seq);

	/* controller */
	proto_tree_add_item(tree, hf_ndmp_scsi_controller, tvb, offset, 4, FALSE);
	offset += 4;

	/* id */
	proto_tree_add_item(tree, hf_ndmp_scsi_id, tvb, offset, 4, FALSE);
	offset += 4;

	/* lun */
	proto_tree_add_item(tree, hf_ndmp_scsi_lun, tvb, offset, 4, FALSE);
	offset += 4;

	return offset;
}

static int
dissect_scsi_set_state_request(tvbuff_t *tvb, int offset,
    packet_info *pinfo _U_, proto_tree *tree, guint32 seq _U_)
{
	/* device */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_scsi_device, offset, NULL);

	/* controller */
	proto_tree_add_item(tree, hf_ndmp_scsi_controller, tvb, offset, 4, FALSE);
	offset += 4;

	/* id */
	proto_tree_add_item(tree, hf_ndmp_scsi_id, tvb, offset, 4, FALSE);
	offset += 4;

	/* lun */
	proto_tree_add_item(tree, hf_ndmp_scsi_lun, tvb, offset, 4, FALSE);
	offset += 4;

	return offset;
}

static int
dissect_execute_cdb_flags(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *parent_tree)
{
	proto_item* item = NULL;
	proto_tree* tree = NULL;
	guint32 flags;

	flags = tvb_get_ntohl(tvb, offset);
	if (parent_tree) {
		item = proto_tree_add_text(parent_tree, tvb, offset, 4,
				"Flags: 0x%08x", flags);
		tree = proto_item_add_subtree(item, ett_ndmp_execute_cdb_flags);
	}

	proto_tree_add_boolean(tree, hf_ndmp_execute_cdb_flags_data_in,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_execute_cdb_flags_data_out,
				tvb, offset, 4, flags);
	offset += 4;
	return offset;
}

static int
dissect_execute_cdb_cdb(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *parent_tree, gint devtype)
{
	proto_item* item = NULL;
	proto_tree* tree = NULL;
	guint32 cdb_len;
	guint32 cdb_len_full;

	cdb_len = tvb_get_ntohl(tvb, offset);
	cdb_len_full = rpc_roundup(cdb_len);

	if (parent_tree) {
		item = proto_tree_add_text(parent_tree, tvb, offset,
				4+cdb_len_full, "CDB");
		tree = proto_item_add_subtree(item, ett_ndmp_execute_cdb_cdb);
	}

	proto_tree_add_uint(tree, hf_ndmp_execute_cdb_cdb_len, tvb, offset, 4,
			cdb_len);
	offset += 4;

	if (cdb_len != 0) {
		dissect_scsi_cdb(tvb, pinfo, tree, offset, cdb_len, devtype, 0xffff);
		offset += cdb_len_full;
	}

	return offset;
}


static int
dissect_execute_cdb_payload(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *parent_tree,
    char *name, int hf_len, gboolean isreq)
{
	proto_item* item = NULL;
	proto_tree* tree = NULL;
	guint32 payload_len;
	guint32 payload_len_full;

	payload_len = tvb_get_ntohl(tvb, offset);
	payload_len_full = rpc_roundup(payload_len);

	if (parent_tree) {
		item = proto_tree_add_text(parent_tree, tvb, offset,
				4+payload_len_full, "%s", name);
		tree = proto_item_add_subtree(item,
		    ett_ndmp_execute_cdb_payload);
	}

	proto_tree_add_uint(tree, hf_len, tvb, offset, 4, payload_len);
	offset += 4;

	if (payload_len != 0) {
		dissect_scsi_payload(tvb, pinfo, tree, offset, isreq,
		    payload_len, 0xffff);
		offset += payload_len_full;
	}

	return offset;
}

/*
 * XXX - we assume that NDMP_SCSI_EXECUTE_CDB requests only go to SCSI Media
 * Changer devices and NDMP_TAPE_EXECUTE_CDB only go to SCSI Sequential
 * Access devices.
 *
 * If that's not the case, we'll have to use the SCSI dissector's mechanisms
 * for saving inquiry data for devices, and use inquiry data when available.
 * Unfortunately, that means we need to save the name of the device, and
 * use it as a device identifier; as the name isn't available in the
 * NDMP_SCSI_EXECUTE_CDB or NDMP_TAPE_EXECUTE_CDB messages, that means
 * we need to remember the currently-opened "SCSI" and "TAPE" devices
 * from NDMP_SCSI_OPEN and NDMP_TAPE_OPEN, and attach to all frames
 * that are the ones that trigger the dissection of NDMP_SCSI_EXECUTE_CDB
 * or NDMP_TAPE_EXECUTE_CDB requests pointers to those names.
 */
static int
dissect_execute_cdb_request(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq, gint devtype)
{
	conversation_t *conversation;
	scsi_task_id_t task_key;

	/*
	 * We need to provide SCSI task information to the SCSI
	 * dissection routines.  We use a conversation plus the
	 * sequence number in requests and the reply sequence
	 * number in replies to identify SCSI tasks.
	 */
	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);
	}
	task_key.conv_id = conversation->index;
	task_key.task_id = seq;
	pinfo->private_data = &task_key;

	/* flags */
	offset = dissect_execute_cdb_flags(tvb, offset, pinfo, tree);

	/* timeout */
	proto_tree_add_item(tree, hf_ndmp_execute_cdb_timeout, tvb, offset, 4, FALSE);
	offset += 4;

	/* datain_len */
	proto_tree_add_item(tree, hf_ndmp_execute_cdb_datain_len, tvb, offset, 4, FALSE);
	offset += 4;

	/* CDB */
	offset = dissect_execute_cdb_cdb(tvb, offset, pinfo, tree, devtype);

	/* dataout */
	offset = dissect_execute_cdb_payload(tvb, offset, pinfo, tree,
	    "Data out", hf_ndmp_execute_cdb_dataout_len, TRUE);

	return offset;
}

static int
dissect_execute_cdb_request_mc(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq)
{
	return dissect_execute_cdb_request(tvb, offset, pinfo, tree, seq,
	    SCSI_DEV_SMC);
}

static int
dissect_execute_cdb_request_tape(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq)
{
	return dissect_execute_cdb_request(tvb, offset, pinfo, tree, seq,
	    SCSI_DEV_SSC);
}

static int
dissect_execute_cdb_sns(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *parent_tree)
{
	proto_item* item = NULL;
	proto_tree* tree = NULL;
	guint32 sns_len;
	guint32 sns_len_full;

	sns_len = tvb_get_ntohl(tvb, offset);
	sns_len_full = rpc_roundup(sns_len);

	if (parent_tree) {
		item = proto_tree_add_text(parent_tree, tvb, offset,
				4+sns_len_full, "Sense data");
		tree = proto_item_add_subtree(item, ett_ndmp_execute_cdb_sns);
	}

	proto_tree_add_uint(tree, hf_ndmp_execute_cdb_sns_len, tvb, offset, 4,
			sns_len);
	offset += 4;

	if (sns_len != 0) {
		dissect_scsi_snsinfo(tvb, pinfo, tree, offset, sns_len, 0xffff);
		offset += sns_len_full;
	}

	return offset;
}

static int
dissect_execute_cdb_reply(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq)
{
	conversation_t *conversation;
	scsi_task_id_t task_key;

	/*
	 * We need to provide SCSI task information to the SCSI
	 * dissection routines.  We use a conversation plus the
	 * sequence number in requests and the reply sequence
	 * number in replies to identify SCSI tasks.
	 */
	conversation = find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst,
	    pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
	if (conversation != NULL) {
		task_key.conv_id = conversation->index;
		task_key.task_id = seq;
	        pinfo->private_data = &task_key;
	} else {
		/* no conversation, meaning we didn't see the request */
		pinfo->private_data = NULL;
	}

	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, seq);

	/* status */
	proto_tree_add_item(tree, hf_ndmp_execute_cdb_status, tvb, offset, 4, FALSE);
	offset += 4;

	/* dataout_len */
	proto_tree_add_item(tree, hf_ndmp_execute_cdb_dataout_len, tvb, offset, 4, FALSE);
	offset += 4;

	/* datain */
	offset = dissect_execute_cdb_payload(tvb, offset, pinfo, tree,
	    "Data in", hf_ndmp_execute_cdb_datain_len, FALSE);

	/* ext_sense */
	offset = dissect_execute_cdb_sns(tvb, offset, pinfo, tree);

	return offset;
}

#define NDMP_TAPE_OPEN_MODE_READ	0
#define NDMP_TAPE_OPEN_MODE_RDWR	1
static const value_string tape_open_mode_vals[] = {
	{NDMP_TAPE_OPEN_MODE_READ,	"Read"},
	{NDMP_TAPE_OPEN_MODE_RDWR,	"Read/Write"},
	{0, NULL}
};

static int
dissect_tape_open_request(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *tree, guint32 seq _U_)
{
	/* device */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_tape_device, offset, NULL);

	/* open mode */
	proto_tree_add_item(tree, hf_ndmp_tape_open_mode, tvb, offset, 4, FALSE);
	offset += 4;

	return offset;
}


static const true_false_string tfs_ndmp_tape_invalid_file_num = {
	"File num is valid",
	"File num is INVALID"
};
static const true_false_string tfs_ndmp_tape_invalid_soft_errors = {
	"Soft errors is valid",
	"Soft errors is INVALID"
};
static const true_false_string tfs_ndmp_tape_invalid_block_size = {
	"Block size is valid",
	"Block size is INVALID"
};
static const true_false_string tfs_ndmp_tape_invalid_block_no = {
	"Block no is valid",
	"Block no is INVALID"
};
static const true_false_string tfs_ndmp_tape_invalid_total_space = {
	"Total space is valid",
	"Total space is INVALID"
};
static const true_false_string tfs_ndmp_tape_invalid_space_remain = {
	"Space remaining is INVALID",
	"Space remaining is valid"
};
static const true_false_string tfs_ndmp_tape_invalid_partition = {
	"Partition is INVALID",
	"Partition is valid"
};
static int
dissect_tape_invalid(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *parent_tree)
{
	proto_item* item = NULL;
	proto_tree* tree = NULL;
	guint32 flags;

	flags=tvb_get_ntohl(tvb, offset);
	if (parent_tree) {
		item = proto_tree_add_text(parent_tree, tvb, offset, 4,
				"Invalids: 0x%08x", flags);
		tree = proto_item_add_subtree(item, ett_ndmp_tape_invalid);
	}

	proto_tree_add_boolean(tree, hf_ndmp_tape_invalid_partition,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_tape_invalid_space_remain,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_tape_invalid_total_space,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_tape_invalid_block_no,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_tape_invalid_block_size,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_tape_invalid_soft_errors,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_tape_invalid_file_num,
				tvb, offset, 4, flags);

	offset+=4;
	return offset;
}

static const true_false_string tfs_ndmp_tape_flags_no_rewind = {
	"This is a NON-REWINDING device",
	"This device supports rewind"
};
static const true_false_string tfs_ndmp_tape_flags_write_protect = {
	"This device is WRITE-PROTECTED",
	"This device is NOT write-protected"
};
static const true_false_string tfs_ndmp_tape_flags_error = {
	"This device shows ERROR",
	"This device shows NO errors"
};
static const true_false_string tfs_ndmp_tape_flags_unload = {
	"This device supports UNLOAD",
	"This device does NOT support unload"
};
static int
dissect_tape_flags(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *parent_tree)
{
	proto_item* item = NULL;
	proto_tree* tree = NULL;
	guint32 flags;

	flags=tvb_get_ntohl(tvb, offset);
	if (parent_tree) {
		item = proto_tree_add_text(parent_tree, tvb, offset, 4,
				"Flags: 0x%08x", flags);
		tree = proto_item_add_subtree(item, ett_ndmp_tape_flags);
	}


	proto_tree_add_boolean(tree, hf_ndmp_tape_flags_unload,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_tape_flags_error,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_tape_flags_write_protect,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_tape_flags_no_rewind,
				tvb, offset, 4, flags);

	offset+=4;
	return offset;
}

static int
dissect_tape_get_state_reply(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq)
{
	/* invalid bits */
	offset=dissect_tape_invalid(tvb, offset, pinfo, tree);

	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, seq);

	/* flags */
	offset=dissect_tape_flags(tvb, offset, pinfo, tree);

	/* file_num */
	proto_tree_add_item(tree, hf_ndmp_tape_file_num, tvb, offset, 4, FALSE);
	offset += 4;

	/* soft_errors */
	proto_tree_add_item(tree, hf_ndmp_tape_soft_errors, tvb, offset, 4, FALSE);
	offset += 4;

	/* block_size */
	proto_tree_add_item(tree, hf_ndmp_tape_block_size, tvb, offset, 4, FALSE);
	offset += 4;

	/* block_no */
	proto_tree_add_item(tree, hf_ndmp_tape_block_no, tvb, offset, 4, FALSE);
	offset += 4;

	/* total_space */
	offset = dissect_rpc_uint64(tvb, tree,hf_ndmp_tape_total_space,
			offset);

	/* space_remain */
	offset = dissect_rpc_uint64(tvb, tree,hf_ndmp_tape_space_remain,
			offset);

	/* partition */
	proto_tree_add_item(tree, hf_ndmp_tape_partition, tvb, offset, 4, FALSE);
	offset += 4;

	return offset;
}

#define NDMP_TAPE_MTIO_FSF	0
#define NDMP_TAPE_MTIO_BSF	1
#define NDMP_TAPE_MTIO_FSR	2
#define NDMP_TAPE_MTIO_BSR	3
#define NDMP_TAPE_MTIO_REW	4
#define NDMP_TAPE_MTIO_EOF	5
#define NDMP_TAPE_MTIO_OFF	6
static const value_string tape_mtio_vals[] = {
	{NDMP_TAPE_MTIO_FSF,	"FSF"},
	{NDMP_TAPE_MTIO_BSF,	"BSF"},
	{NDMP_TAPE_MTIO_FSR,	"FSR"},
	{NDMP_TAPE_MTIO_BSR,	"BSR"},
	{NDMP_TAPE_MTIO_REW,	"REW"},
	{NDMP_TAPE_MTIO_EOF,	"EOF"},
	{NDMP_TAPE_MTIO_OFF,	"OFF"},
	{0, NULL}
};

static int
dissect_tape_mtio_request(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *tree, guint32 seq _U_)
{
	/* op */
	proto_tree_add_item(tree, hf_ndmp_tape_mtio_op, tvb, offset, 4, FALSE);
	offset += 4;

	/* count */
	proto_tree_add_item(tree, hf_ndmp_count, tvb, offset, 4, FALSE);
	offset += 4;

	return offset;
}

static int
dissect_tape_mtio_reply(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq)
{
	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, seq);

	/* resid count */
	proto_tree_add_item(tree, hf_ndmp_resid_count, tvb, offset, 4, FALSE);
	offset += 4;

	return offset;
}

#define NDMP_MOVER_STATE_IDLE		0
#define NDMP_MOVER_STATE_LISTEN		1
#define NDMP_MOVER_STATE_ACTIVE		2
#define NDMP_MOVER_STATE_PAUSED		3
#define NDMP_MOVER_STATE_HALTED		4
static const value_string mover_state_vals[] = {
	{NDMP_MOVER_STATE_IDLE,	"MOVER_STATE_IDLE"},
	{NDMP_MOVER_STATE_LISTEN,	"MOVER_STATE_LISTEN"},
	{NDMP_MOVER_STATE_ACTIVE,	"MOVER_STATE_ACTIVE"},
	{NDMP_MOVER_STATE_PAUSED,	"MOVER_STATE_PAUSED"},
	{NDMP_MOVER_STATE_HALTED,	"MOVER_STATE_HALTED"},
	{0, NULL}
};

#define NDMP_MOVER_PAUSE_NA		0
#define NDMP_MOVER_PAUSE_EOM		1
#define NDMP_MOVER_PAUSE_EOF		2
#define NDMP_MOVER_PAUSE_SEEK		3
#define NDMP_MOVER_PAUSE_MEDIA_ERROR	4
#define NDMP_MOVER_PAUSE_EOW		5
static const value_string mover_pause_vals[] = {
	{NDMP_MOVER_PAUSE_NA,		"MOVER_PAUSE_NA"},
	{NDMP_MOVER_PAUSE_EOM,		"MOVER_PAUSE_EOM"},
	{NDMP_MOVER_PAUSE_EOF,		"MOVER_PAUSE_EOF"},
	{NDMP_MOVER_PAUSE_SEEK,		"MOVER_PAUSE_SEEK"},
	{NDMP_MOVER_PAUSE_MEDIA_ERROR,	"MOVER_PAUSE_MEDIA_ERROR"},
	{NDMP_MOVER_PAUSE_EOW,		"MOVER_PAUSE_EOW"},
	{0, NULL}
};

#define NDMP_HALT_NA		0
#define NDMP_HALT_CONNECT_CLOSE	1
#define NDMP_HALT_ABORTED		2
#define NDMP_HALT_INTERNAL_ERROR	3
#define NDMP_HALT_CONNECT_ERROR	4
static const value_string halt_vals[] = {
	{NDMP_HALT_NA,			"HALT_NA"},
	{NDMP_HALT_CONNECT_CLOSE,	"HALT_CONNECT_CLOSE"},
	{NDMP_HALT_ABORTED,		"HALT_ABORTED"},
	{NDMP_HALT_INTERNAL_ERROR,	"HALT_INTERNAL_ERROR"},
	{NDMP_HALT_CONNECT_ERROR,	"HALT_CONNECT_ERROR"},
	{0, NULL}
};

static int
dissect_tcp_env(tvbuff_t *tvb, int offset, packet_info *pinfo _U_, proto_tree *tree)
{
	/* name */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_tcp_env_name, offset, NULL);

	/* value */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_tcp_env_value, offset, NULL);

	return offset;
}


static int
dissect_ndmp_v4_tcp_addr(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree)
{
	/* IP addr */
	proto_tree_add_item(tree, hf_ndmp_addr_ip, tvb, offset, 4, FALSE);
	offset+=4;

	/* TCP port */
	proto_tree_add_item(tree, hf_ndmp_addr_tcp, tvb, offset, 4, FALSE);
	offset+=4;

	/* addr_env */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_tcp_env, hf_ndmp_tcp_default_env);

	return offset;
}

static int
dissect_ndmp_addr(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *parent_tree)
{
	proto_item* item = NULL;
	proto_tree* tree = NULL;
	guint32 type;

	type=tvb_get_ntohl(tvb, offset);
	if (parent_tree) {
		item = proto_tree_add_text(parent_tree, tvb, offset, 4,
				"Type: %s ", val_to_str(type, addr_type_vals,"Unknown addr type (0x%02x)") );
		tree = proto_item_add_subtree(item, ett_ndmp_addr);
	}

	/*address type*/
	proto_tree_add_item(tree, hf_ndmp_addr_type, tvb, offset, 4, FALSE);
	offset += 4;


	switch(type){
	case NDMP_ADDR_LOCAL:
		break;
	case NDMP_ADDR_TCP:
	  	/* this became an array in version 4 and beyond */
		if(ndmp_protocol_version<NDMP_PROTOCOL_V4){
			/* IP addr */
			proto_tree_add_item(tree, hf_ndmp_addr_ip, tvb, offset, 4, FALSE);
			offset+=4;
	
			/* TCP port */
			proto_tree_add_item(tree, hf_ndmp_addr_tcp, tvb, offset, 4, FALSE);
			offset+=4;
		} else {
			offset = dissect_rpc_array(tvb, pinfo, tree, offset,
				dissect_ndmp_v4_tcp_addr, hf_ndmp_tcp_addr_list);

		}

		break;
	case NDMP_ADDR_FC:
		/* FCAL loop id */
		proto_tree_add_item(tree, hf_ndmp_addr_fcal_loop_id, tvb, offset, 4, FALSE);
		offset+=4;

		break;
	case NDMP_ADDR_IPC:
		/* IPC address */
		offset = dissect_rpc_data(tvb, tree, hf_ndmp_addr_ipc, offset);
		break;
	}

	return offset;
}

static int
dissect_data_connect_msg(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq _U_)
{
	/* ndmp addr */
	offset=dissect_ndmp_addr(tvb, offset, pinfo, tree);
}


static int
dissect_mover_get_state_reply(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq)
{
	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, seq);

	/* mode is only present in version 4 and beyond */
	if(ndmp_protocol_version>=NDMP_PROTOCOL_V4){
		proto_tree_add_item(tree, hf_ndmp_mover_mode, tvb, offset, 4, FALSE);
		offset += 4;
	}

	/* mover state */
	proto_tree_add_item(tree, hf_ndmp_mover_state, tvb, offset, 4, FALSE);
	offset += 4;

	/* mover pause */
	proto_tree_add_item(tree, hf_ndmp_mover_pause, tvb, offset, 4, FALSE);
	offset += 4;

	/* halt */
	proto_tree_add_item(tree, hf_ndmp_halt, tvb, offset, 4, FALSE);
	offset += 4;

	/* record size */
	proto_tree_add_item(tree, hf_ndmp_record_size, tvb, offset, 4, FALSE);
	offset += 4;

	/* record num */
	proto_tree_add_item(tree, hf_ndmp_record_num, tvb, offset, 4, FALSE);
	offset += 4;

	/* data written */
	proto_tree_add_item(tree, hf_ndmp_data_written, tvb, offset, 8, FALSE);
	offset += 8;

	/* seek position */
	proto_tree_add_item(tree, hf_ndmp_seek_position, tvb, offset, 8, FALSE);
	offset += 8;

	/* bytes left to read */
	proto_tree_add_item(tree, hf_ndmp_bytes_left_to_read, tvb, offset, 8, FALSE);
	offset += 8;

	/* window offset */
	proto_tree_add_item(tree, hf_ndmp_window_offset, tvb, offset, 8, FALSE);
	offset += 8;

	/* window length */
	proto_tree_add_item(tree, hf_ndmp_window_length, tvb, offset, 8, FALSE);
	offset += 8;

	/* this is where v2 ends */
	if(ndmp_protocol_version==NDMP_PROTOCOL_V2){
		return offset;
	}


	/* ndmp addr */
	offset=dissect_ndmp_addr(tvb, offset, pinfo, tree);

	return offset;
}

#define NDMP_MOVER_MODE_READ		0
#define NDMP_MOVER_MODE_WRITE		1
#define NDMP_MOVER_MODE_NOACTION	2
static const value_string mover_mode_vals[] = {
	{NDMP_MOVER_MODE_READ,		"MOVER_MODE_READ"},
	{NDMP_MOVER_MODE_WRITE,		"MOVER_MODE_WRITE"},
	{NDMP_MOVER_MODE_NOACTION,	"MOVER_MODE_NOACTION"},
	{0, NULL}
};

static int
dissect_mover_listen_request(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *tree, guint32 seq _U_)
{
	/* mode */
	proto_tree_add_item(tree, hf_ndmp_mover_mode, tvb, offset, 4, FALSE);
	offset += 4;

	/*address type*/
	proto_tree_add_item(tree, hf_ndmp_addr_type, tvb, offset, 4, FALSE);
	offset += 4;

	return offset;
}

static int
dissect_mover_listen_reply(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq)
{
	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, seq);

	/* ndmp addr */
	offset=dissect_ndmp_addr(tvb, offset, pinfo, tree);

	return offset;
}

static int
dissect_mover_set_window_request(tvbuff_t *tvb, int offset,
    packet_info *pinfo _U_, proto_tree *tree, guint32 seq _U_)
{
	/* window offset */
	proto_tree_add_item(tree, hf_ndmp_window_offset, tvb, offset, 8, FALSE);
	offset += 8;

	/* window length */
	proto_tree_add_item(tree, hf_ndmp_window_length, tvb, offset, 8, FALSE);
	offset += 8;

	return offset;
}

static int
dissect_mover_set_record_size_request(tvbuff_t *tvb, int offset,
    packet_info *pinfo _U_, proto_tree *tree, guint32 seq _U_)
{
	/* record size */
	proto_tree_add_item(tree, hf_ndmp_record_size, tvb, offset, 4, FALSE);
	offset += 4;

	return offset;
}

static int
dissect_mover_connect_request(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq _U_)
{
	/* mode */
	proto_tree_add_item(tree, hf_ndmp_mover_mode, tvb, offset, 4, FALSE);
	offset += 4;

	/* ndmp addr */
	offset=dissect_ndmp_addr(tvb, offset, pinfo, tree);

	return offset;
}

static int
dissect_log_file_request(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq)
{
	/* file */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_file_name, offset, NULL);

	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, seq);

	return offset;
}

#define NDMP_LOG_TYPE_NORMAL	0
#define NDMP_LOG_TYPE_DEBUG	1
#define NDMP_LOG_TYPE_ERROR	2
#define NDMP_LOG_TYPE_WARNING	3
static const value_string log_type_vals[] = {
	{NDMP_LOG_TYPE_NORMAL,	"NORMAL"},
	{NDMP_LOG_TYPE_DEBUG,	"DEBUG"},
	{NDMP_LOG_TYPE_ERROR,	"ERROR"},
	{NDMP_LOG_TYPE_WARNING,	"WARNING"},
	{0, NULL}
};

static int
dissect_log_message_request(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *tree, guint32 seq _U_)
{
	/* type */
	proto_tree_add_item(tree, hf_ndmp_log_type, tvb, offset, 4, FALSE);
	offset += 4;

	/* message id */
	proto_tree_add_item(tree, hf_ndmp_log_message_id, tvb, offset, 4, FALSE);
	offset += 4;

	/* message */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_log_message, offset, NULL);

	return offset;
}

static int
dissect_notify_data_halted_request(tvbuff_t *tvb, int offset,
    packet_info *pinfo _U_, proto_tree *tree, guint32 seq _U_)
{
	/* halt */
	proto_tree_add_item(tree, hf_ndmp_halt, tvb, offset, 4, FALSE);
	offset += 4;

	/* reason */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_halt_reason, offset, NULL);

	return offset;
}

#define NDMP_CONNECTED_CONNECTED	0
#define NDMP_CONNECTED_SHUTDOWN		1
#define NDMP_CONNECTED_REFUSED		2
static const value_string connected_vals[] = {
	{NDMP_CONNECTED_CONNECTED,	"CONNECTED"},
	{NDMP_CONNECTED_SHUTDOWN,	"SHUTDOWN"},
	{NDMP_CONNECTED_REFUSED,	"REFUSED"},
	{0, NULL}
};

static int
dissect_notify_connected_request(tvbuff_t *tvb, int offset,
    packet_info *pinfo _U_, proto_tree *tree, guint32 seq _U_)
{
	/* connected */
	proto_tree_add_item(tree, hf_ndmp_connected, tvb, offset, 4, FALSE);
	offset += 4;

	/* version number */
	proto_tree_add_item(tree, hf_ndmp_version, tvb, offset, 4, FALSE);
	offset += 4;

	/* reason */
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_connected_reason, offset, NULL);

	return offset;
}


static int
dissect_notify_mover_paused_request(tvbuff_t *tvb, int offset,
    packet_info *pinfo _U_, proto_tree *tree, guint32 seq _U_)
{
	/* mover pause */
	proto_tree_add_item(tree, hf_ndmp_mover_pause, tvb, offset, 4, FALSE);
	offset += 4;

	/* seek position */
	proto_tree_add_item(tree, hf_ndmp_seek_position, tvb, offset, 8, FALSE);
	offset += 8;

	return offset;
}

static int
dissect_auth_data(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *tree)
{
	guint type;

	type=tvb_get_ntohl(tvb,offset);

	/* auth type */
	proto_tree_add_item(tree, hf_ndmp_auth_type, tvb, offset, 4, FALSE);
	offset += 4;

	switch(type){
	case NDMP_AUTH_NONE:
		break;
	case NDMP_AUTH_TEXT:
		/* auth id */
		offset = dissect_rpc_string(tvb, tree,
				hf_ndmp_auth_id, offset, NULL);

		/* auth password */
		offset = dissect_rpc_string(tvb, tree,
				hf_ndmp_auth_password, offset, NULL);


		break;
	case NDMP_AUTH_MD5:
		/* auth id */
		offset = dissect_rpc_string(tvb, tree,
				hf_ndmp_auth_id, offset, NULL);

		/* digest */
		proto_tree_add_item(tree, hf_ndmp_auth_digest,
			tvb, offset, 16, FALSE);
		offset+=16;
	}

	return offset;
}

static int
dissect_connect_client_auth_request(tvbuff_t *tvb, int offset,
    packet_info *pinfo, proto_tree *tree, guint32 seq _U_)
{
	return dissect_auth_data(tvb, offset, pinfo, tree);
}

static int
dissect_connect_server_auth_reply(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq)
{
	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, seq);

	/* auth data */
	offset = dissect_auth_data(tvb, offset, pinfo, tree);

	return offset;
}

static int
dissect_tape_write_request(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *tree, guint32 seq _U_)
{
	/* data */
	offset = dissect_rpc_data(tvb, tree, hf_ndmp_data, offset);

	return offset;
}

static int
dissect_tape_write_reply(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq)
{
	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, seq);

	/* count */
	proto_tree_add_item(tree, hf_ndmp_count, tvb, offset, 4, FALSE);
	offset += 4;

	return offset;
}

static int
dissect_tape_read_request(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *tree, guint32 seq _U_)
{
	/* count */
	proto_tree_add_item(tree, hf_ndmp_count, tvb, offset, 4, FALSE);
	offset += 4;

	return offset;
}

static int
dissect_tape_read_reply(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq)
{
	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, seq);

	/* data */
	offset = dissect_rpc_data(tvb, tree, hf_ndmp_data, offset);

	return offset;
}

#define NDMP_FS_UNIX	0
#define NDMP_FS_NT	1
#define NDMP_FS_OTHER	2
static const value_string file_fs_type_vals[] = {
	{NDMP_FS_UNIX,	"UNIX"},
	{NDMP_FS_NT,	"NT"},
	{NDMP_FS_OTHER,	"OTHER"},
	{0, NULL}
};

static int
dissect_file_name(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *parent_tree)
{
	proto_item* item = NULL;
	proto_tree* tree = NULL;
	int old_offset=offset;
	guint32 type;
	char *name;

	if (parent_tree) {
		item = proto_tree_add_text(parent_tree, tvb, offset, -1,
				"File");
		tree = proto_item_add_subtree(item, ett_ndmp_file_name);
	}

	/* file type */
	type=tvb_get_ntohl(tvb, offset);
	proto_tree_add_item(tree, hf_ndmp_file_fs_type, tvb, offset, 4, FALSE);
	offset += 4;

	switch(type){
	case NDMP_FS_UNIX:
		/* file */
		offset = dissect_rpc_string(tvb, tree,
				hf_ndmp_file_name, offset, &name);
		if (check_col(pinfo->cinfo, COL_INFO)){
			col_append_fstr(pinfo->cinfo, COL_INFO, " %s", name);
		}
		break;
	case NDMP_FS_NT:
		/* nt file */
		offset = dissect_rpc_string(tvb, tree,
				hf_ndmp_nt_file_name, offset, &name);
		if (check_col(pinfo->cinfo, COL_INFO)){
			col_append_fstr(pinfo->cinfo, COL_INFO, " %s", name);
		}

		/* dos file */
		offset = dissect_rpc_string(tvb, tree,
				hf_ndmp_dos_file_name, offset, NULL);
		break;
	default:
		/* file */
		offset = dissect_rpc_string(tvb, tree,
				hf_ndmp_file_name, offset, &name);
		if (check_col(pinfo->cinfo, COL_INFO)){
			col_append_fstr(pinfo->cinfo, COL_INFO, " %s", name);
		}
	}

	if (check_col(pinfo->cinfo, COL_INFO)){
		col_append_fstr(pinfo->cinfo, COL_INFO, " (%s)",
			val_to_str(type, file_fs_type_vals, "Unknown type") );
	}

	proto_item_set_len(item, offset-old_offset);
	return offset;
}


static const true_false_string tfs_ndmp_file_invalid_atime = {
	"Atime is INVALID",
	"Atime is valid"
};
static const true_false_string tfs_ndmp_file_invalid_ctime = {
	"Ctime is INVALID",
	"Ctime is valid"
};
static const true_false_string tfs_ndmp_file_invalid_group = {
	"Group is INVALID",
	"Group is valid"
};
static int
dissect_file_invalids(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *parent_tree)
{
	proto_item* item = NULL;
	proto_tree* tree = NULL;
	guint32 flags;

	flags=tvb_get_ntohl(tvb, offset);
	if (parent_tree) {
		item = proto_tree_add_text(parent_tree, tvb, offset, 4,
				"Invalids: 0x%08x", flags);
		tree = proto_item_add_subtree(item, ett_ndmp_file_invalids);
	}

	proto_tree_add_boolean(tree, hf_ndmp_file_invalid_group,
			tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_file_invalid_ctime,
			tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_file_invalid_atime,
			tvb, offset, 4, flags);

	offset+=4;
	return offset;
}

#define NDMP_FILE_TYPE_DIR	0
#define NDMP_FILE_TYPE_FIFO	1
#define NDMP_FILE_TYPE_CSPEC	2
#define NDMP_FILE_TYPE_BSPEC	3
#define NDMP_FILE_TYPE_REG	4
#define NDMP_FILE_TYPE_SLINK	5
#define NDMP_FILE_TYPE_SOCK	6
#define NDMP_FILE_TYPE_REGISTRY	7
#define NDMP_FILE_TYPE_OTHER	8
static const value_string file_type_vals[] = {
	{NDMP_FILE_TYPE_DIR,	"DIR"},
	{NDMP_FILE_TYPE_FIFO,	"FIFO"},
	{NDMP_FILE_TYPE_CSPEC,	"CSPEC"},
	{NDMP_FILE_TYPE_BSPEC,	"BSPEC"},
	{NDMP_FILE_TYPE_REG,	"REG"},
	{NDMP_FILE_TYPE_SLINK,	"SLINK"},
	{NDMP_FILE_TYPE_SOCK,	"SOCK"},
	{NDMP_FILE_TYPE_REGISTRY,	"REGISTRY"},
	{NDMP_FILE_TYPE_OTHER,	"OTHER"},
	{0, NULL}
};

static int
dissect_file_stats(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *parent_tree)
{
	proto_item* item = NULL;
	proto_tree* tree = NULL;
	int old_offset=offset;
	nstime_t ns;

	if (parent_tree) {
		item = proto_tree_add_text(parent_tree, tvb, offset, -1,
				"Stats:");
		tree = proto_item_add_subtree(item, ett_ndmp_file_stats);
	}

	/* invalids */
	offset = dissect_file_invalids(tvb, offset, pinfo, tree);

	/* file fs type */
	proto_tree_add_item(tree, hf_ndmp_file_fs_type, tvb, offset, 4, FALSE);
	offset += 4;

	/* file type */
	proto_tree_add_item(tree, hf_ndmp_file_type, tvb, offset, 4, FALSE);
	offset += 4;

	/* mtime */
	ns.secs=tvb_get_ntohl(tvb, offset);
	ns.nsecs=0;
	proto_tree_add_time(tree, hf_ndmp_file_mtime, tvb, offset, 4, &ns);
	offset += 4;

	/* atime */
	ns.secs=tvb_get_ntohl(tvb, offset);
	ns.nsecs=0;
	proto_tree_add_time(tree, hf_ndmp_file_atime, tvb, offset, 4, &ns);
	offset += 4;

	/* ctime */
	ns.secs=tvb_get_ntohl(tvb, offset);
	ns.nsecs=0;
	proto_tree_add_time(tree, hf_ndmp_file_ctime, tvb, offset, 4, &ns);
	offset += 4;

	/* owner */
	proto_tree_add_item(tree, hf_ndmp_file_owner, tvb, offset, 4, FALSE);
	offset += 4;

	/* group */
	proto_tree_add_item(tree, hf_ndmp_file_group, tvb, offset, 4, FALSE);
	offset += 4;

	/*XXX here we should do proper dissection of mode for unix or
	      fattr for nt, call appropriate functions in nfs/smb*/
	/* fattr */
	proto_tree_add_item(tree, hf_ndmp_file_fattr, tvb, offset, 4, FALSE);
	offset += 4;

	/*file size*/
	offset = dissect_rpc_uint64(tvb, tree, hf_ndmp_file_size,
			offset);

	/* links */
	proto_tree_add_item(tree, hf_ndmp_file_links, tvb, offset, 4, FALSE);
	offset += 4;

	proto_item_set_len(item, offset-old_offset);
	return offset;
}


static int
dissect_file(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *parent_tree)
{
	proto_item* item = NULL;
	proto_tree* tree = NULL;
	int old_offset=offset;

	if (parent_tree) {
		item = proto_tree_add_text(parent_tree, tvb, offset, -1,
				"File:");
		tree = proto_item_add_subtree(item, ett_ndmp_file);
	}

	/* file names */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_file_name, hf_ndmp_file_names);

	/* file stats */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_file_stats, hf_ndmp_file_stats);

	/* node */
	proto_tree_add_item(tree, hf_ndmp_file_node, tvb, offset, 8, FALSE);
	offset += 8;

	/* fh_info */
	proto_tree_add_item(tree, hf_ndmp_file_fh_info, tvb, offset, 8, FALSE);
	offset += 8;

	proto_item_set_len(item, offset-old_offset);
	return offset;
}

static int
dissect_fh_add_file_request(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq _U_)
{
	/* files */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_file, hf_ndmp_files);

	return offset;
}

static int
dissect_dir(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree)
{
	/* file names */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_file_name, hf_ndmp_file_names);

	/* node */
	proto_tree_add_item(tree, hf_ndmp_file_node, tvb, offset, 8, FALSE);
	offset += 8;

	/* parent */
	proto_tree_add_item(tree, hf_ndmp_file_parent, tvb, offset, 8, FALSE);
	offset += 8;

	return offset;
}

static int
dissect_fh_add_dir_request(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq _U_)
{
	/* dirs */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_dir, hf_ndmp_dirs);

	return offset;
}

static int
dissect_node(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree)
{
	/* file stats */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_file_stats, hf_ndmp_file_stats);

	/* node */
	proto_tree_add_item(tree, hf_ndmp_file_node, tvb, offset, 8, FALSE);
	offset += 8;

	/* fh_info */
	proto_tree_add_item(tree, hf_ndmp_file_fh_info, tvb, offset, 8, FALSE);
	offset += 8;

	return offset;
}


static int
dissect_fh_add_node_request(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq _U_)
{
	/* node */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_node, hf_ndmp_nodes);

	return offset;
}

static int
dissect_data_start_backup_request(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq _U_)
{
	/*butype name*/
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_butype_name, offset, NULL);

	/* default env */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_default_env, hf_ndmp_butype_default_env);

	return offset;
}

static int
dissect_nlist(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *tree)
{
	/*original path*/
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_bu_original_path, offset, NULL);

	/*destination dir*/
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_bu_destination_dir, offset, NULL);

	if(ndmp_protocol_version==NDMP_PROTOCOL_V2){
		/* just 2 reserved bytes (4 with padding) */
		offset += 4;
	} else {
		/*new name*/
		offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_bu_new_name, offset, NULL);

		/*other name*/
		offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_bu_other_name, offset, NULL);

		/* node */
		proto_tree_add_item(tree, hf_ndmp_file_node, tvb, offset, 8, FALSE);
		offset += 8;
	}

	/* fh_info */
	proto_tree_add_item(tree, hf_ndmp_file_fh_info, tvb, offset, 8, FALSE);
	offset += 8;

	return offset;
}


static int
dissect_data_start_recover_request(tvbuff_t *tvb, int offset,
    packet_info *pinfo, proto_tree *tree, guint32 seq _U_)
{
	if(ndmp_protocol_version==NDMP_PROTOCOL_V2){
		/* ndmp addr */
		offset=dissect_ndmp_addr(tvb, offset, pinfo, tree);
	}

	/* default env */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_default_env, hf_ndmp_butype_default_env);

	/* nlist */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_nlist, hf_ndmp_nlist);

	/*butype name*/
	offset = dissect_rpc_string(tvb, tree,
			hf_ndmp_butype_name, offset, NULL);

	return offset;
}

static int
dissect_data_get_env_reply(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq)
{
	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, seq);

	/* default env */
	offset = dissect_rpc_array(tvb, pinfo, tree, offset,
			dissect_default_env, hf_ndmp_butype_default_env);

	return offset;
}


static const true_false_string tfs_ndmp_state_invalid_ebr = {
	"Estimated Bytes Remaining is INVALID",
	"Estimated Bytes Remaining is valid"
};
static const true_false_string tfs_ndmp_state_invalid_etr = {
	"Estimated Time Remaining is INVALID",
	"Estimated Time Remaining is valid"
};
static int
dissect_state_invalids(tvbuff_t *tvb, int offset, packet_info *pinfo _U_,
    proto_tree *parent_tree)
{
	proto_item* item = NULL;
	proto_tree* tree = NULL;
	guint32 flags;

	flags=tvb_get_ntohl(tvb, offset);
	if (parent_tree) {
		item = proto_tree_add_text(parent_tree, tvb, offset, 4,
				"Invalids: 0x%08x", flags);
		tree = proto_item_add_subtree(item, ett_ndmp_state_invalids);
	}

	proto_tree_add_boolean(tree, hf_ndmp_state_invalid_etr,
				tvb, offset, 4, flags);
	proto_tree_add_boolean(tree, hf_ndmp_state_invalid_ebr,
				tvb, offset, 4, flags);

	offset+=4;
	return offset;
}

#define NDMP_DATA_OP_NOACTION	0
#define NDMP_DATA_OP_BACKUP	1
#define NDMP_DATA_OP_RESTORE	2
static const value_string bu_operation_vals[] = {
	{NDMP_DATA_OP_NOACTION,	"NOACTION"},
	{NDMP_DATA_OP_BACKUP,	"BACKUP"},
	{NDMP_DATA_OP_RESTORE,	"RESTORE"},
	{0, NULL}
};

#define NDMP_DATA_STATE_IDLE		0
#define NDMP_DATA_STATE_ACTIVE		1
#define NDMP_DATA_STATE_HALTED		2
#define NDMP_DATA_STATE_LISTEN		3
#define NDMP_DATA_STATE_CONNECTED	4
static const value_string data_state_vals[] = {
	{NDMP_DATA_STATE_IDLE,		"IDLE"},
	{NDMP_DATA_STATE_ACTIVE,	"ACTIVE"},
	{NDMP_DATA_STATE_HALTED,	"HALTED"},
	{NDMP_DATA_STATE_LISTEN,	"LISTEN"},
	{NDMP_DATA_STATE_CONNECTED,	"CONNECTED"},
	{0, NULL}
};

#define NDMP_DATA_HALTED_NA		0
#define NDMP_DATA_HALTED_SUCCESSFUL	1
#define NDMP_DATA_HALTED_ABORTED	2
#define NDMP_DATA_HALTED_INTERNAL_ERROR	3
#define NDMP_DATA_HALTED_CONNECT_ERROR	4
static const value_string data_halted_vals[] = {
	{NDMP_DATA_HALTED_NA,			"HALTED_NA"},
	{NDMP_DATA_HALTED_SUCCESSFUL,		"HALTED_SUCCESSFUL"},
	{NDMP_DATA_HALTED_ABORTED,		"HALTED_ABORTED"},
	{NDMP_DATA_HALTED_INTERNAL_ERROR,	"HALTED_INTERNAL_ERROR"},
	{NDMP_DATA_HALTED_CONNECT_ERROR,	"HALTED_CONNECT_ERROR"},
	{0, NULL}
};

static int
dissect_data_get_state_reply(tvbuff_t *tvb, int offset, packet_info *pinfo,
    proto_tree *tree, guint32 seq)
{
	nstime_t ns;

	/* invalids */
	offset = dissect_state_invalids(tvb, offset, pinfo, tree);

	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, seq);

	/* operation */
	proto_tree_add_item(tree, hf_ndmp_bu_operation, tvb, offset, 4, FALSE);
	offset += 4;

	/* state */
	proto_tree_add_item(tree, hf_ndmp_data_state, tvb, offset, 4, FALSE);
	offset += 4;

	/* halted reason */
	proto_tree_add_item(tree, hf_ndmp_data_halted, tvb, offset, 4, FALSE);
	offset += 4;

	/*bytes processed*/
	offset = dissect_rpc_uint64(tvb, tree, hf_ndmp_data_bytes_processed,
			offset);

	/*est bytes remain*/
	offset = dissect_rpc_uint64(tvb, tree, hf_ndmp_data_est_bytes_remain,
			offset);

	/* est time remain */
	ns.secs=tvb_get_ntohl(tvb, offset);
	ns.nsecs=0;
	proto_tree_add_time(tree, hf_ndmp_data_est_time_remain, tvb, offset, 4, &ns);
	offset += 4;

	/* ndmp addr */
	offset=dissect_ndmp_addr(tvb, offset, pinfo, tree);

	/* window offset */
	proto_tree_add_item(tree, hf_ndmp_window_offset, tvb, offset, 8, FALSE);
	offset += 8;

	/* window length */
	proto_tree_add_item(tree, hf_ndmp_window_length, tvb, offset, 8, FALSE);
	offset += 8;

	return offset;
}


typedef struct _ndmp_command {
	guint32 cmd;
	int (*request) (tvbuff_t *tvb, int offset, packet_info *pinfo,
	    proto_tree *tree, guint32 seq);
	int (*response)(tvbuff_t *tvb, int offset, packet_info *pinfo,
	    proto_tree *tree, guint32 seq);
} ndmp_command;

static const ndmp_command ndmp_commands[] = {
	{NDMP_CONFIG_GET_HOST_INFO,
	 	NULL, dissect_ndmp_get_host_info_reply},
	{NDMP_CONFIG_GET_CONNECTION_TYPE,
		NULL, dissect_ndmp_config_get_connection_type_reply},
	{NDMP_CONFIG_GET_AUTH_ATTR,
		dissect_get_auth_type_request, dissect_auth_attr_msg},
	{NDMP_CONFIG_GET_BUTYPE_INFO,
		NULL, dissect_get_butype_info_reply},
	{NDMP_CONFIG_GET_FS_INFO,
		NULL, dissect_get_fs_info_reply},
	{NDMP_CONFIG_GET_TAPE_INFO,
		NULL, dissect_get_tape_info_reply},
	{NDMP_CONFIG_GET_SCSI_INFO,
		NULL, dissect_get_scsi_info_reply},
	{NDMP_CONFIG_GET_SERVER_INFO,
		NULL, dissect_get_server_info_reply},
	{NDMP_SCSI_OPEN,
		dissect_scsi_open_request, dissect_error},
	{NDMP_SCSI_CLOSE,
		NULL, dissect_error},
	{NDMP_SCSI_GET_STATE,
		NULL, dissect_scsi_get_state_reply},
	{NDMP_SCSI_SET_TARGET,
		dissect_scsi_set_state_request, dissect_error},
	{NDMP_SCSI_RESET_DEVICE,
		NULL, dissect_error},
	{NDMP_SCSI_RESET_BUS,
		NULL, dissect_error},
	{NDMP_SCSI_EXECUTE_CDB,
		dissect_execute_cdb_request_mc, dissect_execute_cdb_reply},
	{NDMP_TAPE_OPEN,
		dissect_tape_open_request, dissect_error},
	{NDMP_TAPE_CLOSE,
		NULL, dissect_error},
	{NDMP_TAPE_GET_STATE,
	 	NULL, dissect_tape_get_state_reply},
	{NDMP_TAPE_MTIO,
		dissect_tape_mtio_request, dissect_tape_mtio_reply},
	{NDMP_TAPE_WRITE,
		dissect_tape_write_request, dissect_tape_write_reply},
	{NDMP_TAPE_READ,
		dissect_tape_read_request, dissect_tape_read_reply},
	{NDMP_TAPE_EXECUTE_CDB,
		dissect_execute_cdb_request_tape, dissect_execute_cdb_reply},
	{NDMP_DATA_GET_STATE,
		NULL, dissect_data_get_state_reply},
	{NDMP_DATA_START_BACKUP,
		dissect_data_start_backup_request, dissect_error },
	{NDMP_DATA_START_RECOVER,
		dissect_data_start_recover_request, dissect_error },
	{NDMP_DATA_ABORT,
		NULL, dissect_error},
	{NDMP_DATA_GET_ENV,
		NULL, dissect_data_get_env_reply},
	{NDMP_DATA_STOP,
		NULL, dissect_error},
	{NDMP_DATA_LISTEN,
		dissect_ndmp_addr_msg, dissect_mover_listen_reply},
	{NDMP_DATA_CONNECT,
		dissect_data_connect_msg, dissect_error},
	{NDMP_NOTIFY_DATA_HALTED,
		dissect_notify_data_halted_request, NULL},
	{NDMP_NOTIFY_CONNECTED,
		dissect_notify_connected_request, NULL},
	{NDMP_NOTIFY_MOVER_HALTED,
		dissect_notify_data_halted_request, NULL},
	{NDMP_NOTIFY_MOVER_PAUSED,
		dissect_notify_mover_paused_request, NULL},
	{NDMP_NOTIFY_DATA_READ,
		dissect_mover_set_window_request, NULL},
	{NDMP_LOG_FILE,
		dissect_log_file_request, NULL},
	{NDMP_LOG_MESSAGE,
		dissect_log_message_request, NULL},
	{NDMP_FH_ADD_FILE,
		dissect_fh_add_file_request, NULL},
	{NDMP_FH_ADD_DIR,
		dissect_fh_add_dir_request, NULL},
	{NDMP_FH_ADD_NODE,
		dissect_fh_add_node_request, NULL},
	{NDMP_CONNECT_OPEN,
		dissect_connect_open_request, dissect_error},
	{NDMP_CONNECT_CLIENT_AUTH,
		dissect_connect_client_auth_request, dissect_error},
	{NDMP_CONNECT_CLOSE,
		NULL,NULL},
	{NDMP_CONNECT_SERVER_AUTH,
		dissect_auth_attr_msg, dissect_connect_server_auth_reply},
	{NDMP_MOVER_GET_STATE,
		NULL, dissect_mover_get_state_reply},
	{NDMP_MOVER_LISTEN,
		dissect_mover_listen_request, dissect_mover_listen_reply},
	{NDMP_MOVER_CONTINUE,
		NULL, dissect_error},
	{NDMP_MOVER_ABORT,
		NULL, dissect_error},
	{NDMP_MOVER_STOP,
		NULL, dissect_error},
	{NDMP_MOVER_SET_WINDOW,
		dissect_mover_set_window_request, dissect_error},
	{NDMP_MOVER_READ,
		dissect_mover_set_window_request, dissect_error},
	{NDMP_MOVER_CLOSE,
		NULL, dissect_error},
	{NDMP_MOVER_SET_RECORD_SIZE,
		dissect_mover_set_record_size_request, dissect_error},
	{NDMP_MOVER_CONNECT,
		dissect_mover_connect_request, dissect_error},
	{0, NULL,NULL}
};


static int
dissect_ndmp_header(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *parent_tree, struct ndmp_header *nh)
{
	proto_item* item = NULL;
	proto_tree* tree = NULL;
	nstime_t ns;

	if (parent_tree) {
		item = proto_tree_add_item(parent_tree, hf_ndmp_header, tvb,
				offset, 24, FALSE);
		tree = proto_item_add_subtree(item, ett_ndmp_header);
	}

	/* sequence number */
	proto_tree_add_uint(tree, hf_ndmp_sequence, tvb, offset, 4, nh->seq);
	offset += 4;

	/* timestamp */
	ns.secs=nh->time;
	ns.nsecs=0;
	proto_tree_add_time(tree, hf_ndmp_timestamp, tvb, offset, 4, &ns);
	offset += 4;

	/* Message Type */
	proto_tree_add_uint(tree, hf_ndmp_msgtype, tvb, offset, 4, nh->type);
	offset += 4;

	/* Message */
	proto_tree_add_uint(tree, hf_ndmp_msg, tvb, offset, 4, nh->msg);
	offset += 4;

	/* Reply sequence number */
	proto_tree_add_uint(tree, hf_ndmp_reply_sequence, tvb, offset, 4, nh->rep_seq);
	offset += 4;

	/* error */
	offset=dissect_error(tvb, offset, pinfo, tree, nh->seq);

	if (check_col(pinfo->cinfo, COL_INFO)){
		col_append_fstr(pinfo->cinfo, COL_INFO, "%s %s",
			val_to_str(nh->msg, msg_vals, "Unknown Message (0x%02x)"),
			val_to_str(nh->type, msg_type_vals, "Unknown Type (0x%02x)")
			);
	}

	return offset;
}


static int
dissect_ndmp_cmd(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree, struct ndmp_header *nh)
{
	int i;
	proto_item *cmd_item=NULL;
	proto_tree *cmd_tree=NULL;

	offset=dissect_ndmp_header(tvb, offset, pinfo, tree, nh);

	for(i=0;ndmp_commands[i].cmd!=0;i++){
		if(ndmp_commands[i].cmd==nh->msg){
			break;
		}
	}


	if(ndmp_commands[i].cmd==0){
		/* we do not know this message */
		proto_tree_add_text(tree, tvb, offset, -1, "Unknown type of NDMP message: 0x%02x", nh->msg);
		offset+=tvb_length_remaining(tvb, offset);
		return offset;
	}

	if (tvb_reported_length_remaining(tvb, offset) > 0) {
		if(tree){
			cmd_item = proto_tree_add_text(tree, tvb, offset, -1,
				msg_vals[i].strptr);
			cmd_tree = proto_item_add_subtree(cmd_item, ett_ndmp);
		}
	}

	if(nh->type==NDMP_MESSAGE_REQUEST){
		if(ndmp_commands[i].request){
			offset=ndmp_commands[i].request(tvb, offset, pinfo, cmd_tree,
			    nh->seq);
		}
	} else {
		if(ndmp_commands[i].response){
			offset=ndmp_commands[i].response(tvb, offset, pinfo, cmd_tree,
			    nh->rep_seq);
		}
	}

	return offset;
}

static void
dissect_ndmp_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
	int offset = 0;
	guint32 ndmp_rm;
	struct ndmp_header nh;
	guint32 size;
	proto_item *ndmp_item = NULL;
	proto_tree *ndmp_tree = NULL;
	proto_item *hdr_item = NULL;
	proto_tree *hdr_tree = NULL;

	/* size of this NDMP PDU */
	size = tvb_length_remaining(tvb, offset);
	if (size < 28) {
		/* too short to be NDMP */
		return;
	}

	/*
	 * Read the NDMP header, if we have it.
	 */
	ndmp_rm=tvb_get_ntohl(tvb, offset);
	nh.seq = tvb_get_ntohl(tvb, offset+4);
	nh.time = tvb_get_ntohl(tvb, offset+8);
	nh.type = tvb_get_ntohl(tvb, offset+12);
	nh.msg = tvb_get_ntohl(tvb, offset+16);
	nh.rep_seq = tvb_get_ntohl(tvb, offset+20);
	nh.err = tvb_get_ntohl(tvb, offset+24);

	/*
	 * Check if this is the last fragment.
	 */
	if (!(ndmp_rm & NDMP_RM_LASTFRAG)) {
		/*
		 * This isn't the last fragment.
		 * If we're doing reassembly, just return
		 * TRUE to indicate that this looks like
		 * the beginning of an NDMP message,
		 * and let them do reassembly.
		 */
		if (ndmp_defragment)
			return;
	}

	if (check_col(pinfo->cinfo, COL_PROTOCOL))
		col_set_str(pinfo->cinfo, COL_PROTOCOL, "NDMP");
	if (check_col(pinfo->cinfo, COL_INFO)) {
		col_clear(pinfo->cinfo, COL_INFO);
	}

	if (tree) {
		ndmp_item = proto_tree_add_item(tree, proto_ndmp,
		    tvb, 0, -1, FALSE);
		ndmp_tree = proto_item_add_subtree(ndmp_item, ett_ndmp);
	}

	hdr_item = proto_tree_add_text(ndmp_tree, tvb, 0, 4, 
		"Fragment header: %s%u %s", 
		(ndmp_rm & NDMP_RM_LASTFRAG) ? "Last fragment, " : "", 
		ndmp_rm & NDMP_RM_LENGTH, plurality(ndmp_rm & NDMP_RM_LENGTH, "byte", "bytes"));
	hdr_tree = proto_item_add_subtree(hdr_item, ett_ndmp_fraghdr);
	proto_tree_add_boolean(hdr_tree, hf_ndmp_lastfrag, tvb, 0, 4, ndmp_rm);
	proto_tree_add_uint(hdr_tree, hf_ndmp_fraglen, tvb, 0, 4, ndmp_rm);

	/*
	 * We cannot trust what dissect_ndmp_cmd() tells us, as there
	 * are implementations which pad some additional data after
	 * the PDU.  We MUST use size.
	 */
	dissect_ndmp_cmd(tvb, offset+4, pinfo, ndmp_tree, &nh);
	return;
}

static guint
get_ndmp_pdu_len(tvbuff_t *tvb, int offset)
{
  guint len;

  len=tvb_get_ntohl(tvb, offset)&0x7fffffff;
  /* Get the length of the NDMP packet. */

  /*XXX check header for sanity */
  return len+4;
}

static int
dissect_ndmp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
	guint len;
	guint32 tmp;


	/* check that the hgeader looks sane */
	len=tvb_length(tvb);
	/* check the record marker that it looks sane.
	 * It has to be >=24 bytes or (arbitrary limit) <1Mbyte
	 */
	if(len>=4){
		tmp=(tvb_get_ntohl(tvb, 0)&NDMP_RM_LENGTH);
		if( (tmp<24)||(tmp>1000000) ){
			return 0;
		}
	}
	/* check the timestamp,  timestamps are valid if they
	 * (arbitrary) lie between 1980-jan-1 and 2030-jan-1
	 */
	if(len>=12){
		tmp=tvb_get_ntohl(tvb, 8);
		if( (tmp<0x12ceec50)||(tmp>0x70dc1ed0) ){
			return 0;
		}
	}
	/* check the type */
	if(len>=16){
		tmp=tvb_get_ntohl(tvb, 12);
		if( tmp>1 ){
			return 0;
		}
	}
	/* check message */
	if(len>=20){
		tmp=tvb_get_ntohl(tvb, 16);
		if( (tmp>0xa09) || (tmp==0) ){
			return 0;
		}
	}
	/* check error */
	if(len>=28){
		tmp=tvb_get_ntohl(tvb, 24);
		if( (tmp>0x17) ){
			return 0;
		}
	}


	tcp_dissect_pdus(tvb, pinfo, tree, ndmp_desegment, 28,
			 get_ndmp_pdu_len, dissect_ndmp_message);
	return tvb_length(tvb);
}

void
proto_register_ndmp(void)
{

  static hf_register_info hf_ndmp[] = {
	{ &hf_ndmp_header, {
		"NDMP Header", "ndmp.header", FT_NONE, 0,
		NULL, 0, "NDMP Header", HFILL }},

	{ &hf_ndmp_sequence, {
		"Sequence", "ndmp.sequence", FT_UINT32, BASE_DEC,
		NULL, 0, "Sequence number for NDMP PDU", HFILL }},

	{ &hf_ndmp_reply_sequence, {
		"Reply Sequence", "ndmp.reply_sequence", FT_UINT32, BASE_DEC,
		NULL, 0, "Reply Sequence number for NDMP PDU", HFILL }},

	{ &hf_ndmp_timestamp, {
		"Time", "ndmp.timestamp", FT_ABSOLUTE_TIME, BASE_NONE,
		NULL, 0, "Timestamp for this NDMP PDU", HFILL }},

	{ &hf_ndmp_msgtype, {
		"Type", "ndmp.msg_type", FT_UINT32, BASE_DEC,
		VALS(msg_type_vals), 0, "Is this a Request or Response?", HFILL }},

	{ &hf_ndmp_msg, {
		"Message", "ndmp.msg", FT_UINT32, BASE_HEX,
		VALS(msg_vals), 0, "Type of NDMP PDU", HFILL }},

	{ &hf_ndmp_error, {
		"Error", "ndmp.error", FT_UINT32, BASE_DEC,
		VALS(error_vals), 0, "Error code for this NDMP PDU", HFILL }},

	{ &hf_ndmp_version, {
		"Version", "ndmp.version", FT_UINT32, BASE_DEC,
		NULL, 0, "Version of NDMP protocol", HFILL }},

	{ &hf_ndmp_hostname, {
		"Hostname", "ndmp.hostname", FT_STRING, BASE_NONE,
		NULL, 0, "Hostname", HFILL }},

	{ &hf_ndmp_hostid, {
		"HostID", "ndmp.hostid", FT_STRING, BASE_NONE,
		NULL, 0, "HostID", HFILL }},

	{ &hf_ndmp_os_type, {
		"OS Type", "ndmp.os.type", FT_STRING, BASE_NONE,
		NULL, 0, "OS Type", HFILL }},

	{ &hf_ndmp_os_vers, {
		"OS Version", "ndmp.os.version", FT_STRING, BASE_NONE,
		NULL, 0, "OS Version", HFILL }},

	{ &hf_ndmp_addr_types, {
		"Addr Types", "ndmp.addr_types", FT_NONE, BASE_NONE,
		NULL, 0, "List Of Address Types", HFILL }},

	{ &hf_ndmp_addr_type, {
		"Addr Type", "ndmp.addr_type", FT_UINT32, BASE_DEC,
		VALS(addr_type_vals), 0, "Address Type", HFILL }},

	{ &hf_ndmp_auth_type, {
		"Auth Type", "ndmp.auth_type", FT_UINT32, BASE_DEC,
		VALS(auth_type_vals), 0, "Authentication Type", HFILL }},

	{ &hf_ndmp_auth_challenge, {
		"Challenge", "ndmp.auth.challenge", FT_BYTES, BASE_HEX,
		NULL, 0, "Authentication Challenge", HFILL }},

	{ &hf_ndmp_auth_digest, {
		"Digest", "ndmp.auth.digest", FT_BYTES, BASE_HEX,
		NULL, 0, "Authentication Digest", HFILL }},

	{ &hf_ndmp_butype_info, {
		"Butype Info", "ndmp.butype.info", FT_NONE, BASE_NONE,
		NULL, 0, "Butype Info", HFILL }},

	{ &hf_ndmp_butype_name, {
		"Butype Name", "ndmp.butype.name", FT_STRING, BASE_NONE,
		NULL, 0, "Name of Butype", HFILL }},

	{ &hf_ndmp_butype_default_env, {
		"Default Env", "ndmp.butype.default_env", FT_NONE, BASE_NONE,
		NULL, 0, "Default Env's for this Butype Info", HFILL }},

	{ &hf_ndmp_tcp_addr_list, {
		"TCP Ports", "ndmp.tcp.port_list", FT_NONE, BASE_NONE,
		NULL, 0, "List of TCP ports", HFILL }},

	{ &hf_ndmp_tcp_default_env, {
		"Default Env", "ndmp.tcp.default_env", FT_NONE, BASE_NONE,
		NULL, 0, "Default Env's for this Butype Info", HFILL }},

	{ &hf_ndmp_butype_attr_backup_file_history, {
		"", "ndmp.butype.attr.backup_file_history", FT_BOOLEAN, 32,
		TFS(&tfs_butype_attr_backup_file_history), 0x00000001, "backup_file_history", HFILL }},

	{ &hf_ndmp_butype_attr_backup_filelist, {
		"", "ndmp.butype.attr.backup_filelist", FT_BOOLEAN, 32,
		TFS(&tfs_butype_attr_backup_filelist), 0x00000002, "backup_filelist", HFILL }},

	{ &hf_ndmp_butype_attr_recover_filelist, {
		"", "ndmp.butype.attr.recover_filelist", FT_BOOLEAN, 32,
		TFS(&tfs_butype_attr_recover_filelist), 0x00000004, "recover_filelist", HFILL }},

	{ &hf_ndmp_butype_attr_backup_direct, {
		"", "ndmp.butype.attr.backup_direct", FT_BOOLEAN, 32,
		TFS(&tfs_butype_attr_backup_direct), 0x00000008, "backup_direct", HFILL }},

	{ &hf_ndmp_butype_attr_recover_direct, {
		"", "ndmp.butype.attr.recover_direct", FT_BOOLEAN, 32,
		TFS(&tfs_butype_attr_recover_direct), 0x00000010, "recover_direct", HFILL }},

	{ &hf_ndmp_butype_attr_backup_incremental, {
		"", "ndmp.butype.attr.backup_incremental", FT_BOOLEAN, 32,
		TFS(&tfs_butype_attr_backup_incremental), 0x00000020, "backup_incremental", HFILL }},

	{ &hf_ndmp_butype_attr_recover_incremental, {
		"", "ndmp.butype.attr.recover_incremental", FT_BOOLEAN, 32,
		TFS(&tfs_butype_attr_recover_incremental), 0x00000040, "recover_incremental", HFILL }},

	{ &hf_ndmp_butype_attr_backup_utf8, {
		"", "ndmp.butype.attr.backup_utf8", FT_BOOLEAN, 32,
		TFS(&tfs_butype_attr_backup_utf8), 0x00000080, "backup_utf8", HFILL }},

	{ &hf_ndmp_butype_attr_recover_utf8, {
		"", "ndmp.butype.attr.recover_utf8", FT_BOOLEAN, 32,
		TFS(&tfs_butype_attr_recover_utf8), 0x00000100, "recover_utf8", HFILL }},

	{ &hf_ndmp_butype_env_name, {
		"Name", "ndmp.butype.env.name", FT_STRING, BASE_NONE,
		NULL, 0, "Name for this env-variable", HFILL }},

	{ &hf_ndmp_butype_env_value, {
		"Value", "ndmp.butype.env.value", FT_STRING, BASE_NONE,
		NULL, 0, "Value for this env-variable", HFILL }},

	{ &hf_ndmp_tcp_env_name, {
		"Name", "ndmp.tcp.env.name", FT_STRING, BASE_NONE,
		NULL, 0, "Name for this env-variable", HFILL }},

	{ &hf_ndmp_tcp_env_value, {
		"Value", "ndmp.tcp.env.value", FT_STRING, BASE_NONE,
		NULL, 0, "Value for this env-variable", HFILL }},

	{ &hf_ndmp_fs_info, {
		"FS Info", "ndmp.fs.info", FT_NONE, BASE_NONE,
		NULL, 0, "FS Info", HFILL }},

	{ &hf_ndmp_fs_invalid_total_size, {
		"", "ndmp.fs.invalid.total_size", FT_BOOLEAN, 32,
		TFS(&tfs_fs_invalid_total_size), 0x00000001, "If total size is invalid", HFILL }},

	{ &hf_ndmp_fs_invalid_used_size, {
		"", "ndmp.fs.invalid.used_size", FT_BOOLEAN, 32,
		TFS(&tfs_fs_invalid_used_size), 0x00000002, "If used size is invalid", HFILL }},

	{ &hf_ndmp_fs_invalid_avail_size, {
		"", "ndmp.fs.invalid.avail_size", FT_BOOLEAN, 32,
		TFS(&tfs_fs_invalid_avail_size), 0x00000004, "If available size is invalid", HFILL }},

	{ &hf_ndmp_fs_invalid_total_inodes, {
		"", "ndmp.fs.invalid.total_inodes", FT_BOOLEAN, 32,
		TFS(&tfs_fs_invalid_total_inodes), 0x00000008, "If total number of inodes is invalid", HFILL }},

	{ &hf_ndmp_fs_invalid_used_inodes, {
		"", "ndmp.fs.invalid.used_inodes", FT_BOOLEAN, 32,
		TFS(&tfs_fs_invalid_used_inodes), 0x00000010, "If used number of inodes is invalid", HFILL }},

	{ &hf_ndmp_fs_fs_type, {
		"Type", "ndmp.fs.type", FT_STRING, BASE_NONE,
		NULL, 0, "Type of FS", HFILL }},

	{ &hf_ndmp_fs_logical_device, {
		"Logical Device", "ndmp.fs.logical_device", FT_STRING, BASE_NONE,
		NULL, 0, "Name of logical device", HFILL }},

	{ &hf_ndmp_fs_physical_device, {
		"Physical Device", "ndmp.fs.physical_device", FT_STRING, BASE_NONE,
		NULL, 0, "Name of physical device", HFILL }},

	{ &hf_ndmp_fs_total_size, {
		"Total Size", "ndmp.fs.total_size", FT_UINT64, BASE_DEC,
		NULL, 0, "Total size of FS", HFILL }},

	{ &hf_ndmp_fs_used_size, {
		"Used Size", "ndmp.fs.used_size", FT_UINT64, BASE_DEC,
		NULL, 0, "Total used size of FS", HFILL }},

	{ &hf_ndmp_fs_avail_size, {
		"Avail Size", "ndmp.fs.avail_size", FT_UINT64, BASE_DEC,
		NULL, 0, "Total available size on FS", HFILL }},

	{ &hf_ndmp_fs_total_inodes, {
		"Total Inodes", "ndmp.fs.total_inodes", FT_UINT64, BASE_DEC,
		NULL, 0, "Total number of inodes on FS", HFILL }},

	{ &hf_ndmp_fs_used_inodes, {
		"Used Inodes", "ndmp.fs.used_inodes", FT_UINT64, BASE_DEC,
		NULL, 0, "Number of used inodes on FS", HFILL }},

	{ &hf_ndmp_fs_env, {
		"Env variables", "ndmp.fs.env", FT_NONE, BASE_NONE,
		NULL, 0, "Environment variables for FS", HFILL }},

	{ &hf_ndmp_fs_env_name, {
		"Name", "ndmp.fs.env.name", FT_STRING, BASE_NONE,
		NULL, 0, "Name for this env-variable", HFILL }},

	{ &hf_ndmp_fs_env_value, {
		"Value", "ndmp.fs.env.value", FT_STRING, BASE_NONE,
		NULL, 0, "Value for this env-variable", HFILL }},

	{ &hf_ndmp_fs_status, {
		"Status", "ndmp.fs.status", FT_STRING, BASE_NONE,
		NULL, 0, "Status for this FS", HFILL }},

	{ &hf_ndmp_tape_info, {
		"Tape Info", "ndmp.tape.info", FT_NONE, BASE_NONE,
		NULL, 0, "Tape Info", HFILL }},

	{ &hf_ndmp_tape_model, {
		"Model", "ndmp.tape.model", FT_STRING, BASE_NONE,
		NULL, 0, "Model of the TAPE drive", HFILL }},

	{ &hf_ndmp_tape_dev_cap, {
		"Device Capability", "ndmp.tape.dev_cap", FT_NONE, BASE_NONE,
		NULL, 0, "Tape Device Capability", HFILL }},

	{ &hf_ndmp_tape_device, {
		"Device", "ndmp.tape.device", FT_STRING, BASE_NONE,
		NULL, 0, "Name of TAPE Device", HFILL }},

	{ &hf_ndmp_tape_attr_rewind, {
		"", "ndmp.tape.attr.rewind", FT_BOOLEAN, 32,
		TFS(&tfs_tape_attr_rewind), 0x00000001, "If this device supports rewind", HFILL }},

	{ &hf_ndmp_tape_attr_unload, {
		"", "ndmp.tape.attr.unload", FT_BOOLEAN, 32,
		TFS(&tfs_tape_attr_unload), 0x00000002, "If this device supports unload", HFILL }},

	{ &hf_ndmp_tape_capability, {
		"Tape Capabilities", "ndmp.tape.capability", FT_NONE, BASE_NONE,
		NULL, 0, "Tape Capabilities", HFILL }},

	{ &hf_ndmp_tape_capability_name, {
		"Name", "ndmp.tape.cap.name", FT_STRING, BASE_NONE,
		NULL, 0, "Name for this env-variable", HFILL }},

	{ &hf_ndmp_tape_capability_value, {
		"Value", "ndmp.tape.cap.value", FT_STRING, BASE_NONE,
		NULL, 0, "Value for this env-variable", HFILL }},

	{ &hf_ndmp_scsi_info, {
		"SCSI Info", "ndmp.scsi.info", FT_NONE, BASE_NONE,
		NULL, 0, "SCSI Info", HFILL }},

	{ &hf_ndmp_scsi_model, {
		"Model", "ndmp.scsi.model", FT_STRING, BASE_NONE,
		NULL, 0, "Model of the SCSI device", HFILL }},

	{ &hf_ndmp_server_vendor, {
		"Vendor", "ndmp.server.vendor", FT_STRING, BASE_NONE,
		NULL, 0, "Name of vendor", HFILL }},

	{ &hf_ndmp_server_product, {
		"Product", "ndmp.server.product", FT_STRING, BASE_NONE,
		NULL, 0, "Name of product", HFILL }},

	{ &hf_ndmp_server_revision, {
		"Revision", "ndmp.server.revision", FT_STRING, BASE_NONE,
		NULL, 0, "Revision of this product", HFILL }},

	{ &hf_ndmp_auth_types, {
		"Auth types", "ndmp.auth.types", FT_NONE, BASE_NONE,
		NULL, 0, "Auth types", HFILL }},

	{ &hf_ndmp_scsi_device, {
		"Device", "ndmp.scsi.device", FT_STRING, BASE_NONE,
		NULL, 0, "Name of SCSI Device", HFILL }},

	{ &hf_ndmp_scsi_controller, {
		"Controller", "ndmp.scsi.controller", FT_UINT32, BASE_DEC,
		NULL, 0, "Target Controller", HFILL }},

	{ &hf_ndmp_scsi_id, {
		"ID", "ndmp.scsi.id", FT_UINT32, BASE_DEC,
		NULL, 0, "Target ID", HFILL }},

	{ &hf_ndmp_scsi_lun, {
		"LUN", "ndmp.scsi.lun", FT_UINT32, BASE_DEC,
		NULL, 0, "Target LUN", HFILL }},

	{ &hf_ndmp_execute_cdb_flags_data_in, {
		"DATA_IN", "ndmp.execute_cdb.flags.data_in", FT_BOOLEAN, 32,
		NULL, 0x00000001, "DATA_IN", HFILL }},

	{ &hf_ndmp_execute_cdb_flags_data_out, {
		"DATA_OUT", "ndmp.execute_cdb.flags.data_out", FT_BOOLEAN, 32,
		NULL, 0x00000002, "DATA_OUT", HFILL }},

	{ &hf_ndmp_execute_cdb_timeout, {
		"Timeout", "ndmp.execute_cdb.timeout", FT_UINT32, BASE_DEC,
		NULL, 0, "Reselect timeout, in milliseconds", HFILL }},

	{ &hf_ndmp_execute_cdb_datain_len, {
		"Data in length", "ndmp.execute_cdb.datain_len", FT_UINT32, BASE_DEC,
		NULL, 0, "Expected length of data bytes to read", HFILL }},

	{ &hf_ndmp_execute_cdb_cdb_len, {
		"CDB length", "ndmp.execute_cdb.cdb_len", FT_UINT32, BASE_DEC,
		NULL, 0, "Length of CDB", HFILL }},

	{ &hf_ndmp_execute_cdb_dataout, {
		"Data out", "ndmp.execute_cdb.dataout", FT_BYTES, BASE_NONE,
		NULL, 0, "Data to be transferred to the SCSI device", HFILL }},

	{ &hf_ndmp_execute_cdb_status, {
		"Status", "ndmp.execute_cdb.status", FT_UINT8, BASE_DEC,
		VALS(scsi_status_val), 0, "SCSI status", HFILL }},

	{ &hf_ndmp_execute_cdb_dataout_len, {
		"Data out length", "ndmp.execute_cdb.dataout_len", FT_UINT32, BASE_DEC,
		NULL, 0, "Number of bytes transferred to the device", HFILL }},

	{ &hf_ndmp_execute_cdb_datain, {
		"Data in", "ndmp.execute_cdb.datain", FT_BYTES, BASE_NONE,
		NULL, 0, "Data transferred from the SCSI device", HFILL }},

	{ &hf_ndmp_execute_cdb_sns_len, {
		"Sense data length", "ndmp.execute_cdb.sns_len", FT_UINT32, BASE_DEC,
		NULL, 0, "Length of sense data", HFILL }},

	{ &hf_ndmp_tape_open_mode, {
		"Mode", "ndmp.tape.open_mode", FT_UINT32, BASE_DEC,
		VALS(tape_open_mode_vals), 0, "Mode to open tape in", HFILL }},

	{ &hf_ndmp_tape_invalid_file_num, {
		"", "ndmp.tape.invalid.file_num", FT_BOOLEAN, 32,
		TFS(&tfs_ndmp_tape_invalid_file_num), 0x00000001, "invalid_file_num", HFILL }},

	{ &hf_ndmp_tape_invalid_soft_errors, {
		"", "ndmp.tape.invalid.soft_errors", FT_BOOLEAN, 32,
		TFS(&tfs_ndmp_tape_invalid_soft_errors), 0x00000002, "soft_errors", HFILL }},

	{ &hf_ndmp_tape_invalid_block_size, {
		"", "ndmp.tape.invalid.block_size", FT_BOOLEAN, 32,
		TFS(&tfs_ndmp_tape_invalid_block_size), 0x00000004, "block_size", HFILL }},

	{ &hf_ndmp_tape_invalid_block_no, {
		"", "ndmp.tape.invalid.block_no", FT_BOOLEAN, 32,
		TFS(&tfs_ndmp_tape_invalid_block_no), 0x00000008, "block_no", HFILL }},

	{ &hf_ndmp_tape_invalid_total_space, {
		"", "ndmp.tape.invalid.total_space", FT_BOOLEAN, 32,
		TFS(&tfs_ndmp_tape_invalid_total_space), 0x00000010, "total_space", HFILL }},

	{ &hf_ndmp_tape_invalid_space_remain, {
		"", "ndmp.tape.invalid.space_remain", FT_BOOLEAN, 32,
		TFS(&tfs_ndmp_tape_invalid_space_remain), 0x00000020, "space_remain", HFILL }},

	{ &hf_ndmp_tape_invalid_partition, {
		"", "ndmp.tape.invalid.partition", FT_BOOLEAN, 32,
		TFS(&tfs_ndmp_tape_invalid_partition), 0x00000040, "partition", HFILL }},

	{ &hf_ndmp_tape_flags_no_rewind, {
		"", "ndmp.tape.flags.no_rewind", FT_BOOLEAN, 32,
		TFS(&tfs_ndmp_tape_flags_no_rewind), 0x00000008, "no_rewind", HFILL, }},

	{ &hf_ndmp_tape_flags_write_protect, {
		"", "ndmp.tape.flags.write_protect", FT_BOOLEAN, 32,
		TFS(&tfs_ndmp_tape_flags_write_protect), 0x00000010, "write_protect", HFILL, }},

	{ &hf_ndmp_tape_flags_error, {
		"", "ndmp.tape.flags.error", FT_BOOLEAN, 32,
		TFS(&tfs_ndmp_tape_flags_error), 0x00000020, "error", HFILL, }},

	{ &hf_ndmp_tape_flags_unload, {
		"", "ndmp.tape.flags.unload", FT_BOOLEAN, 32,
		TFS(&tfs_ndmp_tape_flags_unload), 0x00000040, "unload", HFILL, }},

	{ &hf_ndmp_tape_file_num, {
		"file_num", "ndmp.tape.status.file_num", FT_UINT32, BASE_DEC,
		NULL, 0, "file_num", HFILL }},

	{ &hf_ndmp_tape_soft_errors, {
		"soft_errors", "ndmp.tape.status.soft_errors", FT_UINT32, BASE_DEC,
		NULL, 0, "soft_errors", HFILL }},

	{ &hf_ndmp_tape_block_size, {
		"block_size", "ndmp.tape.status.block_size", FT_UINT32, BASE_DEC,
		NULL, 0, "block_size", HFILL }},

	{ &hf_ndmp_tape_block_no, {
		"block_no", "ndmp.tape.status.block_no", FT_UINT32, BASE_DEC,
		NULL, 0, "block_no", HFILL }},

	{ &hf_ndmp_tape_total_space, {
		"total_space", "ndmp.tape.status.total_space", FT_UINT64, BASE_DEC,
		NULL, 0, "total_space", HFILL }},

	{ &hf_ndmp_tape_space_remain, {
		"space_remain", "ndmp.tape.status.space_remain", FT_UINT64, BASE_DEC,
		NULL, 0, "space_remain", HFILL }},

	{ &hf_ndmp_tape_partition, {
		"partition", "ndmp.tape.status.partition", FT_UINT32, BASE_DEC,
		NULL, 0, "partition", HFILL }},

	{ &hf_ndmp_tape_mtio_op, {
		"Operation", "ndmp.tape.mtio.op", FT_UINT32, BASE_DEC,
		VALS(tape_mtio_vals), 0, "MTIO Operation", HFILL }},

	{ &hf_ndmp_count, {
		"Count", "ndmp.count", FT_UINT32, BASE_DEC,
		NULL, 0, "Number of bytes/objects/operations", HFILL }},

	{ &hf_ndmp_resid_count, {
		"Resid Count", "ndmp.resid_count", FT_UINT32, BASE_DEC,
		NULL, 0, "Number of remaining bytes/objects/operations", HFILL }},

	{ &hf_ndmp_mover_state, {
		"State", "ndmp.mover.state", FT_UINT32, BASE_DEC,
		VALS(mover_state_vals), 0, "State of the selected mover", HFILL }},

	{ &hf_ndmp_mover_pause, {
		"Pause", "ndmp.mover.pause", FT_UINT32, BASE_DEC,
		VALS(mover_pause_vals), 0, "Reason why the mover paused", HFILL }},

	{ &hf_ndmp_halt, {
		"Halt", "ndmp.halt", FT_UINT32, BASE_DEC,
		VALS(halt_vals), 0, "Reason why it halted", HFILL }},

	{ &hf_ndmp_record_size, {
		"Record Size", "ndmp.record.size", FT_UINT32, BASE_DEC,
		NULL, 0, "Record size in bytes", HFILL }},

	{ &hf_ndmp_record_num, {
		"Record Num", "ndmp.record.num", FT_UINT32, BASE_DEC,
		NULL, 0, "Number of records", HFILL }},

	{ &hf_ndmp_data_written, {
		"Data Written", "ndmp.data.written", FT_UINT64, BASE_DEC,
		NULL, 0, "Number of data bytes written", HFILL }},

	{ &hf_ndmp_seek_position, {
		"Seek Position", "ndmp.seek.position", FT_UINT64, BASE_DEC,
		NULL, 0, "Current seek position on device", HFILL }},

	{ &hf_ndmp_bytes_left_to_read, {
		"Bytes left to read", "ndmp.bytes_left_to_read", FT_INT64, BASE_DEC,
		NULL, 0, "Number of bytes left to be read from the device", HFILL }},

	{ &hf_ndmp_window_offset, {
		"Window Offset", "ndmp.window.offset", FT_UINT64, BASE_DEC,
		NULL, 0, "Offset to window in bytes", HFILL }},

	{ &hf_ndmp_window_length, {
		"Window Length", "ndmp.window.length", FT_UINT64, BASE_DEC,
		NULL, 0, "Size of window in bytes", HFILL }},

	{ &hf_ndmp_addr_ip, {
		"IP Address", "ndmp.addr.ip", FT_IPv4, BASE_DEC,
		NULL, 0, "IP Address", HFILL }},

	{ &hf_ndmp_addr_tcp, {
		"TCP Port", "ndmp.addr.tcp_port", FT_UINT32, BASE_DEC,
		NULL, 0, "TCP Port", HFILL }},

	{ &hf_ndmp_addr_fcal_loop_id, {
		"Loop ID", "ndmp.addr.loop_id", FT_UINT32, BASE_HEX,
		NULL, 0, "FCAL Loop ID", HFILL }},

	{ &hf_ndmp_addr_ipc, {
		"IPC", "ndmp.addr.ipc", FT_BYTES, BASE_HEX,
		NULL, 0, "IPC identifier", HFILL }},

	{ &hf_ndmp_mover_mode, {
		"Mode", "ndmp.mover.mode", FT_UINT32, BASE_HEX,
		VALS(mover_mode_vals), 0, "Mover Mode", HFILL }},

	{ &hf_ndmp_file_name, {
		"File", "ndmp.file", FT_STRING, BASE_NONE,
		NULL, 0, "Name of File", HFILL }},

	{ &hf_ndmp_nt_file_name, {
		"NT File", "ndmp.file", FT_STRING, BASE_NONE,
		NULL, 0, "NT Name of File", HFILL }},

	{ &hf_ndmp_dos_file_name, {
		"DOS File", "ndmp.file", FT_STRING, BASE_NONE,
		NULL, 0, "DOS Name of File", HFILL }},

	{ &hf_ndmp_log_type, {
		"Type", "ndmp.log.type", FT_UINT32, BASE_HEX,
		VALS(log_type_vals), 0, "Type of log entry", HFILL }},

	{ &hf_ndmp_log_message_id, {
		"Message ID", "ndmp.log.message.id", FT_UINT32, BASE_DEC,
		NULL, 0, "ID of this log entry", HFILL }},

	{ &hf_ndmp_log_message, {
		"Message", "ndmp.log.message", FT_STRING, BASE_NONE,
		NULL, 0, "Log entry", HFILL }},

	{ &hf_ndmp_halt_reason, {
		"Reason", "ndmp.halt.reason", FT_STRING, BASE_NONE,
		NULL, 0, "Textual reason for why it halted", HFILL }},

	{ &hf_ndmp_connected, {
		"Connected", "ndmp.connected", FT_UINT32, BASE_DEC,
		VALS(connected_vals), 0, "Status of connection", HFILL }},

	{ &hf_ndmp_connected_reason, {
		"Reason", "ndmp.connected.reason", FT_STRING, BASE_NONE,
		NULL, 0, "Textual description of the connection status", HFILL }},

	{ &hf_ndmp_auth_id, {
		"ID", "ndmp.auth.id", FT_STRING, BASE_NONE,
		NULL, 0, "ID of client authenticating", HFILL }},

	{ &hf_ndmp_auth_password, {
		"Password", "ndmp.auth.password", FT_STRING, BASE_NONE,
		NULL, 0, "Password of client authenticating", HFILL }},

	{ &hf_ndmp_data, {
		"Data", "ndmp.data", FT_BYTES, BASE_HEX,
		NULL, 0, "Data written/read", HFILL }},

	{ &hf_ndmp_files, {
		"Files", "ndmp.files", FT_NONE, 0,
		NULL, 0, "List of files", HFILL }},

	{ &hf_ndmp_file_names, {
		"File Names", "ndmp.file.names", FT_NONE, 0,
		NULL, 0, "List of file names", HFILL }},

	{ &hf_ndmp_file_fs_type, {
		"File FS Type", "ndmp.file.fs_type", FT_UINT32, BASE_DEC,
		VALS(file_fs_type_vals), 0, "Type of file permissions (UNIX or NT)", HFILL }},

	{ &hf_ndmp_file_type, {
		"File Type", "ndmp.file.type", FT_UINT32, BASE_DEC,
		VALS(file_type_vals), 0, "Type of file", HFILL }},

	{ &hf_ndmp_file_stats, {
		"File Stats", "ndmp.file.stats", FT_NONE, 0,
		NULL, 0, "List of file stats", HFILL }},

	{ &hf_ndmp_file_node, {
		"Node", "ndmp.file.node", FT_UINT64, BASE_DEC,
		NULL, 0, "Node used for direct access", HFILL }},

	{ &hf_ndmp_file_parent, {
		"Parent", "ndmp.file.parent", FT_UINT64, BASE_DEC,
		NULL, 0, "Parent node(directory) for this node", HFILL }},

	{ &hf_ndmp_file_fh_info, {
		"FH Info", "ndmp.file.fh_info", FT_UINT64, BASE_DEC,
		NULL, 0, "FH Info used for direct access", HFILL }},

	{ &hf_ndmp_file_invalid_atime, {
		"", "ndmp.file.invalid_atime", FT_BOOLEAN, 32,
		TFS(&tfs_ndmp_file_invalid_atime), 0x00000001, "invalid_atime", HFILL, }},

	{ &hf_ndmp_file_invalid_ctime, {
		"", "ndmp.file.invalid_ctime", FT_BOOLEAN, 32,
		TFS(&tfs_ndmp_file_invalid_ctime), 0x00000002, "invalid_ctime", HFILL, }},

	{ &hf_ndmp_file_invalid_group, {
		"", "ndmp.file.invalid_group", FT_BOOLEAN, 32,
		TFS(&tfs_ndmp_file_invalid_group), 0x00000004, "invalid_group", HFILL, }},

	{ &hf_ndmp_file_mtime, {
		"mtime", "ndmp.file.mtime", FT_ABSOLUTE_TIME, BASE_NONE,
		NULL, 0, "Timestamp for mtime for this file", HFILL }},

	{ &hf_ndmp_file_atime, {
		"atime", "ndmp.file.atime", FT_ABSOLUTE_TIME, BASE_NONE,
		NULL, 0, "Timestamp for atime for this file", HFILL }},

	{ &hf_ndmp_file_ctime, {
		"ctime", "ndmp.file.ctime", FT_ABSOLUTE_TIME, BASE_NONE,
		NULL, 0, "Timestamp for ctime for this file", HFILL }},

	{ &hf_ndmp_file_owner, {
		"Owner", "ndmp.file.owner", FT_UINT32, BASE_DEC,
		NULL, 0, "UID for UNIX, owner for NT", HFILL }},

	{ &hf_ndmp_file_group, {
		"Group", "ndmp.file.group", FT_UINT32, BASE_DEC,
		NULL, 0, "GID for UNIX, NA for NT", HFILL }},

	{ &hf_ndmp_file_fattr, {
		"Fattr", "ndmp.file.fattr", FT_UINT32, BASE_HEX,
		NULL, 0, "Mode for UNIX, fattr for NT", HFILL }},

	{ &hf_ndmp_file_size, {
		"Size", "ndmp.file.size", FT_UINT64, BASE_DEC,
		NULL, 0, "File Size", HFILL }},

	{ &hf_ndmp_file_links, {
		"Links", "ndmp.file.links", FT_UINT32, BASE_DEC,
		NULL, 0, "Number of links to this file", HFILL }},

	{ &hf_ndmp_dirs, {
		"Dirs", "ndmp.dirs", FT_NONE, 0,
		NULL, 0, "List of directories", HFILL }},

	{ &hf_ndmp_nodes, {
		"Nodes", "ndmp.nodes", FT_NONE, 0,
		NULL, 0, "List of nodes", HFILL }},

	{ &hf_ndmp_nlist, {
		"Nlist", "ndmp.nlist", FT_NONE, 0,
		NULL, 0, "List of names", HFILL }},

	{ &hf_ndmp_bu_original_path, {
		"Original Path", "ndmp.bu.original_path", FT_STRING, BASE_NONE,
		NULL, 0, "Original path where backup was created", HFILL }},

	{ &hf_ndmp_bu_destination_dir, {
		"Destination Dir", "ndmp.bu.destination_dir", FT_STRING, BASE_NONE,
		NULL, 0, "Destination directory to restore backup to", HFILL }},

	{ &hf_ndmp_bu_new_name, {
		"New Name", "ndmp.bu.new_name", FT_STRING, BASE_NONE,
		NULL, 0, "New Name", HFILL }},

	{ &hf_ndmp_bu_other_name, {
		"Other Name", "ndmp.bu.other_name", FT_STRING, BASE_NONE,
		NULL, 0, "Other Name", HFILL }},

	{ &hf_ndmp_state_invalid_ebr, {
		"", "ndmp.bu.state.invalid_ebr", FT_BOOLEAN, 32,
		TFS(&tfs_ndmp_state_invalid_ebr), 0x00000001, "Whether EstimatedBytesLeft is valid or not", HFILL, }},

	{ &hf_ndmp_state_invalid_etr, {
		"", "ndmp.bu.state.invalid_etr", FT_BOOLEAN, 32,
		TFS(&tfs_ndmp_state_invalid_etr), 0x00000002, "Whether EstimatedTimeLeft is valid or not", HFILL, }},

	{ &hf_ndmp_bu_operation, {
		"Operation", "ndmp.bu.operation", FT_UINT32, BASE_DEC,
		VALS(bu_operation_vals), 0, "BU Operation", HFILL, }},

	{ &hf_ndmp_data_state, {
		"State", "ndmp.data.state", FT_UINT32, BASE_DEC,
		VALS(data_state_vals), 0, "Data state", HFILL, }},

	{ &hf_ndmp_data_halted, {
		"Halted Reason", "ndmp.data.halted", FT_UINT32, BASE_DEC,
		VALS(data_halted_vals), 0, "Data halted reason", HFILL, }},

	{ &hf_ndmp_data_bytes_processed, {
		"Bytes Processed", "ndmp.data.bytes_processed", FT_UINT64, BASE_DEC,
		NULL, 0, "Number of bytes processed", HFILL }},

	{ &hf_ndmp_data_est_bytes_remain, {
		"Est Bytes Remain", "ndmp.data.est_bytes_remain", FT_UINT64, BASE_DEC,
		NULL, 0, "Estimated number of bytes remaining", HFILL }},

	{ &hf_ndmp_data_est_time_remain, {
		"Est Time Remain", "ndmp.data.est_time_remain", FT_RELATIVE_TIME, BASE_DEC,
		NULL, 0, "Estimated time remaining", HFILL }},
	{ &hf_ndmp_lastfrag, {
		"Last Fragment", "ndmp.lastfrag", FT_BOOLEAN, 32,
		&yesno, NDMP_RM_LASTFRAG, "Last Fragment", HFILL }},
	{ &hf_ndmp_fraglen, {
		"Fragment Length", "ndmp.fraglen", FT_UINT32, BASE_DEC,
		NULL, NDMP_RM_LENGTH, "Fragment Length", HFILL }},
  };

  static gint *ett[] = {
    &ett_ndmp,
    &ett_ndmp_fraghdr,
    &ett_ndmp_header,
    &ett_ndmp_butype_attrs,
    &ett_ndmp_fs_invalid,
    &ett_ndmp_tape_attr,
    &ett_ndmp_execute_cdb_flags,
    &ett_ndmp_execute_cdb_cdb,
    &ett_ndmp_execute_cdb_sns,
    &ett_ndmp_execute_cdb_payload,
    &ett_ndmp_tape_invalid,
    &ett_ndmp_tape_flags,
    &ett_ndmp_addr,
    &ett_ndmp_file,
    &ett_ndmp_file_name,
    &ett_ndmp_file_stats,
    &ett_ndmp_file_invalids,
    &ett_ndmp_state_invalids,
  };

  module_t *ndmp_module;

  proto_ndmp = proto_register_protocol("Network Data Management Protocol", "NDMP", "ndmp");
  proto_register_field_array(proto_ndmp, hf_ndmp, array_length(hf_ndmp));

  proto_register_subtree_array(ett, array_length(ett));

  /* desegmentation */
  ndmp_module = prefs_register_protocol(proto_ndmp, NULL);
  prefs_register_enum_preference(ndmp_module,
	"protocol_version",
	"Protocol version",
	"Version of the NDMP protocol",
	&ndmp_protocol_version,
	ndmp_protocol_versions,
	FALSE);
  prefs_register_bool_preference(ndmp_module, "desegment",
    "Reassemble NDMP messages spanning multiple TCP segments",
    "Whether the NDMP dissector should reassemble messages spanning multiple TCP segments."
    " To use this option, you must also enable \"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.",
  	&ndmp_desegment);
  prefs_register_bool_preference(ndmp_module, "defragment",
  	"Reassemble fragmented NDMP messages spanning multiple packets",
  	"Whether the dissector should defragment NDMP messages spanning multiple packets.",
  	&ndmp_defragment);
}

void
proto_reg_handoff_ndmp(void)
{
  dissector_handle_t ndmp_handle;

  ndmp_handle = new_create_dissector_handle(dissect_ndmp, proto_ndmp);
  dissector_add("tcp.port",TCP_PORT_NDMP, ndmp_handle);
}
