/* packet-ldss.c
 * Routines for Local Download Sharing Service dissection
 * Copyright 2009, Vasantha Crabb <vcrabb@managesoft.com.au>
 *  and Chris Adams <cadams@managesoft.com.au>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/* LDSS is a protocol for peers on a LAN to cooperatively download
 * files from a WAN. The peers ask each other about files and can
 * send files to each other, thus WAN use is minimized. However
 * if no peer possesses a file, a peer can download it via the WAN.
 * Usually the download uses HTTP, but WAN downloads are beyond
 * the scope of this dissector. To avoid saturating the WAN link,
 * peers also tell each other what they are fetching and how fast
 * they're downloading. Files are identified only by digests.
 * Broadcasts are sent via UDP and files transferred via TCP. Both
 * UDP and TCP portions of the protocol are handled in this dissector.
 */

#include "config.h"

#include <stdlib.h>
#include <math.h>

#include <epan/packet.h>
#include <epan/prefs.h>
#include <epan/expert.h>
#include <epan/strutil.h>
#include "packet-tcp.h"

/* The digest is up to 32 bytes long */
#define DIGEST_LEN 32

#define MESSAGE_ID_NEEDFILE	0
#define MESSAGE_ID_WILLSEND	1


static const value_string ldss_message_id_value[] = {
	{ MESSAGE_ID_NEEDFILE,	"Need file"	},
	{ MESSAGE_ID_WILLSEND,	"Will send"	},
	{ 0,			NULL		}
};

/* Message detail is inferred from various contents in the packet */
#define INFERRED_PEERSHUTDOWN	0
#define INFERRED_SEARCH		1
#define INFERRED_OFFER		2
#define INFERRED_PROMISE	3
#define INFERRED_WANDOWNLOAD	4
#define INFERRED_NONE		5

/* Displayed in the info column */
static const value_string ldss_inferred_info[] = {
	{ INFERRED_PEERSHUTDOWN, 	" - peer shutting down"	},
	{ INFERRED_SEARCH,		" - search"		},
	{ INFERRED_OFFER,		" - offer"		},
	{ INFERRED_PROMISE,		" - promise"		},
	{ INFERRED_WANDOWNLOAD,		" - WAN download start"	},
	{ INFERRED_NONE,		""			},
	{ 0, NULL			                        }
};

/* Displayed in the tree as a generated item */
static const value_string ldss_inferred_value[] = {
	{ INFERRED_PEERSHUTDOWN, 	"Peer shutdown"	},
	{ INFERRED_SEARCH,		"File search"	},
	{ INFERRED_OFFER,		"File offer"	},
	{ INFERRED_PROMISE,		"Promise (download in progress)" },
	{ INFERRED_WANDOWNLOAD,		"WAN download start"	},
	{ INFERRED_NONE,		""		},
	{ 0, NULL			}
};


#define DIGEST_TYPE_UNKNOWN	0
#define DIGEST_TYPE_MD5		1
#define DIGEST_TYPE_SHA1	2
#define DIGEST_TYPE_SHA256	3


static const value_string ldss_digest_type_value[] = {
	{ DIGEST_TYPE_UNKNOWN,	"Unknown"	},
	{ DIGEST_TYPE_MD5,	"MD5"		},
	{ DIGEST_TYPE_SHA1,	"SHA1"		},
	{ DIGEST_TYPE_SHA256,	"SHA256"	},
	{ 0,			NULL		}
};


#define COMPRESSION_NONE	0
#define COMPRESSION_GZIP	1


static const value_string ldss_compression_value[] = {
	{ COMPRESSION_NONE,		"None"		},
	{ COMPRESSION_GZIP,		"gzip"		},
	{ 0,			NULL		}
};

/* Info about a broadcaster */
typedef struct _ldss_broadcaster_t {
	address addr;
	guint16 port;
} ldss_broadcaster_t;

/* Info about a file */
typedef struct _ldss_file_t {
	guint8 *digest;
	guint8 digest_type;
} ldss_file_t;

/* Info about a broadcast packet */
typedef struct _ldss_broadcast_t {
	guint32 num;
	nstime_t ts;
	guint16 message_id;
	guint16 message_detail;
	guint16 port;
	guint64 size;
	guint64 offset;
	guint8 compression;
	ldss_file_t *file;
	ldss_broadcaster_t *broadcaster;
} ldss_broadcast_t;

/* Info about a file as seen in a file request */
typedef struct _ldss_file_req_t {
	guint32 num;
	nstime_t ts;
	guint64 size;
	guint64 offset;
	guint8 compression;
	ldss_file_t *file;
} ldss_file_request_t;

/* Info attached to a file transfer conversation */
typedef struct _ldss_transfer_info_t {
	guint32 resp_num;
	nstime_t resp_ts;
	/* Refers either to the file in the request (for pull)
	 * or the file in the broadcast (for push) */
	ldss_file_t *file;
	ldss_file_request_t *req;
	ldss_broadcast_t *broadcast;
} ldss_transfer_info_t;

