/* packet-ceph.c
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

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

#include <stdio.h>
#include <glib.h>
#include <epan/packet.h>
#include <epan/dissectors/packet-tcp.h>

#include "types.h"

#include "crc32c.h"

#include <string.h>

#define PROTO_TAG_CEPH	"CEPH"

/* Wireshark ID of the CEPH protocol */
static int proto_ceph = -1;



/* These are the handles of our subdissectors */
static dissector_handle_t data_handle=NULL;

static dissector_handle_t ceph_handle;
static void dissect_ceph(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree);

static guint32 global_ceph_min_port = 6789;
static guint32 global_ceph_max_port = 6810;

static guint32 global_ceph_min_mon_port = 6789;
static guint32 global_ceph_max_mon_port = 6799;

#define DEST_PORT_CEPH ((pinfo->destport >= global_ceph_min_port) && (pinfo->destport <= global_ceph_max_port))

#define PORT_IS_MON(port) ((port >= global_ceph_min_mon_port) && (port <= global_ceph_max_mon_port))
#define PORT_IS_MDS(port) ((port >= global_ceph_min_mds_port) && (port <= global_ceph_max_mds_port))
#define PORT_IS_OSD(port) ((port >= global_ceph_min_osd_port) && (port <= global_ceph_max_osd_port))

#define IS_ENTITY(cmp, port1, port2) (cmp(port1) || cmp(port2))

#define IS_MON(pinfo) IS_ENTITY(PORT_IS_MON, pinfo->srcport, pinfo->destport)
#define IS_MDS(pinfo) IS_ENTITY(PORT_IS_MDS, pinfo->srcport, pinfo->destport)
#define IS_OSD(pinfo) IS_ENTITY(PORT_IS_OSD, pinfo->srcport, pinfo->destport)

#define MON_STR "mon"
#define MDS_STR "mds"
#define OSD_STR "osd"

#define FMT_INO	"0x%.16llx"

