/* packet-mojito.c
 * Routines for Dissecting the Gnutella Mojito DHT Protocol
 * http://limewire.negatis.com/index.php?title=Mojito_Message_Format
 *
 * Copyright (c) 2008 by Travis Dawson <travis.dawson@sprint.com>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "config.h"

#include <epan/packet.h>
#include <epan/expert.h>

void proto_register_mojito(void);
void proto_reg_handoff_mojito(void);

static dissector_handle_t mojito_handle;

#define MOJITO_HEADER_LENGTH    38

/* All the Defines for OpCodes */
#define MOJITO_PING_REQUEST                1
#define MOJITO_PING_RESPONSE               2
#define MOJITO_STORE_REQUEST               3
#define MOJITO_STORE_RESPONSE              4
#define MOJITO_FIND_NODE_REQUEST           5
#define MOJITO_FIND_NODE_RESPONSE          6
#define MOJITO_FIND_VALUE_REQUEST          7
#define MOJITO_FIND_VALUE_RESPONSE         8
#define MOJITO_STATS_REQUEST_DEPRECATED    9
#define MOJITO_STATS_RESPONSE_DEPRECATED  10

/* Initialize the protocol and registered fields */
static int proto_mojito = -1;

/* Start of fields */
static int hf_mojito_messageid = -1;
static int hf_mojito_fdhtmessage = -1;
static int hf_mojito_mjrversion = -1;
static int hf_mojito_mnrversion = -1;
static int hf_mojito_length = -1;
static int hf_mojito_opcode = -1;
static int hf_mojito_vendor = -1;
static int hf_mojito_origmjrversion = -1;
static int hf_mojito_origmnrversion = -1;
static int hf_mojito_kuid = -1;
static int hf_mojito_socketaddress_version = -1;
static int hf_mojito_socketaddress_ipv4 = -1;
static int hf_mojito_socketaddress_ipv6 = -1;
static int hf_mojito_socketaddress_port = -1;
static int hf_mojito_instanceid = -1;
static int hf_mojito_flags = -1;
static int hf_mojito_flags_shutdown = -1;
static int hf_mojito_flags_firewalled = -1;
static int hf_mojito_extendedlength = -1;
static int hf_mojito_kuidcount = -1;
static int hf_mojito_bigintegerlen = -1;
static int hf_mojito_bigintegerval = -1;
static int hf_mojito_dhtvaluetype = -1;
static int hf_mojito_sectokenlen = -1;
static int hf_mojito_sectoken = -1;
static int hf_mojito_contactcount = -1;
static int hf_mojito_contactvendor = -1;
static int hf_mojito_contactversion = -1;
static int hf_mojito_contactkuid = -1;
static int hf_mojito_dhtvaluecount = -1;
static int hf_mojito_dhtvalue_kuid = -1;
static int hf_mojito_target_kuid = -1;
static int hf_mojito_dhtvalue_valuetype = -1;
static int hf_mojito_dhtvalue_version = -1;
static int hf_mojito_dhtvalue_length = -1;
static int hf_mojito_dhtvalue_value = -1;
static int hf_mojito_bigint_value_one = -1;
static int hf_mojito_bigint_value_two = -1;
static int hf_mojito_bigint_value_three = -1;
static int hf_mojito_bigint_value_four = -1;
static int hf_mojito_storestatuscode_count = -1;
static int hf_mojito_storestatuscode_code = -1;
static int hf_mojito_storestatuscode_kuid = -1;
static int hf_mojito_storestatuscode_secondary_kuid = -1;
static int hf_mojito_requestload = -1;
#if 0
static int hf_mojito_startflag = -1;
static int hf_mojito_endflag = -1;
static int hf_mojito_priorityflag = -1;
#endif
static int hf_mojito_opcode_data = -1;

/* Initialize the subtree pointers */
static gint ett_mojito = -1;
static gint ett_mojito_header = -1;
static gint ett_mojito_header_version = -1;
static gint ett_mojito_contact = -1;
static gint ett_mojito_contact_version = -1;
static gint ett_mojito_socket_address = -1;
static gint ett_mojito_flags = -1;
static gint ett_mojito_bigint = -1;
static gint ett_mojito_opcode = -1;
static gint ett_mojito_dht_version = -1;
static gint ett_mojito_dht = -1;
static gint ett_mojito_status_code = -1;
static gint ett_mojito_kuids = -1;

static expert_field ei_mojito_socketaddress_unknown = EI_INIT;
static expert_field ei_mojito_bigint_unsupported = EI_INIT;

typedef struct mojito_header_data {
	guint8 opcode;
	guint32 payloadlength;
} mojito_header_data_t;