/* Define udp_port for LDSS (IANA assigned) */
#define UDP_PORT_LDSS 6087

void proto_register_ldss(void);
void proto_reg_handoff_ldss(void);

/* Define the ldss proto */
static int	proto_ldss		= -1;

/* Define headers for ldss */
static int	hf_ldss_message_id	= -1;
static int	hf_ldss_message_detail	= -1;
static int	hf_ldss_digest_type	= -1;
static int	hf_ldss_compression	= -1;
static int	hf_ldss_cookie		= -1;
static int	hf_ldss_digest		= -1;
static int	hf_ldss_size		= -1;
static int	hf_ldss_offset		= -1;
static int	hf_ldss_target_time	= -1;
static int	hf_ldss_reserved_1	= -1;
static int	hf_ldss_port		= -1;
static int	hf_ldss_rate		= -1;
static int	hf_ldss_priority	= -1;
static int	hf_ldss_property_count	= -1;
static int	hf_ldss_properties	= -1;
static int	hf_ldss_file_data	= -1;
static int	hf_ldss_response_in	= -1;
static int	hf_ldss_response_to	= -1;
static int	hf_ldss_initiated_by	= -1;
static int	hf_ldss_transfer_response_time	= -1;
static int	hf_ldss_transfer_completed_in	= -1;

/* Define the tree for ldss */
static int ett_ldss_broadcast	 = -1;
static int ett_ldss_transfer     = -1;
static int ett_ldss_transfer_req = -1;

static expert_field ei_ldss_unrecognized_line = EI_INIT;


static dissector_handle_t	ldss_udp_handle;
static dissector_handle_t	ldss_tcp_handle;

/* Global variables associated with the preferences for ldss */
static guint	global_udp_port_ldss	= UDP_PORT_LDSS;

/* Avoid creating conversations and data twice */
static unsigned int highest_num_seen = 0;

/* When seeing a broadcast talking about an open TCP port on a host, create
 * a conversation to dissect anything sent/received at that address.  Setup
 * protocol data so the TCP dissection knows what broadcast triggered it. */
static void
prepare_ldss_transfer_conv(ldss_broadcast_t *broadcast)
{
	if (!find_conversation(broadcast->num, &broadcast->broadcaster->addr, &broadcast->broadcaster->addr,
	                       PT_TCP, broadcast->broadcaster->port, broadcast->broadcaster->port, NO_ADDR2|NO_PORT2)) {
		conversation_t *transfer_conv;
		ldss_transfer_info_t *transfer_info;

		transfer_info = wmem_new0(wmem_file_scope(), ldss_transfer_info_t);
		transfer_info->broadcast = broadcast;

		/* Preparation for later push/pull dissection */
		transfer_conv = conversation_new (broadcast->num, &broadcast->broadcaster->addr, &broadcast->broadcaster->addr,
						PT_TCP, broadcast->broadcaster->port, broadcast->broadcaster->port, NO_ADDR2|NO_PORT2);
		conversation_add_proto_data(transfer_conv, proto_ldss, transfer_info);
		conversation_set_dissector(transfer_conv, ldss_tcp_handle);
	}
}

/* Broadcasts are searches, offers or promises.
 *
 * Searches are sent by
 * a peer when it needs a file (ie. while applying its policy, when it needs
 * files such as installers to install software.)
 *
 * Each broadcast relates to one file and each file is identified only by its
 * checksum - no file names are ever used. A search times out after 10 seconds
 * (configurable) and the peer will then attempt to act on any offers by
 * downloading (via push or pull - see dissect_ldss_transfer) from those peers.
 *
 * If no offers are received, the search fails and the peer fetches the file
 * from a remote server, generally a HTTP server on the other side of a WAN.
 * The protocol exists to minimize the number of WAN downloads needed.
 *
 * While downloading from WAN the peer sends promises to inform other peers
 * when it will be available for them to download. This prevents multiple peers
 * simultaneously downloading the same file. Promises also inform other peers
 * how much download bandwidth is being used by their download. Other peers use
 * this information and the configured knowledge of the WAN bandwidth to avoid
 * saturating the WAN link, as file downloads are a non-time-critical and
 * non-business-critical network function. LDSS is intended for networks of
 * 5-20 machines connected by slow WAN link. The current implementation of the
 * protocol allows administrator to configure "time windows" when WAN usage is
 * throttled/unthrottled, though this isn't visible in LDSS.
 *
 * Once a WAN download or a LAN transfer (see below above dissect_ldss_transfer)
 * has complete the peer will offer the file to other peers on the LAN so they
 * don't need to download it themselves.
 *
 * Peers also notify when they shut down in case any other peer is waiting for
 * a file. */