#define PROTO_ADD_TEXT(type, s, field, modifier)\
	proto_tree_add_text(tree, tvb, offset + offsetof(type, field), sizeof(s->field), "" #field ": " modifier, s->field);

#define PROTO_ADD_ITEM(tree, type, hf, s, field) \
	proto_tree_add_item(tree, hf, tvb, offset + offsetof(type, field), sizeof(s->field), TRUE);

#define CTIME_BUF_LEN	128

#define PROTO_ADD_TIME(tvb, tree, type, offset, head, field, name) \
	do { \
		time_t time;			\
		time = head->field.tv_sec;	\
		proto_tree_add_text(tree, tvb, offset + offsetof(type, field), \
			sizeof(head->field), "" #name ": %s (%d ns)", ctime(&time), head->field.tv_nsec); \
	} while (0)

static const value_string packettypenames[] = {
	{ 1, "Shutdown" },
	{ 2, "Ping" },
	{ 4, "Mon Map" },
	{ 5, "Mon Get Map" },
	{ 10, "Client Mount" },
	{ 11, "Client Mount Ack" },
	{ 12, "Client Unmount" },
	{ 13, "Statfs" },
	{ 20, "MDS Get Map" },
	{ 21, "MDS Map" },
	{ 22, "Client Session" },
	{ 23, "Client Reconnect" },
	{ 24, "Client Request" },
	{ 25, "Client Request Forward" },
	{ 26, "Client Reply" },
	{ 0x310, "Client Caps" },
	{ 0x311, "Client Lease" },
	{ 0x312, "Client Snap" },
	{ 40, "OSD Get Map" },
	{ 41, "OSD Map" },
	{ 42, "OSD Op" },
	{ 43, "OSD Op Reply" },
	{ 0, NULL }
};	

#define ACK_MSG_SIZE			9
#define TVB_MSG_HEADER_POS(x) (1 + offsetof(struct ceph_msg_header, x))
#define TVB_IS_ACK(ofs) (tvb_get_guint8(tvb, ofs) == CEPH_MSGR_TAG_ACK)
#define TVB_MSG_FIELD(func, tvb, ofs, field) func(tvb, ofs + (TVB_IS_ACK(ofs) ? ACK_MSG_SIZE : 0) + TVB_MSG_HEADER_POS(field))

/* The following hf_* variables are used to hold the Wireshark IDs of
* our header fields; they are filled out when we call
* proto_register_field_array() in proto_register_ceph()
*/
/** Kts attempt at defining the protocol */
static gint hf_ceph = -1;
static gint hf_ceph_mds_op = -1;
static gint hf_ceph_header = -1;
static gint hf_ceph_banner = -1;
static gint hf_ceph_entity_addr = -1;
static gint hf_ceph_entity_type = -1;
static gint hf_ceph_entity_num = -1;
static gint hf_ceph_fsid = -1;
static gint hf_ceph_banner_magic = -1;
static gint hf_ceph_banner_version = -1;
static gint hf_ceph_connect_erank = -1;
static gint hf_ceph_connect_nonce = -1;
static gint hf_ceph_sockaddr_in = -1;
static gint hf_ceph_connect_host_type = -1;
static gint hf_ceph_connect_tag = -1;
static gint hf_ceph_connect_global_seq = -1;
static gint hf_ceph_connect_connect_seq = -1;
static gint hf_ceph_connect_flags = -1;
static gint hf_ceph_length = -1;
static gint hf_ceph_type = -1;
static gint hf_ceph_text = -1;
static gint hf_ceph_path = -1;
static gint hf_sin_family = -1;
static gint hf_sin_port = -1;
static gint hf_sin_addr = -1;
static gint hf_ceph_hdr_tag = -1;
static gint hf_ceph_hdr_seq_ack = -1;
static gint hf_ceph_hdr_seq = -1;
static gint hf_ceph_hdr_type = -1;
static gint hf_ceph_hdr_priority = -1;
static gint hf_ceph_hdr_version = -1;
static gint hf_ceph_hdr_mon_protocol = -1;
static gint hf_ceph_hdr_osd_protocol = -1;
static gint hf_ceph_hdr_mds_protocol = -1;
static gint hf_ceph_hdr_client_protocol = -1;
static gint hf_ceph_hdr_front_len = -1;
static gint hf_ceph_hdr_middle_len = -1;
static gint hf_ceph_hdr_data_off = -1;
static gint hf_ceph_hdr_data_len = -1;
static gint hf_ceph_data = -1;
static gint hf_ceph_front = -1;
static gint hf_ceph_hdr_src = -1;
static gint hf_ceph_hdr_orig_src = -1;
static gint hf_ceph_hdr_dst = -1;
static gint hf_ceph_hdr_crc = -1;
static gint hf_ceph_footer = -1;
static gint hf_ceph_footer_flags = -1;
static gint hf_ceph_footer_front_crc = -1;
static gint hf_ceph_footer_middle_crc = -1;
static gint hf_ceph_footer_data_crc = -1;


/* These are the ids of the subtrees that we may be creating */
static gint ett_ceph = -1;
static gint ett_ceph_header = -1;
static gint ett_ceph_banner = -1;
static gint ett_ceph_entity_addr = -1;
static gint ett_ceph_length = -1;
static gint ett_ceph_type = -1;
static gint ett_ceph_text = -1;
static gint ett_ceph_front = -1;
static gint ett_ceph_data = -1;
static gint ett_ceph_footer = -1;


void proto_reg_handoff_ceph(void)
{
	static gboolean initialized=FALSE;
	static guint32 port;

	if (!initialized) {
		data_handle = find_dissector("data");
		ceph_handle = create_dissector_handle(dissect_ceph, proto_ceph);
		for (port = global_ceph_min_port; port <= global_ceph_max_port; port++)
			dissector_add("tcp.port", port, ceph_handle);
	}

}

void proto_register_ceph (void)
{
	/* A header field is something you can search/filter on.
	* 
	* We create a structure to register our fields. It consists of an
	* array of hf_register_info structures, each of which are of the format
	* {&(field id), {name, abbrev, type, display, strings, bitmask, blurb, HFILL}}.
	*/
	static hf_register_info hf[] = {
		{ &hf_ceph,
			{ "Data", "ceph.data", FT_NONE, BASE_NONE, NULL, 0x0,
			"CEPH PDU", HFILL }},
		{ &hf_ceph_header,
			{ "Header", "ceph.header", FT_NONE, BASE_NONE, NULL, 0x0,
		 	"CEPH Header", HFILL }},
		{ &hf_ceph_banner,
			{ "Ceph Banner", "ceph.connect.banner", FT_STRING, BASE_NONE, NULL, 0x0,
			"Ceph Banner", HFILL }},
		{ &hf_ceph_entity_type,
			{ "Ceph Entity Type", "ceph.entity.type", FT_UINT32, BASE_DEC, NULL, 0x0,
			"Ceph Entity Type", HFILL }},
		{ &hf_ceph_entity_num,
			{ "Ceph Entity Num", "ceph.entity.num", FT_UINT32, BASE_DEC, NULL, 0x0,
			"Ceph Entity Num", HFILL }},
		{ &hf_ceph_entity_addr,
			{ "Ceph Entity Addr", "ceph.entity.addr", FT_NONE, BASE_NONE, NULL, 0x0,
			"Ceph Entity Addr", HFILL }},
		{ &hf_ceph_fsid,
			{ "Ceph FSID", "ceph.fsid", FT_NONE, BASE_NONE, NULL, 0x0,
			"Ceph FSID", HFILL }},
		{ &hf_ceph_banner_magic,
			{ "Ceph Banner Magic", "ceph.connect.banner.magic", FT_STRING, BASE_NONE, NULL, 0x0,
			"Ceph Banner Magic", HFILL }},
		{ &hf_ceph_banner_version,
			{ "Ceph Banner Version", "ceph.connect.banner.ver", FT_STRING, BASE_NONE, NULL, 0x0,
			"Ceph Banner", HFILL }},
		{ &hf_ceph_connect_erank,
			{ "erank", "ceph.connect.erank", FT_UINT32, BASE_HEX, NULL, 0x0,
			"connect: erank", HFILL }},
		{ &hf_ceph_connect_nonce,
			{ "nonce", "ceph.connect.nonce", FT_UINT32, BASE_HEX, NULL, 0x0,
			"connect: nonce", HFILL }},
		{ &hf_ceph_sockaddr_in,
			{ "sockaddr_in", "ceph.sockaddr_in", FT_NONE, BASE_NONE, NULL, 0x0,
		 	"sockaddr_in", HFILL }},
		{ &hf_sin_family,
			{ "sin_family", "ceph.sin_family", FT_UINT16, BASE_HEX, NULL, 0x0,
		 	"sockaddr_in: sin_family", HFILL }},
		{ &hf_sin_port,
			{ "sin_port", "ceph.sin_port", FT_UINT16, BASE_DEC, NULL, 0x0,
		 	"sockaddr_in: sin_port", HFILL }},
		{ &hf_sin_addr,
			{ "ip addr", "ceph.addr", FT_IPv4, BASE_NONE, NULL, 0x0,
		 	"sockaddr_in: ip addr", HFILL }},
		{ &hf_ceph_connect_host_type,
			{ "host_type", "ceph.connect.host_type", FT_UINT32, BASE_DEC, NULL, 0x0,
		 	"connect: host_type", HFILL }},
		{ &hf_ceph_connect_tag,
			{ "tag", "ceph.connect.tag", FT_UINT8, BASE_DEC, NULL, 0x0,
		 	"connect: tag", HFILL }},
		{ &hf_ceph_mds_op,
			{ "mds op", "ceph.mds.op", FT_UINT32, BASE_HEX, NULL, 0x0,
		 	"ceph: mds op", HFILL }},
		{ &hf_ceph_connect_global_seq,
			{ "global_seq", "ceph.connect.global_seq", FT_UINT32, BASE_DEC, NULL, 0x0,
		 	"connect: global_seq", HFILL }},
		{ &hf_ceph_connect_connect_seq,
			{ "connect_seq", "ceph.connect.connect_seq", FT_UINT32, BASE_DEC, NULL, 0x0,
		 	"connect: connect_seq", HFILL }},
		{ &hf_ceph_connect_flags,
			{ "flags", "ceph.connect.flags", FT_UINT8, BASE_HEX, NULL, 0x0,
		 	"connect: flags", HFILL }},
		{ &hf_ceph_length,
			{ "Package Length", "ceph.len", FT_UINT32, BASE_DEC, NULL, 0x0,
			"Package Length", HFILL }},
		{ &hf_ceph_type,
			{ "Type", "ceph.type", FT_UINT8, BASE_DEC, VALS(packettypenames), 0x0,
		 	"Package Type", HFILL }},
		{ &hf_ceph_text,
			{ "Text", "ceph.text", FT_STRING, BASE_NONE, NULL, 0x0,
		 	"Text", HFILL }},
		{ &hf_ceph_path,
			{ "path", "ceph.path", FT_STRING, BASE_NONE, NULL, 0x0,
		 	"path", HFILL }},
		{ &hf_ceph_hdr_tag,
			{ "tag", "ceph.tag", FT_UINT8, BASE_DEC, NULL, 0x0,
		 	"hdr: tag", HFILL }},
		{ &hf_ceph_hdr_seq_ack,
			{ "ack seq", "ceph.ack.seq", FT_UINT64, BASE_DEC, NULL, 0x0,
		 	"ack: seq", HFILL }},
		{ &hf_ceph_hdr_seq,
			{ "seq", "ceph.seq", FT_UINT64, BASE_DEC, NULL, 0x0,
		 	"hdr: seq", HFILL }},
		{ &hf_ceph_hdr_type,
			{ "type", "ceph.type", FT_UINT16, BASE_HEX, NULL, 0x0,
		 	"hdr: type", HFILL }},
		{ &hf_ceph_hdr_priority,
			{ "priority", "ceph.priority", FT_UINT16, BASE_DEC, NULL, 0x0,
		 	"hdr: priority", HFILL }},
		{ &hf_ceph_hdr_version,
			{ "version", "ceph.version", FT_UINT16, BASE_DEC, NULL, 0x0,
		 	"hdr: version", HFILL }},
		{ &hf_ceph_hdr_mon_protocol,
			{ "mon_protocol", "ceph.mon_protocol", FT_UINT16, BASE_DEC, NULL, 0x0,
		 	"hdr: mon_protocol", HFILL }},
		{ &hf_ceph_hdr_osd_protocol,
			{ "osd_protocol", "ceph.osd_protocol", FT_UINT16, BASE_DEC, NULL, 0x0,
		 	"hdr: osd_protocol", HFILL }},
		{ &hf_ceph_hdr_mds_protocol,
			{ "mds_protocol", "ceph.mds_protocol", FT_UINT16, BASE_DEC, NULL, 0x0,
		 	"hdr: mds_protocol", HFILL }},
		{ &hf_ceph_hdr_client_protocol,
			{ "client_protocol", "ceph.client_protocol", FT_UINT16, BASE_DEC, NULL, 0x0,
		 	"hdr: client_protocol", HFILL }},
		{ &hf_ceph_hdr_front_len,
			{ "front_len", "ceph.front_len", FT_UINT32, BASE_DEC, NULL, 0x0,
		 	"hdr: front_len", HFILL }},
		{ &hf_ceph_hdr_middle_len,
			{ "middle_len", "ceph.middle_len", FT_UINT32, BASE_DEC, NULL, 0x0,
		 	"hdr: middle_len", HFILL }},
		{ &hf_ceph_hdr_data_off,
			{ "data_off", "ceph.data_off", FT_UINT32, BASE_DEC, NULL, 0x0,
		 	"hdr: data_off", HFILL }},
		{ &hf_ceph_hdr_data_len,
			{ "data_len", "ceph.data_len", FT_UINT32, BASE_DEC, NULL, 0x0,
		 	"hdr: data_len", HFILL }},
		{ &hf_ceph_hdr_src,
			{ "src", "ceph.src", FT_NONE, BASE_NONE, NULL, 0x0,
		 	"hdr: src", HFILL }},
		{ &hf_ceph_hdr_orig_src,
			{ "orig_src", "ceph.orig_src", FT_NONE, BASE_NONE, NULL, 0x0,
		 	"hdr: orig_src", HFILL }},
		{ &hf_ceph_hdr_dst,
			{ "dst", "ceph.dst", FT_NONE, BASE_NONE, NULL, 0x0,
		 	"hdr: dst", HFILL }},
		{ &hf_ceph_hdr_crc,
			{ "crc", "ceph.crc", FT_UINT32, BASE_HEX, NULL, 0x0,
		 	"hdr: crc", HFILL }},
		{ &hf_ceph_front,
			{ "Front", "ceph.front", FT_NONE, BASE_NONE, NULL, 0x0,
		 	"Ceph Front", HFILL }},
		{ &hf_ceph_data,
			{ "Data", "ceph.data", FT_NONE, BASE_HEX, NULL, 0x0,
		 	"Ceph Data", HFILL }},
		{ &hf_ceph_footer,
			{ "Footer", "ceph.footer", FT_NONE, BASE_HEX, NULL, 0x0,
		 	"Ceph Footer", HFILL }},
		{ &hf_ceph_footer_flags,
			{ "flags", "ceph.footer.flags", FT_UINT32, BASE_HEX, NULL, 0x0,
		 	"footer: flags", HFILL }},
		{ &hf_ceph_footer_front_crc,
			{ "front_crc", "ceph.footer.front_crc", FT_UINT32, BASE_HEX, NULL, 0x0,
		 	"footer: front_crc", HFILL }},
		{ &hf_ceph_footer_middle_crc,
			{ "middle_crc", "ceph.footer.middle_crc", FT_UINT32, BASE_HEX, NULL, 0x0,
		 	"footer: middle_crc", HFILL }},
		{ &hf_ceph_footer_data_crc,
			{ "data_crc", "ceph.footer.data_crc", FT_UINT32, BASE_HEX, NULL, 0x0,
		 	"footer: data_crc", HFILL }},
	};
	static gint *ett[] = {
		&ett_ceph,
		&ett_ceph_header,
		&ett_ceph_banner,
		&ett_ceph_length,
		&ett_ceph_entity_addr,
		&ett_ceph_type,
		&ett_ceph_text,
		&ett_ceph_data,
		&ett_ceph_front,
		&ett_ceph_footer
	};
	//if (proto_ceph == -1) { /* execute protocol initialization only once */
	proto_ceph = proto_register_protocol ("CEPH Protocol", "CEPH", "ceph");

	proto_register_field_array (proto_ceph, hf, array_length (hf));
	proto_register_subtree_array (ett, array_length (ett));
	register_dissector("ceph", dissect_ceph, proto_ceph);
	//}
}
	
static guint32 dissect_sockaddr_in(tvbuff_t *tvb, proto_tree *tree, guint32 offset)
{
	proto_tree *ceph_sockaddr_tree = NULL;
	proto_item *ceph_sub_item = NULL;
	proto_item *ceph_item = proto_tree_get_parent(tree);

	ceph_sockaddr_tree = proto_item_add_subtree(ceph_item, ett_ceph);

	ceph_sub_item = proto_tree_add_item( tree, hf_ceph_sockaddr_in, tvb, offset, 16, TRUE );
	ceph_sockaddr_tree = proto_item_add_subtree(ceph_sub_item, ett_ceph);

	proto_tree_add_item(ceph_sockaddr_tree, hf_sin_family, tvb, offset, 2, TRUE);
	proto_tree_add_item(ceph_sockaddr_tree, hf_sin_port, tvb, offset+2, 2, TRUE);
	proto_tree_add_item(ceph_sockaddr_tree, hf_sin_addr, tvb, offset+4, 4, FALSE);
	offset += 16;
	return offset;
}

static guint32 dissect_ceph_banner(tvbuff_t *tvb, proto_tree *tree, guint32 offset)
{
	proto_tree *ceph_banner_tree = NULL;
	proto_item *ceph_sub_item = NULL;

	ceph_sub_item = proto_tree_add_item( tree, hf_ceph_banner, tvb, offset, 9, TRUE );
	ceph_banner_tree = proto_item_add_subtree(ceph_sub_item, ett_ceph);

	proto_tree_add_item(ceph_banner_tree, hf_ceph_banner_magic, tvb, offset, 4, TRUE);
	proto_tree_add_item(ceph_banner_tree, hf_ceph_banner_version, tvb, offset+5, 4, TRUE);

	return offset+9;
}

static guint32 dissect_ceph_entity_addr(tvbuff_t *tvb, proto_tree *tree, guint32 offset)
{
	proto_tree *ceph_entity_tree = NULL;
	proto_item *ceph_sub_item = NULL;

	ceph_sub_item = proto_tree_add_item( tree, hf_ceph_entity_addr, tvb, offset, sizeof(struct ceph_entity_addr), TRUE );
	ceph_entity_tree = proto_item_add_subtree(ceph_sub_item, ett_ceph);
	proto_tree_add_item(ceph_entity_tree, hf_ceph_connect_erank, tvb, offset, 4, TRUE);
	proto_tree_add_item(ceph_entity_tree, hf_ceph_connect_nonce, tvb, offset+4, 4, TRUE);
	dissect_sockaddr_in(tvb, ceph_entity_tree, offset+8);
#if 0
	proto_tree_add_item(ceph_entity_tree, hf_ceph_connect_host_type, tvb, offset, 4, TRUE);
	offset += 4;
#endif

	return offset + sizeof(struct ceph_entity_addr);
}

static guint32 dissect_ceph_fsid(tvbuff_t *tvb, proto_tree *tree, guint32 offset)
{
	proto_tree *ceph_entity_tree = NULL;
	proto_item *ceph_sub_item = NULL;
	struct ceph_fsid *fsid;

	fsid = (struct ceph_fsid *)tvb_get_ptr(tvb, offset, sizeof(struct ceph_fsid));

	ceph_sub_item = proto_tree_add_item( tree, hf_ceph_fsid, tvb, offset, sizeof(struct ceph_entity_addr), TRUE );
	ceph_entity_tree = proto_item_add_subtree(ceph_sub_item, ett_ceph);

	proto_tree_add_item(ceph_entity_tree, hf_ceph_connect_erank, tvb, offset, 4, TRUE);
	proto_tree_add_item(ceph_entity_tree, hf_ceph_connect_nonce, tvb, offset+4, 4, TRUE);

	proto_tree_add_text(tree, tvb, 0, 
				sizeof(struct ceph_fsid), "fsid: " FMT_INO "." FMT_INO, ((unsigned long long *)fsid->fsid)[0], ((unsigned long long *)fsid->fsid)[1]);
	
	offset += sizeof(struct ceph_fsid);

	return offset;
}

static guint32 dissect_ceph_entity_inst(tvbuff_t *tvb, proto_tree *tree, guint32 offset)
{
	proto_tree *ceph_entity_tree = NULL;
	proto_item *ceph_sub_item = NULL;

	ceph_sub_item = proto_tree_add_item( tree, hf_ceph_entity_addr, tvb, offset, sizeof(struct ceph_entity_addr), TRUE );
	ceph_entity_tree = proto_item_add_subtree(ceph_sub_item, ett_ceph);
	proto_tree_add_item(ceph_entity_tree, hf_ceph_entity_type, tvb, offset, 4, TRUE);
	proto_tree_add_item(ceph_entity_tree, hf_ceph_entity_num, tvb, offset+4, 4, TRUE);
	offset += 8;
	offset = dissect_ceph_entity_addr(tvb, ceph_entity_tree, offset);
	return offset;
}

static guint32 dissect_ceph_footer(tvbuff_t *tvb, proto_tree *tree, guint32 offset, guint32 data_crc)
{
	proto_tree *ceph_footer_tree = NULL;
	proto_item *ceph_sub_item = NULL;
	proto_item *data_crc_item = NULL;

	ceph_sub_item = proto_tree_add_item( tree, hf_ceph_footer, tvb, offset, sizeof(struct ceph_msg_footer), TRUE );
	ceph_footer_tree = proto_item_add_subtree(ceph_sub_item, ett_ceph);
	proto_tree_add_item(ceph_footer_tree, hf_ceph_footer_flags, tvb, offset, 4, TRUE);
	proto_tree_add_item(ceph_footer_tree, hf_ceph_footer_front_crc, tvb, offset+4, 4, TRUE);
	proto_tree_add_item(ceph_footer_tree, hf_ceph_footer_middle_crc, tvb, offset+8, 4, TRUE);
	data_crc_item = proto_tree_add_item(ceph_footer_tree, hf_ceph_footer_data_crc, tvb, offset+12, 4, TRUE);
	proto_item_append_text(data_crc_item, " (calculated %x)", data_crc);

	offset += 12;
	return offset;
}

static guint32 dissect_ceph_client_connect(tvbuff_t *tvb, proto_tree *tree, guint32 offset)
{
	proto_tree *ceph_header_tree = NULL;
	proto_item *ceph_sub_item = NULL;
	proto_item *ceph_item = proto_tree_get_parent(tree);
	struct ceph_msg_connect *msg;

	offset = dissect_ceph_banner(tvb, tree, offset);

	ceph_header_tree = proto_item_add_subtree(ceph_item, ett_ceph);

	ceph_sub_item = proto_tree_add_item( tree, hf_ceph_header, tvb, offset, -1, TRUE );
	ceph_header_tree = proto_item_add_subtree(ceph_sub_item, ett_ceph);

	offset = dissect_ceph_entity_addr(tvb, ceph_header_tree, offset);
#if 0
	proto_tree_add_item(ceph_header_tree, hf_ceph_connect_host_type, tvb, offset, 4, TRUE);
	offset += 4;
	proto_tree_add_item(ceph_header_tree, hf_ceph_connect_global_seq, tvb, offset, 4, TRUE);
	proto_tree_add_item(ceph_header_tree, hf_ceph_connect_connect_seq, tvb, offset+4, 4, TRUE);
	proto_tree_add_item(ceph_header_tree, hf_ceph_connect_flags, tvb, offset+8, 1, TRUE);
	offset += 8;
#endif

	msg = (struct ceph_msg_connect *)tvb_get_ptr(tvb, offset, sizeof(struct ceph_msg_connect));
	PROTO_ADD_TEXT(struct ceph_msg_connect, msg, host_type, "%d");
	PROTO_ADD_TEXT(struct ceph_msg_connect, msg, global_seq, "%d");
	PROTO_ADD_TEXT(struct ceph_msg_connect, msg, connect_seq, "%d");
	PROTO_ADD_TEXT(struct ceph_msg_connect, msg, protocol_version, "%d");
	PROTO_ADD_TEXT(struct ceph_msg_connect, msg, flags, "%x");

	return offset;
}

static guint32 dissect_ceph_server_connect(tvbuff_t *tvb, proto_tree *tree, guint32 offset)
{
	proto_tree *ceph_header_tree = NULL;
	proto_item *ceph_sub_item = NULL;
	proto_item *ceph_item = proto_tree_get_parent(tree);
	struct ceph_msg_connect_reply *msg;

	offset = dissect_ceph_banner(tvb, tree, offset);

	ceph_header_tree = proto_item_add_subtree(ceph_item, ett_ceph);

	ceph_sub_item = proto_tree_add_item( tree, hf_ceph_header, tvb, offset, -1, TRUE );
	ceph_header_tree = proto_item_add_subtree(ceph_sub_item, ett_ceph);

	offset = dissect_ceph_entity_addr(tvb, ceph_header_tree, offset);
	offset = dissect_ceph_entity_addr(tvb, ceph_header_tree, offset);
	msg = (struct ceph_msg_connect_reply *)tvb_get_ptr(tvb, offset, sizeof(struct ceph_msg_connect_reply));
	PROTO_ADD_TEXT(struct ceph_msg_connect_reply, msg, tag, "%d");
	PROTO_ADD_TEXT(struct ceph_msg_connect_reply, msg, global_seq, "%d");
	PROTO_ADD_TEXT(struct ceph_msg_connect_reply, msg, connect_seq, "%d");
	PROTO_ADD_TEXT(struct ceph_msg_connect_reply, msg, protocol_version, "%d");
	PROTO_ADD_TEXT(struct ceph_msg_connect_reply, msg, flags, "%x");

	return offset;
}

static guint32 dissect_ceph_file_layout(tvbuff_t *tvb, proto_tree *tree, guint32 offset)
{
	guint32 orig_ofs = offset;
	struct ceph_file_layout *lo;

	lo = (struct ceph_file_layout *)tvb_get_ptr(tvb, offset, sizeof(struct ceph_file_layout));
	
	PROTO_ADD_TEXT(struct ceph_file_layout, lo, fl_stripe_unit, "%d");
	PROTO_ADD_TEXT(struct ceph_file_layout, lo, fl_stripe_count, "%d");
	PROTO_ADD_TEXT(struct ceph_file_layout, lo, fl_object_size, "%d");
	PROTO_ADD_TEXT(struct ceph_file_layout, lo, fl_cas_hash, "%d");
	PROTO_ADD_TEXT(struct ceph_file_layout, lo, fl_object_stripe_unit, "%d");
	PROTO_ADD_TEXT(struct ceph_file_layout, lo, fl_pg_preferred, "%d");
	PROTO_ADD_TEXT(struct ceph_file_layout, lo, fl_pg_pool, "%u");

	return orig_ofs + sizeof(struct ceph_mds_reply_head);
}
#if 0
static int dissect_ceph_filepath(tvbuff_t *tvb, proto_tree *tree, guint32 offset, char **path, guint64 *ino)
{
	guint32 len;
	const char *p = NULL;

	*ino = tvb_get_letoh64(tvb, offset);
	proto_tree_add_text(tree, tvb, offset, sizeof(*ino), "inode: " FMT_INO, *ino);
	offset += sizeof(*ino);
	len = tvb_get_letohl(tvb, offset);
	proto_tree_add_text(tree, tvb, offset, sizeof(len), "len: %d", len);
	offset += sizeof(len);

	if (len) {
		p = tvb_get_ptr(tvb, offset, len);
		*path = malloc(len+1);
		if (*path) {
			memcpy(*path, p, len);
			(*path)[len] = '\0';
			proto_tree_add_item(tree, hf_ceph_path, tvb, offset, len, TRUE);
		}
	}

	offset += len;

	return offset;
}
#endif
static guint32 dissect_ceph_mon_statfs(tvbuff_t *tvb, proto_tree *tree, guint32 offset)
{
	struct ceph_mon_statfs *req;

	req = (struct ceph_mon_statfs *)tvb_get_ptr(tvb, offset, sizeof(struct ceph_mon_statfs));
	
	dissect_ceph_fsid(tvb, tree, offset + offsetof(struct ceph_mon_statfs, fsid));
	PROTO_ADD_TEXT(struct ceph_mon_statfs, req, tid, "%lld");

	return offset + sizeof(struct ceph_mon_statfs);
}

static guint32 dissect_ceph_mon_statfs_reply(tvbuff_t *tvb, proto_tree *tree, guint32 offset)
{
	struct ceph_mon_statfs_reply *req;

	req = (struct ceph_mon_statfs_reply *)tvb_get_ptr(tvb, offset, sizeof(struct ceph_mon_statfs_reply));
	
	dissect_ceph_fsid(tvb, tree, offset + offsetof(struct ceph_mon_statfs_reply, fsid));
	PROTO_ADD_TEXT(struct ceph_mon_statfs_reply, req, tid, "%lld");
	PROTO_ADD_TEXT(struct ceph_mon_statfs_reply, req, st.kb, "%lld");
	PROTO_ADD_TEXT(struct ceph_mon_statfs_reply, req, st.kb_used, "%lld");
	PROTO_ADD_TEXT(struct ceph_mon_statfs_reply, req, st.kb_avail, "%lld");
	PROTO_ADD_TEXT(struct ceph_mon_statfs_reply, req, st.num_objects, "%lld");

	return offset + sizeof(struct ceph_mon_statfs_reply);
}
#if 0
static guint32 dissect_ceph_client_osd_getmap(tvbuff_t *tvb, proto_tree *tree, guint32 offset)
{
	struct ceph_osd_getmap *req;

	req = (struct ceph_osd_getmap *)tvb_get_ptr(tvb, offset, sizeof(struct ceph_osd_getmap));
	
	dissect_ceph_fsid(tvb, tree, offset + offsetof(struct ceph_osd_getmap, fsid));
	PROTO_ADD_TEXT(struct ceph_osd_getmap, req, start, "%d");

	return offset + sizeof(struct ceph_osd_getmap);
}

static guint32 dissect_ceph_client_mds_getmap(tvbuff_t *tvb, proto_tree *tree, guint32 offset)
{
	struct ceph_mds_getmap *req;

	req = (struct ceph_mds_getmap *)tvb_get_ptr(tvb, offset, sizeof(struct ceph_mds_getmap));
	
	PROTO_ADD_TEXT(struct ceph_mds_getmap, req, monhdr.have_version, "%lld");
	dissect_ceph_fsid(tvb, tree, offset + offsetof(struct ceph_mds_getmap, fsid));

	return offset + sizeof(struct ceph_mds_getmap);
}
#endif
static guint32 dissect_ceph_client_mds_request(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset)
{
	struct ceph_mds_request_head *head;
	proto_item *item;

	head = (struct ceph_mds_request_head *)tvb_get_ptr(tvb, offset, sizeof(struct ceph_mds_request_head));
	
	PROTO_ADD_TEXT(struct ceph_mds_request_head, head, tid, "%lld");
	PROTO_ADD_TEXT(struct ceph_mds_request_head, head, oldest_client_tid, "%lld");
	PROTO_ADD_TEXT(struct ceph_mds_request_head, head, mdsmap_epoch, "%d");
	PROTO_ADD_TEXT(struct ceph_mds_request_head, head, num_retry, "%d");
	PROTO_ADD_TEXT(struct ceph_mds_request_head, head, num_fwd, "%d");
	PROTO_ADD_TEXT(struct ceph_mds_request_head, head, num_releases, "%d");

	item = proto_tree_add_item(tree, hf_ceph_mds_op, tvb, offset+offsetof(struct ceph_mds_request_head, op), sizeof(head->op), TRUE);
	proto_item_append_text(item, " (%s)", ceph_mds_op_name(head->op));

	PROTO_ADD_TEXT(struct ceph_mds_request_head, head, caller_uid, "%d");
	PROTO_ADD_TEXT(struct ceph_mds_request_head, head, caller_gid, "%d");

	if (check_col(pinfo->cinfo, COL_INFO)) {
		col_append_fstr(pinfo->cinfo, COL_INFO, " (%s)", ceph_mds_op_name(head->op));
	}
	
	/* FIXME */
	switch (head->op) {
	case CEPH_MDS_OP_LOOKUP:
//		PROTO_ADD_TEXT(struct ceph_mds_request_head, head, args.lookup.mask, "0x%.4x");
		break;
	case CEPH_MDS_OP_SETXATTR:
		break;
	case CEPH_MDS_OP_SETLAYOUT:
		dissect_ceph_file_layout(tvb, tree, offset + offsetof(struct ceph_mds_request_head, args.setlayout.layout));
		break;
	case CEPH_MDS_OP_SETATTR:
		break;
	case CEPH_MDS_OP_MKNOD:
		PROTO_ADD_TEXT(struct ceph_mds_request_head, head, args.mknod.mode, "0%.5o");
		PROTO_ADD_TEXT(struct ceph_mds_request_head, head, args.mknod.rdev, "%d");
		break;
	case CEPH_MDS_OP_OPEN:
		PROTO_ADD_TEXT(struct ceph_mds_request_head, head, args.open.flags, "%x");
		PROTO_ADD_TEXT(struct ceph_mds_request_head, head, args.open.mode, "0%.5o");
		break;
	case CEPH_MDS_OP_MKDIR:
		PROTO_ADD_TEXT(struct ceph_mds_request_head, head, args.mkdir.mode, "0%.5o");
		break;
	case CEPH_MDS_OP_RMXATTR:
	case CEPH_MDS_OP_LINK:
	case CEPH_MDS_OP_UNLINK:
	case CEPH_MDS_OP_RENAME:
	case CEPH_MDS_OP_RMDIR:
	case CEPH_MDS_OP_SYMLINK:
	case CEPH_MDS_OP_LSSNAP:
	case CEPH_MDS_OP_MKSNAP:
	case CEPH_MDS_OP_RMSNAP:
		break;
	}

	offset += sizeof(struct ceph_mds_request_head);
#if 0
	if (head->op == CEPH_MDS_OP_FINDINODE) {
	
	} else {
		guint64 ino1, ino2;
		char *s1 = NULL, *s2 = NULL;

		offset = dissect_ceph_filepath(tvb, tree, offset, &s1, &ino1);
		offset = dissect_ceph_filepath(tvb, tree, offset, &s2, &ino2);

		if (check_col(pinfo->cinfo, COL_INFO)) {
			if (s1)
				col_append_fstr(pinfo->cinfo, COL_INFO, " %s", s1);
			if (s2)
				col_append_fstr(pinfo->cinfo, COL_INFO, " -> %s", s2);
		}


		
	}
#endif
	return offset;
}

static guint32 dissect_ceph_client_mds_reply(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset)
{
	guint32 orig_ofs = offset;
	struct ceph_mds_reply_head *head;

	head = (struct ceph_mds_reply_head *)tvb_get_ptr(tvb, offset, sizeof(struct ceph_mds_reply_head));
	
	PROTO_ADD_TEXT(struct ceph_mds_reply_head, head, tid, "%lld");

	proto_tree_add_text(tree, tvb, offsetof(struct ceph_mds_reply_head, op), 
				sizeof(head->op), "op: %d (%s)", head->op, ceph_mds_op_name(head->op));

	PROTO_ADD_TEXT(struct ceph_mds_reply_head, head, result, "%d");
	PROTO_ADD_TEXT(struct ceph_mds_reply_head, head, mdsmap_epoch, "%d");


	if (check_col(pinfo->cinfo, COL_INFO)) {
		col_append_fstr(pinfo->cinfo, COL_INFO, " (%s)", ceph_mds_op_name(head->op));
	}

	return orig_ofs + sizeof(struct ceph_mds_reply_head);
}

static guint32 dissect_ceph_client_mds_lease_request(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset)
{
	guint32 orig_ofs = offset;
	struct ceph_mds_lease *head;
	static char *lease_action[] = { "", "revoke", "release", "renew" };

	head = (struct ceph_mds_lease *)tvb_get_ptr(tvb, offset, sizeof(struct ceph_mds_lease));
	
	PROTO_ADD_TEXT(struct ceph_mds_lease, head, action, "%d");
	PROTO_ADD_TEXT(struct ceph_mds_lease, head, mask, "%.4x");
	PROTO_ADD_TEXT(struct ceph_mds_lease, head, ino, FMT_INO);
	PROTO_ADD_TEXT(struct ceph_mds_lease, head, first, "%lld");
	PROTO_ADD_TEXT(struct ceph_mds_lease, head, last, "%lld");

	if (check_col(pinfo->cinfo, COL_INFO)) {
		if (head->action < 4) {
			col_append_fstr(pinfo->cinfo, COL_INFO, " (%s)", lease_action[head->action]);
		}
	}

	return orig_ofs + sizeof(struct ceph_mds_lease);
}

static guint32 dissect_ceph_client_mds_caps_request(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset)
{
	guint32 orig_ofs = offset;
	struct ceph_mds_caps *head;

	head = (struct ceph_mds_caps *)tvb_get_ptr(tvb, offset, sizeof(struct ceph_mds_caps));
	
	PROTO_ADD_TEXT(struct ceph_mds_caps, head, op, "%d");
	PROTO_ADD_TEXT(struct ceph_mds_caps, head, ino, FMT_INO);
	PROTO_ADD_TEXT(struct ceph_mds_caps, head, seq, "%d");
	PROTO_ADD_TEXT(struct ceph_mds_caps, head, caps, "%d");
	PROTO_ADD_TEXT(struct ceph_mds_caps, head, wanted, "%d");
	PROTO_ADD_TEXT(struct ceph_mds_caps, head, size, "%llu");
	PROTO_ADD_TEXT(struct ceph_mds_caps, head, max_size, "%llu");
	PROTO_ADD_TEXT(struct ceph_mds_caps, head, truncate_seq, "%d");
	PROTO_ADD_TEXT(struct ceph_mds_caps, head, migrate_seq, "%d");
	PROTO_ADD_TEXT(struct ceph_mds_caps, head, time_warp_seq, "%u");
	PROTO_ADD_TEXT(struct ceph_mds_caps, head, snap_follows, "%llu");
	PROTO_ADD_TEXT(struct ceph_mds_caps, head, snap_trace_len, "%d");

#define CAPS_REQ_ADD_TIME(field) PROTO_ADD_TIME(tvb, tree, struct ceph_mds_caps, offset, head, field, field) 
	CAPS_REQ_ADD_TIME(mtime);
	CAPS_REQ_ADD_TIME(atime);
	CAPS_REQ_ADD_TIME(ctime);

	if (check_col(pinfo->cinfo, COL_INFO)) {
		col_append_fstr(pinfo->cinfo, COL_INFO, " (%s)", ceph_cap_op_name(head->op));
	}

	return orig_ofs + sizeof(struct ceph_mds_caps);
}

static guint32 dissect_ceph_client_front(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset, guint16 type)
{
	switch (type) {
	case CEPH_MSG_STATFS:
		offset = dissect_ceph_mon_statfs(tvb, tree, offset);
		break;
	case CEPH_MSG_STATFS_REPLY:
		offset = dissect_ceph_mon_statfs_reply(tvb, tree, offset);
		break;
	case CEPH_MSG_CLIENT_REQUEST: /* mds request */
		offset = dissect_ceph_client_mds_request(tvb, pinfo, tree, offset);
		break;
	case CEPH_MSG_CLIENT_REPLY:
		offset = dissect_ceph_client_mds_reply(tvb, pinfo, tree, offset);
		break;
	case CEPH_MSG_CLIENT_LEASE:
		offset = dissect_ceph_client_mds_lease_request(tvb, pinfo, tree, offset);
		break;
	case CEPH_MSG_CLIENT_CAPS:
		offset = dissect_ceph_client_mds_caps_request(tvb, pinfo, tree, offset);
		break;
	default:
		break;
	}
	return offset;
}

static guint32 dissect_ceph_generic(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset)
{
	proto_tree *ceph_header_tree = NULL;
	proto_item *ceph_sub_item = NULL;
	proto_item *ceph_item = proto_tree_get_parent(tree);
	guint32 front_len, middle_len, data_len;
	guint8 tag;
	guint32 orig_ofs = offset;
	guint16 type;
	guint64 seq;
	struct ceph_msg_header *header;
    unsigned int data_crc = 0;

	ceph_header_tree = proto_item_add_subtree(ceph_item, ett_ceph);

	ceph_sub_item = proto_tree_add_item( tree, hf_ceph_header, tvb, offset, -1, TRUE );
	ceph_header_tree = proto_item_add_subtree(ceph_sub_item, ett_ceph);

	tag = tvb_get_guint8(tvb, offset);

	if (tag == CEPH_MSGR_TAG_ACK) {
		proto_tree_add_item(ceph_header_tree, hf_ceph_hdr_tag, tvb, offset, 1, TRUE);
		proto_tree_add_item(ceph_header_tree, hf_ceph_hdr_seq_ack, tvb, offset+1, 8, TRUE);
		offset += ACK_MSG_SIZE;
	}

	proto_tree_add_item(ceph_header_tree, hf_ceph_hdr_tag, tvb, offset, 1, TRUE);
	offset++;

	header = (struct ceph_msg_header *)tvb_get_ptr(tvb, offset, sizeof(struct ceph_msg_header));

	PROTO_ADD_ITEM(ceph_header_tree, struct ceph_msg_header, hf_ceph_hdr_seq, header, seq);
	PROTO_ADD_ITEM(ceph_header_tree, struct ceph_msg_header, hf_ceph_hdr_type, header, type);
	PROTO_ADD_ITEM(ceph_header_tree, struct ceph_msg_header, hf_ceph_hdr_priority, header, priority);
	PROTO_ADD_ITEM(ceph_header_tree, struct ceph_msg_header, hf_ceph_hdr_version, header, version);
	PROTO_ADD_ITEM(ceph_header_tree, struct ceph_msg_header, hf_ceph_hdr_front_len, header, front_len);
	PROTO_ADD_ITEM(ceph_header_tree, struct ceph_msg_header, hf_ceph_hdr_middle_len, header, middle_len);
	PROTO_ADD_ITEM(ceph_header_tree, struct ceph_msg_header, hf_ceph_hdr_data_off, header, data_off);
	PROTO_ADD_ITEM(ceph_header_tree, struct ceph_msg_header, hf_ceph_hdr_data_len, header, data_len);
	dissect_ceph_entity_inst(tvb, ceph_header_tree, offset + offsetof(struct ceph_msg_header, src));
	dissect_ceph_entity_inst(tvb, ceph_header_tree, offset + offsetof(struct ceph_msg_header, orig_src));
	PROTO_ADD_ITEM(ceph_header_tree, struct ceph_msg_header, hf_ceph_hdr_crc, header, crc);
	
	offset += sizeof(struct ceph_msg_header);
	
	type = TVB_MSG_FIELD(tvb_get_letohl, tvb, orig_ofs, type);
	seq = TVB_MSG_FIELD(tvb_get_letoh64, tvb, orig_ofs, seq);
	front_len = TVB_MSG_FIELD(tvb_get_letohs, tvb, orig_ofs, front_len);
	middle_len = TVB_MSG_FIELD(tvb_get_letohs, tvb, orig_ofs, middle_len);
	data_len = TVB_MSG_FIELD(tvb_get_letohl, tvb, orig_ofs, data_len);

	if (front_len) {
	/*	ceph_sub_item = proto_tree_add_item( tree, hf_ceph_front, tvb, offset, front_len, TRUE );
		offset += front_len; */

		dissect_ceph_client_front(tvb, pinfo, tree, offset, type);
		offset += front_len;
	}

	if (middle_len) {
		 offset += middle_len;
	}

	if (data_len) {
		char *data;
		ceph_sub_item = proto_tree_add_item( tree, hf_ceph_data, tvb, offset, data_len, TRUE );
	    data = (char *)tvb_get_ptr(tvb, offset, data_len);

		data_crc = crc32c_le(0, data, data_len);

		offset += data_len;
	}

	offset = dissect_ceph_footer(tvb, tree, offset, data_crc);

	return offset;
}

static const char *entity_name_by_type(int type)
{
	if (type < 4)
		return "???";

	if (type < 20)
		return "mon";

	if (type < 30)
		return "mds";

	if (type < 50)
		return "osd";

	if (type < 0x300)
		return "???";

	if (type < 0x400)
		return "mds";

	return "???";
}

static void
dissect_ceph_client(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{

	proto_item *ceph_item = NULL;
	proto_tree *ceph_tree = NULL;
	guint16 type = 0;
	const guchar *ptr;
	guint32 pos = 0;
	int have_banner = 0;
	
	if (check_col(pinfo->cinfo, COL_PROTOCOL))
		col_set_str(pinfo->cinfo, COL_PROTOCOL, PROTO_TAG_CEPH);
	/* Clear out stuff in the info column */
	if(check_col(pinfo->cinfo,COL_INFO)){
		col_clear(pinfo->cinfo,COL_INFO);
	}

	ptr = tvb_get_ptr(tvb, pos, 9);
	if (ptr && memcmp(ptr, "ceph", 4) == 0) {
		have_banner = 1;
		pos += 9;
	}

	// This is not a good way of dissecting packets.  The tvb length should
	// be sanity checked so we aren't going past the actual size of the buffer.
	type = tvb_get_guint8( tvb, 4 ); // Get the type byte

	if (check_col(pinfo->cinfo, COL_INFO)) {
		const char *entity_str = NULL;
			
		if (have_banner) {
			if (IS_MON(pinfo))
				entity_str = MON_STR;
			else 
				entity_str = "???";

			col_add_fstr(pinfo->cinfo, COL_INFO, "[%s] Connect Request", entity_str);
		} else {
			type = TVB_MSG_FIELD(tvb_get_letohl, tvb, 0, type);
			entity_str = entity_name_by_type(type);
			col_add_fstr(pinfo->cinfo, COL_INFO, "[%s] %s",
			entity_str,
			val_to_str(type, packettypenames, "Unknown Type:0x%02x"));
		}
	}

	if (tree) { /* we are being asked for details */
		guint32 offset = 0;

		ceph_item = proto_tree_add_item(tree, proto_ceph, tvb, 0, -1, TRUE);
		ceph_tree = proto_item_add_subtree(ceph_item, ett_ceph);
		if (have_banner) { /* this is a connect message */
			offset = dissect_ceph_client_connect(tvb, ceph_tree, offset);
		} else {
			offset = dissect_ceph_generic(tvb, pinfo, ceph_tree, offset);
		}
	}
}

static void
dissect_ceph_server(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{

	proto_item *ceph_item = NULL;
	proto_tree *ceph_tree = NULL;
	guint16 type = 0;
	const guchar *ptr;
	guint32 pos = 0;
	int have_banner = 0;

	if (check_col(pinfo->cinfo, COL_PROTOCOL))
		col_set_str(pinfo->cinfo, COL_PROTOCOL, PROTO_TAG_CEPH);
	/* Clear out stuff in the info column */
	if(check_col(pinfo->cinfo,COL_INFO)){
		col_clear(pinfo->cinfo,COL_INFO);
	}

	ptr = tvb_get_ptr(tvb, pos, 9);
	if (ptr && memcmp(ptr, "ceph", 4) == 0) {
		have_banner = 1;
		pos += 9;
	}

	// This is not a good way of dissecting packets.  The tvb length should
	// be sanity checked so we aren't going past the actual size of the buffer.
	type = tvb_get_guint8( tvb, 4 ); // Get the type byte

	if (check_col(pinfo->cinfo, COL_INFO)) {
		const char *entity_str;
			if (IS_MON(pinfo))
				entity_str = MON_STR;
			else 
				entity_str = "???";
		if (have_banner) {
			col_add_fstr(pinfo->cinfo, COL_INFO, "[%s] Connect Response", entity_str);
		} else {
			type = TVB_MSG_FIELD(tvb_get_letohl, tvb, 0, type);
			entity_str = entity_name_by_type(type);
			col_add_fstr(pinfo->cinfo, COL_INFO, "[%s] %s",
			entity_str,
			val_to_str(type, packettypenames, "Unknown Type:0x%02x"));
		}
	}

	if (tree) { /* we are being asked for details */
		guint32 offset = 0;

		ceph_item = proto_tree_add_item(tree, proto_ceph, tvb, 0, -1, TRUE);
		ceph_tree = proto_item_add_subtree(ceph_item, ett_ceph);

		if (have_banner) {
			offset = dissect_ceph_server_connect(tvb, ceph_tree, offset);
		} else {
			offset = dissect_ceph_generic(tvb, pinfo, ceph_tree, offset);
		}
	}
}

static void
dissect_ceph_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
	if (DEST_PORT_CEPH)
		dissect_ceph_client(tvb, pinfo, tree);
	else
		dissect_ceph_server(tvb, pinfo, tree);
}		

static guint dissect_ceph_acks(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
	guint32 offset = 0;

	if (check_col(pinfo->cinfo, COL_PROTOCOL))
		col_set_str(pinfo->cinfo, COL_PROTOCOL, PROTO_TAG_CEPH);
	/* Clear out stuff in the info column */
	if(check_col(pinfo->cinfo,COL_INFO)){
		col_clear(pinfo->cinfo,COL_INFO);
		col_add_fstr(pinfo->cinfo, COL_INFO, "Ack");
	}
	if (tree) {
		proto_tree_add_item(tree, proto_ceph, tvb, 0, 5, TRUE);
		proto_tree_add_item(tree, hf_ceph_hdr_tag, tvb, offset, 1, TRUE);
		proto_tree_add_item(tree, hf_ceph_hdr_seq_ack, tvb, offset+1, 8, TRUE);
		offset += 9;
	}

	return offset;
}

/* determine PDU length of protocol ceph */
static guint get_ceph_message_len(packet_info *pinfo, tvbuff_t *tvb, int offset)
{
	const char *ptr;
	guint32 len;
	guint32 pos = 0;

	ptr = tvb_get_ptr(tvb, offset, /* sizeof(CEPH_BANNER) */tvb->length-offset);
	if (ptr && memcmp(ptr, "ceph", 4) == 0) {
		if (DEST_PORT_CEPH) {
			len = sizeof(CEPH_BANNER) - 1 +
				sizeof(struct ceph_entity_addr) +
				sizeof(struct ceph_msg_connect);
		} else
			len = sizeof(CEPH_BANNER) - 1 +
				sizeof(struct ceph_entity_addr) +
				sizeof(struct ceph_entity_addr) +
				sizeof(struct ceph_msg_connect_reply);

		return len;
	}

	if (*ptr == CEPH_MSGR_TAG_ACK)
		pos = ACK_MSG_SIZE;

	len = pos + (guint)1 + sizeof(struct ceph_msg_header) +
		TVB_MSG_FIELD(tvb_get_letohl, tvb, offset, front_len) + 
		TVB_MSG_FIELD(tvb_get_letohl, tvb, offset, data_len) + 
		sizeof(struct ceph_msg_footer);
	
	if (!*ptr)
		return 0;
	return len;
}


static void dissect_ceph(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
	const char *ptr;

	ptr = tvb_get_ptr(tvb, 0, 6);

	if ((*ptr == CEPH_MSGR_TAG_MSG) || 
            (memcmp(ptr, CEPH_BANNER, 4) == 0) || 
	   ((ptr[0] == CEPH_MSGR_TAG_ACK) && (ptr[5] == CEPH_MSGR_TAG_MSG))
	)  {
		tcp_dissect_pdus(tvb, pinfo, tree, TRUE, TVB_MSG_HEADER_POS(src),
			get_ceph_message_len, dissect_ceph_message);
	} else {
		dissect_ceph_acks(tvb, pinfo, tree);
	}
}