/* Values for OPCode Flags */
static const value_string opcodeflags[] = {
	{ MOJITO_PING_REQUEST,              "PING REQUEST" },
	{ MOJITO_PING_RESPONSE,             "PING RESPONSE" },
	{ MOJITO_STORE_REQUEST,             "STORE REQUEST" },
	{ MOJITO_STORE_RESPONSE,            "STORE RESPONSE" },
	{ MOJITO_FIND_NODE_REQUEST,         "FIND NODE REQUEST" },
	{ MOJITO_FIND_NODE_RESPONSE,        "FIND NODE RESPONSE" },
	{ MOJITO_FIND_VALUE_REQUEST,        "FIND VALUE REQUEST" },
	{ MOJITO_FIND_VALUE_RESPONSE,       "FIND VALUE RESPONSE" },
	{ MOJITO_STATS_REQUEST_DEPRECATED,  "STATS REQUEST (DEPRECATED)" },
	{ MOJITO_STATS_RESPONSE_DEPRECATED, "STATS RESPONSE (DEPRECATED)" },
	{ 0, NULL }
};

static const value_string statuscodeflags[] = {
	{ 1, "OK" },
	{ 2, "Error" },
	{ 0, NULL }
};

#if 0
static const value_string vendorcodeflags[] = {
	{  0, "MESSAGES_SUPPORTED" },
	{  4, "HOPS_FLOW" },
	{  5, "CRAWLER_PING" },
	{  6, "CRAWLER_PONG" },
	{  7, "UDP_CONNECT_BACK" },
	{  8, "UDP_CONNECT_BACK_REDIR" },
	{  9, "NGTH_MINUS_PAYLOAD" },
	{ 10, "CAPABILITIES" },
	{ 11, "LIME_ACK" },
	{ 12, "REPLY_NUMBER" },
	{ 13, "OOB_PROXYING_CONTROL" },
	{ 14, "GIVE_STATS" },
	{ 15, "STATISTICS" },
	{ 16, "SIMPP_REQ" },
	{ 17, "SIMPP" },
	{ 21, "PUSH_PROXY_REQ" },
	{ 22, "PUSH_PROXY_ACK" },
	{ 23, "UDP_HEAD_PING" },
	{ 24, "UDP_HEAD_PONG" },
	{ 25, "HEADER_UPDATE" },
	{ 26, "UPDATE_REQ" },
	{ 27, "UPDATE_RESP" },
	{ 28, "CONTENT_REQ" },
	{ 29, "CONTENT_RESP" },
	{ 30, "INSPECTION_REQ" },
	{ 31, "INSPECTION_RESP" },
	{ 32, "ADVANCED_TOGGLE" },
	{ 33, "DHT_CONTACTS" },

	{ 0, NULL }
};
#endif

static int
dissect_mojito_address(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
		int offset, const char *title)
{
	int         offset_start;
	guint8      socket_address_version;
	proto_tree *socket_tree;
	proto_item *socket_item;

	offset_start = offset;

	/* new subtree for socket address*/
	socket_address_version = tvb_get_guint8(tvb, offset);
	socket_tree = proto_tree_add_subtree(tree, tvb, offset, 1, ett_mojito_socket_address, &socket_item, title);

	proto_tree_add_item(socket_tree, hf_mojito_socketaddress_version, tvb, offset, 1, ENC_NA);
	offset += 1;

	switch (socket_address_version)
	{
	case FT_IPv4_LEN: /* IPv4 */

		proto_tree_add_item(socket_tree, hf_mojito_socketaddress_ipv4, tvb, offset, 4, ENC_BIG_ENDIAN);
		offset += 4;
		break;

	case FT_IPv6_LEN: /* IPv6 */

		proto_tree_add_item(socket_tree, hf_mojito_socketaddress_ipv6, tvb, offset, 16, ENC_NA);
		offset += 16;
		break;

	default: /* ABORT */
		expert_add_info(pinfo, socket_item, &ei_mojito_socketaddress_unknown);
		return 0;
	}

	proto_tree_add_item(socket_tree, hf_mojito_socketaddress_port, tvb, offset, 2, ENC_BIG_ENDIAN);
	offset += 2;

	proto_item_set_len(socket_item, offset - offset_start);

	return offset;
}