static int
dissect_ldss_broadcast(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
	guint16	messageID;
	guint8 digest_type;
	guint8 compression;
	guint32 cookie;
	guint8 *digest;
	guint64	size;
	guint64	offset;
	guint32	targetTime;
	guint16 port;
	guint16	rate;
	guint16 messageDetail = INFERRED_NONE;

	proto_tree	*ti, *ldss_tree;

	const gchar *packet_type, *packet_detail;

	messageID   = tvb_get_ntohs  (tvb,  0);
	digest_type = tvb_get_guint8 (tvb,  2);
	compression = tvb_get_guint8 (tvb,  3);
	cookie      = tvb_get_ntohl  (tvb,  4);
	digest      = (guint8 *)tvb_memdup (wmem_file_scope(), tvb,  8, DIGEST_LEN);
	size	    = tvb_get_ntoh64 (tvb, 40);
	offset	    = tvb_get_ntoh64 (tvb, 48);
	targetTime  = tvb_get_ntohl  (tvb, 56);
	port        = tvb_get_ntohs  (tvb, 64);
	rate	    = tvb_get_ntohs  (tvb, 66);

	packet_type = val_to_str_const(messageID, ldss_message_id_value, "unknown");

	if (messageID == MESSAGE_ID_WILLSEND) {
		if (cookie == 0) {
			/* Shutdown: Dishonor promises from this peer. Current
			 * implementation abuses WillSend for this. */
			messageDetail = INFERRED_PEERSHUTDOWN;
		}
		else if (size == 0 && offset == 0) {
			/* NeedFile search failed - going to WAN */
			messageDetail = INFERRED_WANDOWNLOAD;
		}
		else if (size > 0) {
			/* Size is known (not always the case) */
			if (size == offset) {
				/* File is available for pull on this peer's TCP port */
				messageDetail = INFERRED_OFFER;
			}
			else {
				/* WAN download progress announcement from this peer */
				messageDetail = INFERRED_PROMISE;
			}
		}
	}
	else if (messageID == MESSAGE_ID_NEEDFILE) {
		messageDetail = INFERRED_SEARCH;
	}
	packet_detail = val_to_str_const(messageDetail, ldss_inferred_info, "unknown");

	/* Set the info column */
	col_add_fstr(pinfo->cinfo, COL_INFO, "LDSS Broadcast (%s%s)",
			     packet_type,
			     packet_detail);

	/* If we have a non-null tree (ie we are building the proto_tree
	 * instead of just filling out the columns), then give more detail. */
	ti = proto_tree_add_item(tree, proto_ldss,
			tvb, 0, (tvb_captured_length(tvb) > 72) ? tvb_captured_length(tvb) : 72, ENC_NA);
	ldss_tree = proto_item_add_subtree(ti, ett_ldss_broadcast);

	proto_tree_add_item(ldss_tree, hf_ldss_message_id,
			tvb, 0, 2, ENC_BIG_ENDIAN);
	ti = proto_tree_add_uint(ldss_tree, hf_ldss_message_detail,
			tvb, 0, 0, messageDetail);
	PROTO_ITEM_SET_GENERATED(ti);
	proto_tree_add_item(ldss_tree, hf_ldss_digest_type,
			tvb, 2,	    1,	ENC_BIG_ENDIAN);
	proto_tree_add_item(ldss_tree, hf_ldss_compression,
			tvb, 3,	    1,	ENC_BIG_ENDIAN);
	proto_tree_add_uint_format_value(ldss_tree, hf_ldss_cookie,
			tvb, 4,	    4,	FALSE,
			"0x%x%s",
			cookie,
			(cookie == 0)
			? " - shutdown (promises from this peer are no longer valid)"
			: "");
	proto_tree_add_item(ldss_tree, hf_ldss_digest,
			tvb, 8,	    DIGEST_LEN, ENC_NA);
	proto_tree_add_item(ldss_tree, hf_ldss_size,
			tvb, 40,    8,	ENC_BIG_ENDIAN);
	proto_tree_add_item(ldss_tree, hf_ldss_offset,
			tvb, 48,    8,	ENC_BIG_ENDIAN);
	proto_tree_add_uint_format_value(ldss_tree, hf_ldss_target_time,
			tvb, 56,    4,	FALSE,
			"%d:%02d:%02d",
			(int)(targetTime / 3600),
			(int)((targetTime / 60) % 60),
			(int)(targetTime % 60));
	proto_tree_add_item(ldss_tree, hf_ldss_reserved_1,
			tvb, 60,    4,	ENC_BIG_ENDIAN);
	proto_tree_add_uint_format_value(ldss_tree, hf_ldss_port,
			tvb, 64,    2,	FALSE,
			"%d%s",
			port,
			(messageID == MESSAGE_ID_WILLSEND &&
			 size > 0 &&
			 size == offset)
			? " - file can be pulled at this TCP port"
			: (messageID == MESSAGE_ID_NEEDFILE
				? " - file can be pushed to this TCP port"
				: ""));
	proto_tree_add_uint_format_value(ldss_tree, hf_ldss_rate,
			tvb, 66,    2,	FALSE,
			"%ld",
			(rate > 0)
			? (long)floor(exp(rate * G_LN2 / 2048))
			: 0);
	proto_tree_add_item(ldss_tree, hf_ldss_priority,
			tvb, 68, 2, ENC_BIG_ENDIAN);
	proto_tree_add_item(ldss_tree, hf_ldss_property_count,
			tvb, 70, 2, ENC_BIG_ENDIAN);
	if (tvb_reported_length(tvb) > 72) {
		proto_tree_add_item(ldss_tree, hf_ldss_properties,
				tvb, 72, tvb_captured_length(tvb) - 72, ENC_NA);
	}

	/* Finally, store the broadcast and register ourselves to dissect
	 * any pushes or pulls that result from this broadcast. All data
	 * is pushed/pulled over TCP using the port from the broadcast
	 * packet's port field.
	 * Track each by a TCP conversation with the remote end wildcarded.
	 * The TCP conv tracks back to a broadcast conv to determine what it
	 * is in response to.
	 *
	 * These steps only need to be done once per packet, so a variable
	 * tracks the highest frame number seen. Handles the case of first frame
	 * being frame zero. */
	if (messageDetail != INFERRED_PEERSHUTDOWN &&
	    (highest_num_seen == 0 ||
	     highest_num_seen < pinfo->num)) {

		ldss_broadcast_t *data;

		/* Populate data from the broadcast */
		data = wmem_new0(wmem_file_scope(), ldss_broadcast_t);
		data->num = pinfo->num;
		data->ts = pinfo->abs_ts;
		data->message_id = messageID;
		data->message_detail = messageDetail;
		data->port = port;
		data->size = size;
		data->offset = offset;
		data->compression = compression;

		data->file = wmem_new0(wmem_file_scope(), ldss_file_t);
		data->file->digest = digest;
		data->file->digest_type = digest_type;

		data->broadcaster = wmem_new0(wmem_file_scope(), ldss_broadcaster_t);
		copy_address(&data->broadcaster->addr, &pinfo->src);
		data->broadcaster->port = port;

		/* Dissect any future pushes/pulls */
		if (port > 0) {
			prepare_ldss_transfer_conv(data);
		}

		/* Record that the frame was processed */
		highest_num_seen = pinfo->num;
	}

	return tvb_captured_length(tvb);
}

/* Transfers happen in response to broadcasts, they are always TCP and are
 * used to send the file to the port mentioned in the broadcast. There are
 * 2 types of transfers: Pushes, which are direct responses to searches,
 * in which the peer that has the file connects to the peer that doesn't and
 * sends it, then disconnects. The other type of transfer is a pull, where
 * the peer that doesn't have the file connects to the peer that does and
 * requests it be sent.
 *
 * Pulls have a file request which identifies the desired file,
 * while pushes simply send the file. In practice this works because every
 * file the implementation sends searches for is on a different TCP port
 * on the searcher's machine. */
static int
dissect_ldss_transfer (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data)
{
	conversation_t *transfer_conv;
	ldss_transfer_info_t *transfer_info;
	struct tcpinfo *transfer_tcpinfo;
	proto_tree *ti, *line_tree = NULL, *ldss_tree = NULL;
	nstime_t broadcast_response_time;

	/* Reject the packet if data is NULL */
	if (data == NULL)
		return 0;
	transfer_tcpinfo = (struct tcpinfo *)data;

	col_set_str(pinfo->cinfo, COL_PROTOCOL, "LDSS");

	/* Look for the transfer conversation; this was created during
	 * earlier broadcast dissection (see prepare_ldss_transfer_conv) */
	transfer_conv = find_conversation (pinfo->num, &pinfo->src, &pinfo->dst,
					   PT_TCP, pinfo->srcport, pinfo->destport, 0);
	DISSECTOR_ASSERT(transfer_conv);
	transfer_info = (ldss_transfer_info_t *)conversation_get_proto_data(transfer_conv, proto_ldss);
	DISSECTOR_ASSERT(transfer_info);

	/* For a pull, the first packet in the TCP connection is the file request.
	 * First packet is identified by relative seq/ack numbers of 1.
	 * File request only appears on a pull (triggered by an offer - see above
	 * about broadcasts) */
	if (transfer_tcpinfo->seq == 1 &&
	    transfer_tcpinfo->lastackseq == 1 &&
	    transfer_info->broadcast->message_id == MESSAGE_ID_WILLSEND) {
		/* LDSS pull transfers look a lot like HTTP.
		 * Sample request:
		 * md5:01234567890123...
		 * Size: 2550
		 * Start: 0
		 * Compression: 0
		 * (remote end sends the file identified by the digest) */
		guint offset = 0;
		gboolean already_dissected = TRUE;

		col_set_str(pinfo->cinfo, COL_INFO, "LDSS File Transfer (Requesting file - pull)");

		if (highest_num_seen == 0 ||
		    highest_num_seen < pinfo->num) {

			already_dissected = FALSE;
			transfer_info->req = wmem_new0(wmem_file_scope(), ldss_file_request_t);
			transfer_info->req->file = wmem_new0(wmem_file_scope(), ldss_file_t);
			highest_num_seen = pinfo->num;
		}

		ti = proto_tree_add_item(tree, proto_ldss,
				tvb, 0, tvb_reported_length(tvb), ENC_NA);
		ldss_tree = proto_item_add_subtree(ti, ett_ldss_transfer);

		/* Populate digest data into the file struct in the request */
		transfer_info->file = transfer_info->req->file;

		/* Grab each line from the packet, there should be 4 but lets
		 * not walk off the end looking for more. */
		while (tvb_offset_exists(tvb, offset)) {
			gint next_offset;
			const guint8 *line;
			int linelen;
			gboolean is_digest_line;
			guint digest_type_len;

			linelen = tvb_find_line_end(tvb, offset, -1, &next_offset, FALSE);

			/* Include new-line in line */
			line = (guint8 *)tvb_memdup(wmem_packet_scope(), tvb, offset, linelen+1); /* XXX - memory leak? */

			line_tree = proto_tree_add_subtree(ldss_tree, tvb, offset, linelen,
							 ett_ldss_transfer_req, NULL,
							 tvb_format_text(tvb, offset, next_offset-offset));

			/* Reduce code duplication processing digest lines.
			 * There are too many locals to pass to a function - the signature
			 * looked pretty ugly when I tried! */
			is_digest_line = FALSE;

			if (strncmp(line,"md5:",4)==0) {
				is_digest_line = TRUE;
				digest_type_len = 4;
				transfer_info->file->digest_type = DIGEST_TYPE_MD5;
			}
			else if (strncmp(line, "sha1:", 5)==0) {
				is_digest_line = TRUE;
				digest_type_len = 5;
				transfer_info->file->digest_type = DIGEST_TYPE_SHA1;
			}
			else if (strncmp(line, "sha256:", 7)==0) {
				is_digest_line = TRUE;
				digest_type_len = 7;
				transfer_info->file->digest_type = DIGEST_TYPE_SHA256;
			}
			else if (strncmp(line, "unknown:", 8)==0) {
				is_digest_line = TRUE;
				digest_type_len = 8;
				transfer_info->file->digest_type = DIGEST_TYPE_UNKNOWN;
			}
			else if (strncmp(line, "Size: ", 6)==0) {
				/* Sample size line:
				 * Size: 2550\n */
				transfer_info->req->size = g_ascii_strtoull(line+6, NULL, 10);
				ti = proto_tree_add_uint64(line_tree, hf_ldss_size,
						tvb, offset+6, linelen-6, transfer_info->req->size);
				PROTO_ITEM_SET_GENERATED(ti);
			}
			else if (strncmp(line, "Start: ", 7)==0) {
				/* Sample offset line:
				 * Start: 0\n */
				transfer_info->req->offset = g_ascii_strtoull(line+7, NULL, 10);
				ti = proto_tree_add_uint64(line_tree, hf_ldss_offset,
						tvb, offset+7, linelen-7, transfer_info->req->offset);
				PROTO_ITEM_SET_GENERATED(ti);
			}
			else if (strncmp(line, "Compression: ", 13)==0) {
				/* Sample compression line:
				 * Compression: 0\n */
				transfer_info->req->compression = (gint8)strtol(line+13, NULL, 10); /* XXX - bad cast */
				ti = proto_tree_add_uint(line_tree, hf_ldss_compression,
						tvb, offset+13, linelen-13, transfer_info->req->compression);
				PROTO_ITEM_SET_GENERATED(ti);
			}
			else {
				proto_tree_add_expert(line_tree, pinfo, &ei_ldss_unrecognized_line, tvb, offset, linelen);
			}

			if (is_digest_line) {
				proto_item *tii = NULL;

				/* Sample digest-type/digest line:
				 * md5:0123456789ABCDEF\n */
				if (!already_dissected) {
					GByteArray *digest_bytes;

					digest_bytes = g_byte_array_new();
					hex_str_to_bytes(
							tvb_get_ptr(tvb, offset+digest_type_len, linelen-digest_type_len),
							digest_bytes, FALSE);

					if(digest_bytes->len >= DIGEST_LEN)
						digest_bytes->len = (DIGEST_LEN-1);
					/* Ensure the digest is zero-padded */
					transfer_info->file->digest = (guint8 *)wmem_alloc0(wmem_file_scope(), DIGEST_LEN);
					memcpy(transfer_info->file->digest, digest_bytes->data, digest_bytes->len);

					g_byte_array_free(digest_bytes, TRUE);
				}

				tii = proto_tree_add_uint(line_tree, hf_ldss_digest_type,
						tvb, offset, digest_type_len, transfer_info->file->digest_type);
				PROTO_ITEM_SET_GENERATED(tii);
				tii = proto_tree_add_bytes(line_tree, hf_ldss_digest,
						tvb, offset+digest_type_len, MIN(linelen-digest_type_len, DIGEST_LEN),
						transfer_info->file->digest);
				PROTO_ITEM_SET_GENERATED(tii);
			}

			offset = next_offset;
		}

		/* Link forwards to the response for this pull. */
		if (transfer_info->resp_num != 0) {
			ti = proto_tree_add_uint(ldss_tree, hf_ldss_response_in,
						 tvb, 0, 0, transfer_info->resp_num);
			PROTO_ITEM_SET_GENERATED(ti);
		}

		transfer_info->req->num = pinfo->num;
		transfer_info->req->ts = pinfo->abs_ts;
	}
	/* Remaining packets are the file response */
	else {
		guint64 size;
		guint64 offset;
		guint8 compression;

		/* size, digest, compression come from the file request for a pull but
		 * they come from the broadcast for a push. Pushes don't bother
		 * with a file request - they just send the data. We have to get file
		 * info from the offer broadcast which triggered this transfer.
		 * If we cannot find the file request, default to the broadcast. */
		if (transfer_info->broadcast->message_id == MESSAGE_ID_WILLSEND &&
		    transfer_info->req != NULL) {
			transfer_info->file = transfer_info->req->file;
			size = transfer_info->req->size;
			offset = transfer_info->req->offset;
			compression = transfer_info->req->compression;
		}
		else {
			transfer_info->file = transfer_info->broadcast->file;
			size = transfer_info->broadcast->size;
			offset = transfer_info->broadcast->offset;
			compression = transfer_info->broadcast->compression;
		}

		/* Remaining data in this TCP connection is all file data.
		 * Always desegment if the size is 0 (ie. unknown)
		 */
		if (pinfo->can_desegment) {
			if (size == 0 || tvb_captured_length(tvb) < size) {
				pinfo->desegment_offset = 0;
				pinfo->desegment_len = DESEGMENT_UNTIL_FIN;
				return -1;
			}
		}

		/* OK. Now we have the whole file that was transferred. */
		transfer_info->resp_num = pinfo->num;
		transfer_info->resp_ts = pinfo->abs_ts;

		col_add_fstr(pinfo->cinfo, COL_INFO, "LDSS File Transfer (Sending file - %s)",
				     transfer_info->broadcast->message_id == MESSAGE_ID_WILLSEND
				     ? "pull"
				     : "push");

		ti = proto_tree_add_item(tree, proto_ldss,
				tvb, 0, tvb_reported_length(tvb), ENC_NA);
		ldss_tree = proto_item_add_subtree(ti, ett_ldss_transfer);
		proto_tree_add_bytes_format(ldss_tree, hf_ldss_file_data,
				tvb, 0, tvb_captured_length(tvb), NULL,
				compression == COMPRESSION_GZIP
				? "Gzip compressed data: %d bytes"
				: "File data: %d bytes",
				tvb_captured_length(tvb));
#ifdef HAVE_ZLIB
		/* Be nice and uncompress the file data. */
		if (compression == COMPRESSION_GZIP) {
			tvbuff_t *uncomp_tvb;
			uncomp_tvb = tvb_child_uncompress(tvb, tvb, 0, tvb_captured_length(tvb));
			if (uncomp_tvb != NULL) {
				/* XXX: Maybe not a good idea to add a data_source for
				   what may very well be a large buffer since then
				   the full uncompressed buffer will be shown in a tab
				   in the hex bytes pane ?
				   However, if we don't, bytes in an unrelated tab will
				   be highlighted.
				 */
				add_new_data_source(pinfo, uncomp_tvb, "Uncompressed Data");
				proto_tree_add_bytes_format_value(ldss_tree, hf_ldss_file_data,
						uncomp_tvb, 0, tvb_captured_length(uncomp_tvb),
						NULL, "Uncompressed data: %d bytes",
						tvb_captured_length(uncomp_tvb));
			}
		}
#endif
		ti = proto_tree_add_uint(ldss_tree, hf_ldss_digest_type,
				tvb, 0, 0, transfer_info->file->digest_type);
		PROTO_ITEM_SET_GENERATED(ti);
		if (transfer_info->file->digest != NULL) {
			/* This is ugly. You can't add bytes of nonzero length and have
			 * filtering work correctly unless you give a valid location in
			 * the packet. This hack pretends the first 32 bytes of the packet
			 * are the digest, which they aren't: they're actually the first 32
			 * bytes of the file that was sent. */
			ti = proto_tree_add_bytes(ldss_tree, hf_ldss_digest,
					tvb, 0, DIGEST_LEN, transfer_info->file->digest);
		}
		PROTO_ITEM_SET_GENERATED(ti);
		ti = proto_tree_add_uint64(ldss_tree, hf_ldss_size,
				tvb, 0, 0, size);
		PROTO_ITEM_SET_GENERATED(ti);
		ti = proto_tree_add_uint64(ldss_tree, hf_ldss_offset,
				tvb, 0, 0, offset);
		PROTO_ITEM_SET_GENERATED(ti);
		ti = proto_tree_add_uint(ldss_tree, hf_ldss_compression,
				tvb, 0, 0, compression);
		PROTO_ITEM_SET_GENERATED(ti);
		/* Link to the request for a pull. */
		if (transfer_info->broadcast->message_id == MESSAGE_ID_WILLSEND &&
				transfer_info->req != NULL &&
				transfer_info->req->num != 0) {
			ti = proto_tree_add_uint(ldss_tree, hf_ldss_response_to,
					tvb, 0, 0, transfer_info->req->num);
			PROTO_ITEM_SET_GENERATED(ti);
		}
	}

	/* Print the pull response time */
	if (transfer_info->broadcast->message_id == MESSAGE_ID_WILLSEND &&
	    transfer_info->req != NULL &&
	    transfer_info->resp_num != 0) {
		nstime_t pull_response_time;
		nstime_delta(&pull_response_time, &transfer_info->resp_ts,
			     &transfer_info->req->ts);
		ti = proto_tree_add_time(ldss_tree, hf_ldss_transfer_response_time,
					 tvb, 0, 0, &pull_response_time);
		PROTO_ITEM_SET_GENERATED(ti);
	}

	/* Link the transfer back to the initiating broadcast. Response time is
	 * calculated as the time from broadcast to completed transfer. */
	ti = proto_tree_add_uint(ldss_tree, hf_ldss_initiated_by,
				 tvb, 0, 0, transfer_info->broadcast->num);
	PROTO_ITEM_SET_GENERATED(ti);

	if (transfer_info->resp_num != 0) {
		nstime_delta(&broadcast_response_time, &transfer_info->resp_ts,
			     &transfer_info->broadcast->ts);
		ti = proto_tree_add_time(ldss_tree, hf_ldss_transfer_completed_in,
					 tvb, 0, 0, &broadcast_response_time);
		PROTO_ITEM_SET_GENERATED(ti);
	}

	/* This conv got its addr2/port2 set by the TCP dissector because a TCP
	 * connection was established. Make a new one to handle future connections
	 * to the addr/port mentioned in the broadcast, because that socket is
	 * still open. */
	if (transfer_tcpinfo->seq == 1 &&
	    transfer_tcpinfo->lastackseq == 1) {

		prepare_ldss_transfer_conv(transfer_info->broadcast);
	}

	return tvb_captured_length(tvb);
}