static int
dissect_mojito_contact(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, int contact_id)
{
	int         offset_start;
	proto_tree *contact_tree, *version_tree;
	proto_item *contact_item, *version_item;

	offset_start = offset;

	if (contact_id > 0)
	{
		contact_tree = proto_tree_add_subtree_format(tree, tvb, offset, 1, ett_mojito_contact, &contact_item, "Contact #%d", contact_id);
	}
	else
	{
		contact_tree = proto_tree_add_subtree(tree, tvb, offset, 1, ett_mojito_contact, &contact_item, "Contact");
	}

	proto_tree_add_item(contact_tree, hf_mojito_contactvendor, tvb, offset, 4, ENC_ASCII);
	offset += 4;

	version_item = proto_tree_add_item(contact_tree, hf_mojito_contactversion, tvb, offset, 2, ENC_BIG_ENDIAN);
	version_tree = proto_item_add_subtree(version_item, ett_mojito_contact_version);
	proto_tree_add_item(version_tree, hf_mojito_mjrversion, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;
	proto_tree_add_item(version_tree, hf_mojito_mnrversion, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;

	proto_tree_add_item(contact_tree, hf_mojito_contactkuid, tvb, offset, 20, ENC_NA);
	offset += 20;

	offset = dissect_mojito_address(tvb, pinfo, contact_tree, offset, "Socket Address");

	if (offset == 0)
	{
		return 0;
	}

	proto_item_set_len(contact_item, offset - offset_start);

	return offset - offset_start;
}

static int
dissect_mojito_header(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
		      int offset, mojito_header_data_t* header_data)
{
	proto_tree *header_tree, *version_tree, *contact_tree, *flag_tree;
	proto_item *header_item, *contact_item, *flag_item;
	int         start_offset = offset;
	int         contact_start_offset;

	header_tree = proto_tree_add_subtree(tree, tvb, offset, 61, ett_mojito_header, &header_item, "Gnutella Header");

	proto_tree_add_item(header_tree, hf_mojito_messageid, tvb, offset, 16, ENC_NA);
	offset += 16;

	proto_tree_add_item(header_tree, hf_mojito_fdhtmessage, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;

	version_tree = proto_tree_add_subtree(header_tree, tvb, offset, 2, ett_mojito_header_version, NULL, "Version");

	proto_tree_add_item(version_tree, hf_mojito_mjrversion, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;
	proto_tree_add_item(version_tree, hf_mojito_mnrversion, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;

	/* Payload Length : in Little Endian */
	header_data->payloadlength = tvb_get_letohl(tvb, offset);
	proto_tree_add_item(header_tree, hf_mojito_length, tvb, offset, 4, ENC_LITTLE_ENDIAN);
	offset += 4;

	header_data->opcode = tvb_get_guint8(tvb, offset);
	col_add_str(pinfo->cinfo, COL_INFO, val_to_str_const(header_data->opcode, opcodeflags, "Unknown"));
	proto_tree_add_item(header_tree, hf_mojito_opcode, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;

	contact_start_offset = offset;
	contact_tree = proto_tree_add_subtree(header_tree, tvb, offset, 35, ett_mojito_contact, &contact_item, "Originating Contact");

	proto_tree_add_item(contact_tree, hf_mojito_vendor, tvb, offset, 4, ENC_ASCII);
	offset += 4;

	version_tree = proto_tree_add_subtree(contact_tree, tvb, offset, 2, ett_mojito_contact_version, NULL, "Contact Version");

	proto_tree_add_item(version_tree, hf_mojito_origmjrversion, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;
	proto_tree_add_item(version_tree, hf_mojito_origmnrversion, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;

	proto_tree_add_item(contact_tree, hf_mojito_kuid, tvb, offset, 20, ENC_NA);
	offset += 20;

	offset = dissect_mojito_address(tvb, pinfo, contact_tree, offset, "Socket Address");

	if (offset == 0)
	{
		return 0;
	}

	proto_item_set_len(contact_item, offset - contact_start_offset);

	proto_tree_add_item(header_tree, hf_mojito_instanceid, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;

	/*Flags*/
	flag_item = proto_tree_add_item(header_tree, hf_mojito_flags, tvb, offset, 1, ENC_BIG_ENDIAN);
	flag_tree = proto_item_add_subtree(flag_item, ett_mojito_flags);
	proto_tree_add_item(flag_tree, hf_mojito_flags_shutdown, tvb, offset, 1, ENC_BIG_ENDIAN);
	proto_tree_add_item(flag_tree, hf_mojito_flags_firewalled, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;

	proto_tree_add_item(header_tree, hf_mojito_extendedlength, tvb, offset, 2, ENC_BIG_ENDIAN);
	offset += 2;

	proto_item_set_len(header_item, offset-start_offset);
	return offset;
}

static void
dissect_mojito_ping_response(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset)
{
	guint8      bigintlen;
	proto_tree *bigint_tree;
	proto_item *bigint_item;

	offset = dissect_mojito_address(tvb, pinfo, tree,
			offset, "Requester's External Socket Address");

	if (offset == 0)
	{
		return;
	}

	/* BigInt subtree */
	bigintlen = tvb_get_guint8(tvb, offset);
	bigint_tree = proto_tree_add_subtree(tree, tvb, offset, bigintlen + 1, ett_mojito_bigint, &bigint_item, "Estimated DHT size");

	proto_tree_add_item(bigint_tree, hf_mojito_bigintegerlen, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;

	switch (bigintlen)
	{
	case 1: /* 1 byte */
		proto_tree_add_item(bigint_tree, hf_mojito_bigint_value_one, tvb, offset, bigintlen, ENC_BIG_ENDIAN);
		break;

	case 2: /* 2 byte */
		proto_tree_add_item(bigint_tree, hf_mojito_bigint_value_two, tvb, offset, bigintlen, ENC_BIG_ENDIAN);
		break;

	case 3: /* 3 byte */
		proto_tree_add_item(bigint_tree, hf_mojito_bigint_value_three, tvb, offset, bigintlen, ENC_BIG_ENDIAN);
		break;

	case 4: /* 4 byte */
		proto_tree_add_item(bigint_tree, hf_mojito_bigint_value_four, tvb, offset, bigintlen, ENC_BIG_ENDIAN);
		break;
	default: /* ABORT */
		expert_add_info(pinfo, bigint_item, &ei_mojito_bigint_unsupported);
		return;
	}

	/* BigInt Value */
	proto_tree_add_item(bigint_tree, hf_mojito_bigintegerval, tvb, offset, bigintlen, ENC_NA);
}

static void
dissect_mojito_store_request(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset)
{
	proto_tree *dht_tree, *version_tree;
	proto_item *dht_item, *version_item;
	guint8      ii, contactcount;
	guint8      sectokenlen = tvb_get_guint8(tvb, offset);
	guint16     dhtvaluelength;
	int         contact_offset, start_offset;

	proto_tree_add_item(tree, hf_mojito_sectokenlen, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;

	proto_tree_add_item(tree, hf_mojito_sectoken, tvb, offset, sectokenlen, ENC_NA);
	offset += sectokenlen;

	/* Contact count */
	proto_tree_add_item(tree, hf_mojito_dhtvaluecount, tvb, offset, 1, ENC_BIG_ENDIAN);
	contactcount = tvb_get_guint8(tvb, offset);
	offset += 1;

	/* For each Contact, display the info */
	for (ii = 0; ii < contactcount; ii++)
	{
		dht_tree = proto_tree_add_subtree_format(tree, tvb, offset, 1, ett_mojito_dht, &dht_item, "DHTValue #%d", ii+1);
		start_offset = offset;
		contact_offset = dissect_mojito_contact(tvb, pinfo, dht_tree, offset, -1);
		if (contact_offset == 0)
			return;
		offset += contact_offset;

		proto_tree_add_item(dht_tree, hf_mojito_dhtvalue_kuid, tvb, offset, 20, ENC_NA);
		offset += 20;

		proto_tree_add_item(dht_tree, hf_mojito_dhtvalue_valuetype, tvb, offset, 4, ENC_ASCII);
		offset += 4;

		/* Version */
		version_item = proto_tree_add_item(dht_tree, hf_mojito_dhtvalue_version, tvb, offset, 2, ENC_BIG_ENDIAN);
		version_tree = proto_item_add_subtree(version_item, ett_mojito_dht_version);

		proto_tree_add_item(version_tree, hf_mojito_mjrversion, tvb, offset, 1, ENC_BIG_ENDIAN);
		offset += 1;
		proto_tree_add_item(version_tree, hf_mojito_mnrversion, tvb, offset, 1, ENC_BIG_ENDIAN);
		offset += 1;

		dhtvaluelength = tvb_get_ntohs(tvb, offset);
		proto_tree_add_item(dht_tree, hf_mojito_dhtvalue_length, tvb, offset, 2, ENC_BIG_ENDIAN);
		offset += 2;

		proto_tree_add_item(dht_tree, hf_mojito_dhtvalue_value, tvb, offset, dhtvaluelength, ENC_ASCII);
		offset += dhtvaluelength;

		proto_item_set_len(dht_item, offset-start_offset);
	}
}

static void
dissect_mojito_store_response(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, int offset)
{
	proto_tree *sc_tree;
	proto_item *sc_item;
	guint8      ii, contactcount = tvb_get_guint8(tvb, offset);
	guint16     dhtvaluelength;
	int         start_offset;

	proto_tree_add_item(tree, hf_mojito_storestatuscode_count, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;

	/* For each Contact, display the info */
	for (ii = 0; ii < contactcount; ii++)
	{
		sc_tree = proto_tree_add_subtree_format(tree, tvb, offset, 23, ett_mojito_status_code, &sc_item, "Status Code %d", ii+1);

		start_offset = offset;

		/*Primary KUID */
		proto_tree_add_item(sc_tree, hf_mojito_storestatuscode_kuid, tvb, offset, 20, ENC_NA);
		offset += 20;

		if (tvb_reported_length_remaining(tvb, offset+3) > 0)
		{
			/* Must be a secondard KUID */
			proto_tree_add_item(sc_tree, hf_mojito_storestatuscode_secondary_kuid, tvb, offset, 20, ENC_NA);
			offset += 20;
		}

		proto_tree_add_item(sc_tree, hf_mojito_storestatuscode_code, tvb, offset, 2, ENC_BIG_ENDIAN);
		offset += 2;

		dhtvaluelength = tvb_get_ntohs(tvb, offset);
		proto_tree_add_item(sc_tree, hf_mojito_dhtvalue_length, tvb, offset, 2, ENC_BIG_ENDIAN);
		offset += 2;

		proto_tree_add_item(sc_tree, hf_mojito_dhtvalue_value, tvb, offset, dhtvaluelength, ENC_ASCII);
		offset += dhtvaluelength;

		proto_item_set_len(sc_item, offset-start_offset);
	}
}

static void
dissect_mojito_find_node_response(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset)
{
	guint8 ii, contactcount;
	guint8 sectokenlen = tvb_get_guint8(tvb, offset);
	int    contact_offset;

	proto_tree_add_item(tree, hf_mojito_sectokenlen, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;

	proto_tree_add_item(tree, hf_mojito_sectoken, tvb, offset, sectokenlen, ENC_NA);
	offset += sectokenlen;

	contactcount = tvb_get_guint8(tvb, offset);
	proto_tree_add_item(tree, hf_mojito_contactcount, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;

	/* For each Contact, display the info */
	for (ii = 0; ii < contactcount; ii++)
	{
		contact_offset = dissect_mojito_contact(tvb, pinfo, tree, offset, ii+1);
		if (contact_offset == 0)
			return;
		offset += contact_offset;
	}
}

static void
dissect_mojito_find_value_request(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, int offset)
{
	proto_tree *kuid_tree;
	guint8      i, kuidcount;

	if (!tree)
		return;

	proto_tree_add_item(tree, hf_mojito_target_kuid, tvb, offset, 20, ENC_NA);
	offset += 20;

	kuidcount = tvb_get_guint8(tvb, offset);

	kuid_tree = proto_tree_add_subtree(tree, tvb, offset, (20 * kuidcount) + 1, ett_mojito_kuids, NULL, "Secondary KUID\'s");

	proto_tree_add_item(kuid_tree, hf_mojito_kuidcount, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;

	/* All the Secondary KUID's */
	for (i = 0; i < kuidcount; i++)
	{
		proto_tree_add_item(kuid_tree, hf_mojito_kuid, tvb, offset, 20, ENC_NA);
		offset += 20;
	}

	proto_tree_add_item(tree, hf_mojito_dhtvaluetype, tvb, offset, 4, ENC_ASCII);
	/*offset += 4;*/
}

static void
dissect_mojito_find_value_response(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset)
{
	proto_tree *dht_tree, *version_tree, *kuid_tree;
	proto_item *dht_item, *version_item;
	guint16     dhtvaluelength;
	int         contact_offset, start_offset;
	guint8      ii, dhtvaluescount, kuidcount;

	proto_tree_add_item(tree, hf_mojito_requestload, tvb, offset, 4, ENC_BIG_ENDIAN);
	offset += 4;

	dhtvaluescount = tvb_get_guint8(tvb, offset);
	proto_tree_add_item(tree, hf_mojito_dhtvaluecount, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;

	/* For each Contact, display the info */
	for (ii = 0; ii < dhtvaluescount; ii++)
	{
		dht_tree = proto_tree_add_subtree_format(tree, tvb, offset, 1, ett_mojito_dht, &dht_item, "DHTValue #%d", ii+1);
		start_offset = offset;
		contact_offset = dissect_mojito_contact(tvb, pinfo, dht_tree, offset, -1);
		if (contact_offset == 0)
			return;

		offset += contact_offset;

		proto_tree_add_item(dht_tree, hf_mojito_dhtvalue_kuid, tvb, offset, 20, ENC_NA);
		offset += 20;

		proto_tree_add_item(dht_tree, hf_mojito_dhtvalue_valuetype, tvb, offset, 4, ENC_ASCII);
		offset += 4;

		/* Version */
		version_item = proto_tree_add_item(dht_tree, hf_mojito_dhtvalue_version, tvb, offset, 2, ENC_BIG_ENDIAN);
		version_tree = proto_item_add_subtree(version_item, ett_mojito_dht_version);

		proto_tree_add_item(version_tree, hf_mojito_mjrversion, tvb, offset, 1, ENC_BIG_ENDIAN);
		offset += 1;
		proto_tree_add_item(version_tree, hf_mojito_mnrversion, tvb, offset, 1, ENC_BIG_ENDIAN);
		offset += 1;

		/* Length */
		dhtvaluelength = tvb_get_ntohs(tvb, offset);
		proto_tree_add_item(dht_tree, hf_mojito_dhtvalue_length, tvb, offset, 2, ENC_BIG_ENDIAN);
		offset += 2;

		/* Value */
		proto_tree_add_item(dht_tree, hf_mojito_dhtvalue_value, tvb, offset, dhtvaluelength, ENC_ASCII);
		offset += dhtvaluelength;

		proto_item_set_len(dht_item, offset-start_offset);
	}

	/*KUID Count */
	kuidcount = tvb_get_guint8(tvb, offset);
	kuid_tree = proto_tree_add_subtree(tree, tvb, offset, (20 * kuidcount) + 1, ett_mojito_kuids, NULL, "Secondary KUID\'s");
	proto_tree_add_item(kuid_tree, hf_mojito_kuidcount, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;

	/* All the Secondary KUID's */
	for (ii = 0; ii < kuidcount; ii++)
	{
		proto_tree_add_item(kuid_tree, hf_mojito_kuid, tvb, offset, 20, ENC_NA);
		offset += 20;
	}
}

static int
dissect_mojito(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
	proto_tree           *mojito_tree, *opcode_tree;
	proto_item           *ti;
	mojito_header_data_t  header_data;
	gint                  offset = 0;

	col_set_str(pinfo->cinfo, COL_PROTOCOL, "Mojito");
	col_clear(pinfo->cinfo, COL_INFO);

	/* Add a new item to the tree */
	ti = proto_tree_add_item(tree, proto_mojito, tvb, 0, -1, ENC_NA);
	mojito_tree = proto_item_add_subtree(ti, ett_mojito);

	offset = dissect_mojito_header(tvb, pinfo, mojito_tree, offset, &header_data);
	if (offset == 0) /* Some error occurred */
		return 0;

	opcode_tree = proto_tree_add_subtree_format(mojito_tree, tvb,
					  offset, header_data.payloadlength - MOJITO_HEADER_LENGTH,
					  ett_mojito_opcode, NULL, "Opcode specific data (%s)",
					  val_to_str_const(header_data.opcode, opcodeflags, "Unknown"));

	/* Now use the opcode to figure out what to do next */
	switch (header_data.opcode)
	{
	case MOJITO_PING_RESPONSE: /* PING RESPONSE */
		dissect_mojito_ping_response(tvb, pinfo, opcode_tree, offset);
		break;

	case MOJITO_STORE_REQUEST: /* STORE REQUEST */
		dissect_mojito_store_request(tvb, pinfo, opcode_tree, offset);
		break;

	case MOJITO_STORE_RESPONSE: /* STORE RESPONSE */
		dissect_mojito_store_response(tvb, pinfo, opcode_tree, offset);
		break;

	case MOJITO_FIND_NODE_REQUEST: /* FIND NODE REQUEST */
		proto_tree_add_item(opcode_tree, hf_mojito_target_kuid, tvb, offset, 20, ENC_NA);
		break;

	case MOJITO_FIND_NODE_RESPONSE: /* FIND NODE RESPONSE */
		dissect_mojito_find_node_response(tvb, pinfo, opcode_tree, offset);
		break;

	case MOJITO_FIND_VALUE_REQUEST: /* FIND VALUE REQUEST */
		dissect_mojito_find_value_request(tvb, pinfo, opcode_tree, offset);
		break;

	case MOJITO_FIND_VALUE_RESPONSE: /* FIND VALUE RESPONSE */
		dissect_mojito_find_value_response(tvb, pinfo, opcode_tree, offset);
		break;

	case MOJITO_PING_REQUEST: /* PING REQUEST */
	case MOJITO_STATS_REQUEST_DEPRECATED: /* STATS REQUEST (DEPRECATED) */
	case MOJITO_STATS_RESPONSE_DEPRECATED: /* STATS RESPONSE (DEPRECATED) */
	default:
		if (header_data.payloadlength - MOJITO_HEADER_LENGTH > 0)
			proto_tree_add_item(opcode_tree, hf_mojito_opcode_data, tvb,
					    offset, header_data.payloadlength - MOJITO_HEADER_LENGTH, ENC_NA);
		break;
	}

	return tvb_captured_length(tvb);
}

static gboolean dissect_mojito_heuristic (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
	/*
	  Test the overall length to make sure it's at least 61 bytes (the header)
	  Test to make sure that it's of type 44 (mojito)
	  Test to make sure that the length field is there and correct
	  (tvb_get_letohl(tvb, 20) + 23) == tvb_length(tvb)
	*/
	if ((tvb_captured_length(tvb) >= 60) &&
	    (tvb_get_guint8(tvb, 16) == 68) &&
	    ((tvb_get_letohl(tvb, 19) + 23) == tvb_reported_length(tvb)))
	{
		dissect_mojito(tvb, pinfo, tree, NULL);
		return TRUE;
	}

	return FALSE;
}

/* Register the mojito dissector */
void
proto_register_mojito(void)
{
	expert_module_t* expert_mojito;

	static hf_register_info hf[] = {
		{ &hf_mojito_dhtvaluecount,
		  { "DHTValue Count", "mojito.dhtvaluecount",
		    FT_UINT8, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_messageid,
		  { "Message ID", "mojito.messageid",
		    FT_BYTES, BASE_NONE,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_requestload,
		  { "Request Load", "mojito.requestload",
		    FT_UINT32, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_fdhtmessage,
		  { "FDHTMessage", "mojito.fdhtmessage",
		    FT_UINT8, BASE_HEX,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_mjrversion,
		  { "Major Version", "mojito.majorversion",
		    FT_UINT8, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_mnrversion,
		  { "Minor Version", "mojito.minorversion",
		    FT_UINT8, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_length,
		  { "Payload Length", "mojito.payloadlength",
		    FT_UINT32, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_opcode,
		  { "OPCode", "mojito.opcode",
		    FT_UINT8, BASE_DEC,
		    VALS(opcodeflags), 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_vendor,
		  { "Vendor", "mojito.vendor",
		    FT_STRING, BASE_NONE,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_origmjrversion,
		  { "Major Version", "mojito.majorversion",
		    FT_UINT8, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_origmnrversion,
		  { "Minor Version", "mojito.minorversion",
		    FT_UINT8, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_kuid,
		  { "Kademlia Unique ID (KUID)", "mojito.kuid",
		    FT_BYTES, BASE_NONE,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_socketaddress_version,
		  { "IP Version", "mojito.socketaddressversion",
		    FT_UINT8, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_socketaddress_ipv4,
		  { "IP Address", "mojito.socketaddressipv4",
		    FT_IPv4, BASE_NONE,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_socketaddress_ipv6,
		  { "IP Address", "mojito.socketaddressipv6",
		    FT_IPv6, BASE_NONE,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_socketaddress_port,
		  { "IP Port", "mojito.socketaddressport",
		    FT_UINT16, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_instanceid,
		  { "Instance ID", "mojito.instanceid",
		    FT_UINT8, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_flags,
		  { "Flags", "mojito.flags",
		    FT_UINT8, BASE_HEX,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_flags_shutdown,
		  { "SHUTDOWN", "mojito.shutdownflag",
		    FT_BOOLEAN, 8,
		    NULL, 2,
		    NULL, HFILL }
		},
		{ &hf_mojito_flags_firewalled,
		  { "Firewalled", "mojito.firewalledflag",
		    FT_BOOLEAN, 8,
		    NULL, 1,
		    NULL, HFILL }
		},
		{ &hf_mojito_extendedlength,
		  { "Extended Length", "mojito.extlength",
		    FT_UINT16, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_kuidcount,
		  { "Secondary KUID Count", "mojito.kuidcount",
		    FT_UINT8, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_dhtvaluetype,
		  { "DHT Value Type", "mojito.dhtvaluetype",
		    FT_STRING, BASE_NONE,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_bigintegerlen,
		  { "Big Integer Length", "mojito.bigintegerlen",
		    FT_UINT8, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_bigintegerval,
		  { "Big Integer HEX Value", "mojito.bigintegerhexval",
		    FT_BYTES, BASE_NONE,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_sectokenlen,
		  { "Security Token Length", "mojito.sectokenlen",
		    FT_UINT8, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_sectoken,
		  { "Security Token", "mojito.sectoken",
		    FT_BYTES, BASE_NONE,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_contactcount,
		  { "Contact Count", "mojito.contactcount",
		    FT_UINT8, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_contactvendor,
		  { "Vendor", "mojito.contactvendor",
		    FT_STRING, BASE_NONE,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_contactversion,
		  { "Contact Version", "mojito.contactversion",
		    FT_UINT16, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_contactkuid,
		  { "KUID of the Contact", "mojito.contactkuid",
		    FT_BYTES, BASE_NONE,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_dhtvalue_valuetype,
		  { "DHTValue ValueType", "mojito.dhtvaluevaluetype",
		    FT_STRING, BASE_NONE,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_dhtvalue_version,
		  { "DHTValue Version", "mojito.dhtvalueversion",
		    FT_UINT16, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_dhtvalue_length,
		  { "DHTValue Length", "mojito.dhtvaluelength",
		    FT_UINT16, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_dhtvalue_value,
		  { "DHTValue", "mojito.dhtvaluehexvalue",
		    FT_STRING, BASE_NONE,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_bigint_value_one,
		  { "Big Integer DEC Value", "mojito.bigintegerval",
		    FT_UINT8, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_bigint_value_two,
		  { "Big Integer DEC Value", "mojito.bigintegerval",
		    FT_UINT16, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_bigint_value_three,
		  { "Big Integer DEC Value", "mojito.bigintegerval",
		    FT_UINT24, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_bigint_value_four,
		  { "Big Integer DEC Value", "mojito.bigintegerval",
		    FT_UINT32, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_dhtvalue_kuid,
		  { "Kademlia Unique ID (KUID)", "mojito.kuid",
		    FT_BYTES, BASE_NONE,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_target_kuid,
		  { "Target Kademlia Unique ID (KUID)", "mojito.kuid",
		    FT_BYTES, BASE_NONE,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_storestatuscode_count,
		  { "Status Code Count", "mojito.statuscodecount",
		    FT_UINT8, BASE_DEC,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_storestatuscode_code,
		  { "StatusCode", "mojito.statuscodecode",
		    FT_UINT16, BASE_DEC,
		    VALS(statuscodeflags), 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_storestatuscode_kuid,
		  { "Primary KUID of the Status Code", "mojito.statuscodekuid",
		    FT_BYTES, BASE_NONE,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_storestatuscode_secondary_kuid,
		  { "Secondary KUID of the Status Code", "mojito.statuscodesecondarykuid",
		    FT_BYTES, BASE_NONE,
		    NULL, 0x0,
		    NULL, HFILL }
		},
		{ &hf_mojito_opcode_data,
		  { "Data", "mojito.opcode.data",
		    FT_BYTES, BASE_NONE,
		    NULL, 0x0,
		    NULL, HFILL }
		}
	};

	/* Setup protocol subtree array */
	static gint *ett[] = {
		&ett_mojito,
		&ett_mojito_header,
		&ett_mojito_header_version,
		&ett_mojito_contact,
		&ett_mojito_contact_version,
		&ett_mojito_socket_address,
		&ett_mojito_flags,
		&ett_mojito_bigint,
		&ett_mojito_opcode,
		&ett_mojito_dht_version,
		&ett_mojito_dht,
		&ett_mojito_status_code,
		&ett_mojito_kuids
	};

	static ei_register_info ei[] = {
		{ &ei_mojito_socketaddress_unknown, { "mojito.socketaddress.unknown", PI_PROTOCOL, PI_ERROR, "Unsupported Socket Address Type", EXPFILL }},
		{ &ei_mojito_bigint_unsupported, { "mojito.bigint.unsupported", PI_PROTOCOL, PI_ERROR, "Unsupported BigInt length", EXPFILL }},
	};

	proto_mojito = proto_register_protocol("Mojito DHT", "Mojito", "mojito");

	proto_register_field_array(proto_mojito, hf, array_length(hf));
	proto_register_subtree_array(ett, array_length(ett));
	expert_mojito = expert_register_protocol(proto_mojito);
	expert_register_field_array(expert_mojito, ei, array_length(ei));

	mojito_handle = register_dissector("mojito", dissect_mojito, proto_mojito);
}

/* Control the handoff */
void
proto_reg_handoff_mojito(void)
{
	heur_dissector_add("udp", dissect_mojito_heuristic, "Mojito over UDP", "mojito_udp", proto_mojito, HEURISTIC_ENABLE);
	dissector_add_for_decode_as_with_preference("udp.port", mojito_handle);
}

/*
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 8
 * tab-width: 8
 * indent-tabs-mode: t
 * End:
 *
 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
 * :indentSize=8:tabSize=8:noTabs=false:
 */