static gboolean
is_broadcast(address* addr)
{
	static address broadcast_addr;
	static const guint8 broadcast_addr_bytes[6] = {
		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
	};

	set_address(&broadcast_addr, AT_ETHER, 6, broadcast_addr_bytes);
	return addresses_equal(addr, &broadcast_addr);
}

static int
dissect_ldss (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
	if (is_broadcast(&pinfo->dl_dst)) {

		col_set_str(pinfo->cinfo, COL_PROTOCOL, "LDSS");
		return dissect_ldss_broadcast(tvb, pinfo, tree);
	}

	/* Definitely not LDSS */
	return 0;
}

/* Initialize the highest num seen each time a
 * new file is loaded or re-loaded in wireshark */
static void
ldss_init_protocol(void)
{
	/* We haven't dissected anything yet. */
	highest_num_seen = 0;
}

void
proto_register_ldss (void) {
	static hf_register_info hf[] =	{
		{   &hf_ldss_message_id,
		    {	"LDSS Message ID",
			"ldss.message_id",
			FT_UINT16, BASE_DEC, VALS(ldss_message_id_value), 0x0,
			NULL, HFILL
		    }
		},
		{   &hf_ldss_message_detail,
		    {	"Inferred meaning",
			"ldss.inferred_meaning",
			FT_UINT16, BASE_DEC, VALS(ldss_inferred_value), 0x0,
			"Inferred meaning of the packet", HFILL
		    }
		},
		{   &hf_ldss_digest_type,
		    {	"Digest Type",
			"ldss.digest_type",
			FT_UINT8, BASE_DEC, VALS(ldss_digest_type_value), 0x0,
			NULL, HFILL
		    }
		},
		{   &hf_ldss_compression,
		    {	"Compressed Format",
			"ldss.compression",
			FT_UINT8, BASE_DEC, VALS(ldss_compression_value), 0x0,
			NULL, HFILL
		    }
		},
		{   &hf_ldss_cookie,
		    {	"Cookie",
			"ldss.cookie",
			FT_UINT32, BASE_HEX, NULL, 0x0,
			"Random value used for duplicate rejection", HFILL
		    }
		},
		{   &hf_ldss_digest,
		    {	"Digest",
			"ldss.digest",
			FT_BYTES, BASE_NONE, NULL, 0x0,
			"Digest of file padded with 0x00", HFILL
		    }
		},
		{   &hf_ldss_size,
		    {	"Size",
			"ldss.size",
			FT_UINT64, BASE_DEC, NULL, 0x0,
			"Size of complete file", HFILL
		    }
		},
		{   &hf_ldss_offset,
		    {	"Offset",
			"ldss.offset",
			FT_UINT64, BASE_DEC, NULL, 0x0,
			"Size of currently available portion of file", HFILL
		    }
		},
		{   &hf_ldss_target_time,
		    {	"Target time (relative)",
			"ldss.target_time",
			FT_UINT32, BASE_DEC, NULL, 0x0,
			"Time until file will be needed/available", HFILL
		    }
		},
		{   &hf_ldss_reserved_1,
		    {	"Reserved",
			"ldss.reserved_1",
			FT_UINT32, BASE_HEX, NULL, 0x0,
			"Unused field - should be 0x00000000", HFILL
		    }
		},
		{   &hf_ldss_port,
		    {	"Port",
			"ldss.port",
			FT_UINT16, BASE_DEC, NULL, 0x0,
			"TCP port for push (Need file) or pull (Will send)", HFILL
		    }
		},
		{   &hf_ldss_rate,
		    {	"Rate (B/s)",
			"ldss.rate",
			FT_UINT16, BASE_DEC, NULL, 0x0,
			"Estimated current download rate", HFILL
		    }
		},
		{   &hf_ldss_priority,
		    {	"Priority",
			"ldss.priority",
			FT_UINT16, BASE_DEC, NULL, 0x0,
			NULL, HFILL
		    }
		},
		{   &hf_ldss_property_count,
		    {	"Property Count",
			"ldss.property_count",
			FT_UINT16, BASE_DEC, NULL, 0x0,
			NULL, HFILL
		    }
		},
		{   &hf_ldss_properties,
		    {	"Properties",
			"ldss.properties",
			FT_BYTES, BASE_NONE, NULL, 0x0,
			NULL, HFILL
		    }
		},
		{   &hf_ldss_file_data,
		    {	"File data",
			"ldss.file_data",
			FT_BYTES, BASE_NONE, NULL, 0x0,
			NULL, HFILL
		    }
		},
		{   &hf_ldss_response_in,
		    { "Response In",
		      "ldss.response_in",
		      FT_FRAMENUM, BASE_NONE, NULL, 0x0,
		      "The response to this file pull request is in this frame", HFILL }
		},
		{   &hf_ldss_response_to,
		    { "Request In",
		      "ldss.response_to",
		      FT_FRAMENUM, BASE_NONE, NULL, 0x0,
		      "This is a response to the file pull request in this frame", HFILL }
		},
		{   &hf_ldss_initiated_by,
		    { "Initiated by",
		      "ldss.initiated_by",
		      FT_FRAMENUM, BASE_NONE, NULL, 0x0,
		      "The broadcast that initiated this file transfer", HFILL }
		},
		{   &hf_ldss_transfer_response_time,
		    { "Transfer response time",
		      "ldss.transfer_response_time",
		      FT_RELATIVE_TIME, BASE_NONE, NULL, 0x0,
		      "The time between the request and the response for a pull transfer", HFILL }
		},
		{   &hf_ldss_transfer_completed_in,
		    { "Transfer completed in",
		      "ldss.transfer_completed_in",
		      FT_RELATIVE_TIME, BASE_NONE, NULL, 0x0,
		      "The time between requesting the file and completion of the file transfer", HFILL }
		}
	};

	static gint  *ett[] = { &ett_ldss_broadcast, &ett_ldss_transfer, &ett_ldss_transfer_req };

	static ei_register_info ei[] = {
		{ &ei_ldss_unrecognized_line, { "ldss.unrecognized_line", PI_PROTOCOL, PI_WARN, "Unrecognized line ignored", EXPFILL }},
	};

	module_t     *ldss_module;
	expert_module_t* expert_ldss;

	proto_ldss = proto_register_protocol("Local Download Sharing Service", "LDSS", "ldss");
	proto_register_field_array(proto_ldss, hf, array_length(hf));
	proto_register_subtree_array(ett, array_length(ett));
	expert_ldss = expert_register_protocol(proto_ldss);
	expert_register_field_array(expert_ldss, ei, array_length(ei));

	ldss_module = prefs_register_protocol(	proto_ldss, proto_reg_handoff_ldss);
	prefs_register_uint_preference(		ldss_module, "udp_port",
						"LDSS UDP Port",
						"The UDP port on which "
						"Local Download Sharing Service "
						"broadcasts will be sent",
						10, &global_udp_port_ldss);

	register_init_routine(&ldss_init_protocol);
}


/* The registration hand-off routine */
void
proto_reg_handoff_ldss (void)
{
	static guint	  saved_udp_port_ldss;
	static gboolean	  ldss_initialized	= FALSE;

	if (!ldss_initialized) {
		ldss_udp_handle = create_dissector_handle(dissect_ldss, proto_ldss);
		ldss_tcp_handle = create_dissector_handle(dissect_ldss_transfer, proto_ldss);
		ldss_initialized = TRUE;
	}
	else {
		dissector_delete_uint("udp.port", saved_udp_port_ldss, ldss_udp_handle);
	}
	dissector_add_uint("udp.port", global_udp_port_ldss, ldss_udp_handle);
	saved_udp_port_ldss = global_udp_port_ldss;
}

/*
 * Editor modelines  -  http://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:
 */
