/* packet-ltp.c
 * Routines for LTP dissection
 * Copyright 2009, Mithun Roy <mithunroy13@gmail.com>
 * Copyright 2017, Krishnamurthy Mayya <krishnamurthymayya@gmail.com>
     Revision: Minor modifications to Header and Trailer extensions
               by correcting the offset handling.
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

/*
 *    Modifications were made to this file under designation MFS-33289-1 and
 *    are Copyright 2015 United States Government as represented by NASA
 *       Marshall Space Flight Center. All Rights Reserved.
 *
 *    Released under the GNU GPL with NASA legal approval granted 2016-06-10.
 *
 *    The subject software is provided "AS IS" WITHOUT ANY WARRANTY of any kind,
 *    either expressed, implied or statutory and this agreement does not,
 *    in any manner, constitute an endorsement by government agency of any
 *    results, designs or products resulting from use of the subject software.
 *    See the Agreement for the specific language governing permissions and
 *    limitations.
 */

/*
 * Licklider Transmission Protocol - RFC 5326.
 */

#include "config.h"

#include <epan/packet.h>
#include <epan/expert.h>
#include <epan/conversation.h>
#include <epan/conversation_table.h>
#include <epan/conversation_filter.h>
#include <epan/proto_data.h>
#include <epan/reassemble.h>
#include <epan/stats_tree.h>
#include <epan/to_str.h>
#include <wsutil/wmem/wmem_map.h>
#include <wsutil/wmem/wmem_interval_tree.h>

void proto_register_ltp(void);
void proto_reg_handoff_ltp(void);

static dissector_handle_t ltp_handle;

#define LTP_MIN_DATA_BUFFER  5

/// Unique session identifier
typedef struct {
	/// Session originator
	guint64 orig_eng_id;
	/// Session number
	guint64 sess_num;
} ltp_session_id_t;

/** Function to match the GHashFunc signature.
 */
static guint
ltp_session_id_hash(gconstpointer ptr)
{
	const ltp_session_id_t *obj = ptr;
	return (
		g_int64_hash(&(obj->orig_eng_id))
		^ g_int64_hash(&(obj->sess_num))
	);
}

/** Function to match the GEqualFunc signature.
 */
static gboolean
ltp_session_id_equal(gconstpointer a, gconstpointer b)
{
	const ltp_session_id_t *aobj = a;
	const ltp_session_id_t *bobj = b;
	return (
		(aobj->orig_eng_id == bobj->orig_eng_id)
		&& (aobj->sess_num == bobj->sess_num)
	);
}

/// Reassembly function
static gpointer
ltp_session_new_key(const packet_info *pinfo _U_, const guint32 id _U_,
		const void *data)
{
	const ltp_session_id_t *obj = data;
	ltp_session_id_t *key = g_slice_new(ltp_session_id_t);

	key->orig_eng_id = obj->orig_eng_id;
	key->sess_num = obj->sess_num;

	return (gpointer)key;
}

/// Reassembly function
static void
ltp_session_free_key(gpointer ptr)
{
	ltp_session_id_t *key = (ltp_session_id_t *)ptr;
	g_slice_free(ltp_session_id_t, key);
}

typedef struct {
	guint32 frame_num;
	nstime_t abs_ts;
} ltp_frame_info_t;

static ltp_frame_info_t *
ltp_frame_info_new(const packet_info *pinfo)
{
	ltp_frame_info_t *obj = wmem_new(wmem_file_scope(), ltp_frame_info_t);
	obj->frame_num = pinfo->num;
	obj->abs_ts = pinfo->abs_ts;
	return obj;
}

/** Function to match the GCompareFunc signature.
 */
static gint
ltp_frame_info_find_pinfo(gconstpointer a, gconstpointer b)
{
	const ltp_frame_info_t *aobj = a;
	const packet_info *bobj = b;
	if (aobj->frame_num < bobj->num) return -1;
	if (aobj->frame_num > bobj->num) return 1;
	return 0;
}

/// A session is an LTP conversation
typedef struct {
	/** Map from first-seen segment data ranges to data frame info (ltp_frame_info_t*) */
	wmem_itree_t *data_segs;
	/** Map from report ID (guint64) to tree (wmem_itree_t*) of
	 * first-seen segment data ranges to data frame info (ltp_frame_info_t*) */
	wmem_map_t *rpt_segs;
	/** Set after seeing EORP */
	guint64 *red_size;
	/** Set after seeing EOB */
	guint64 *block_size;

	/** Map from checkpoint ID (guint64) to wmem_list_t of frame info (ltp_frame_info_t*) */
	wmem_map_t *checkpoints;
	/** Map from checkpoint ID (guint64) to wmem_list_t of frame info (ltp_frame_info_t*) */
	wmem_map_t *chkp_acks;
	/** Map from report ID (guint64) to wmem_list_t of frame info (ltp_frame_info_t*) */
	wmem_map_t *reports;
	/** Map from report ID (guint64) to wmem_list_t of frame info (ltp_frame_info_t*) */
	wmem_map_t *rpt_acks;
	/** Map from report ID (guint64) to wmem_list_t of frame info (ltp_frame_info_t*) */
	wmem_map_t *rpt_datas;
} ltp_session_data_t;

/** Add a cross-reference value source.
 * @param map The map to add to.
 * @param ref_num The cross-reference value.
 * @param pinfo The source frame of the value.
 */
static void
ltp_ref_src(wmem_map_t *map, guint64 ref_num, const packet_info *pinfo)
{
	wmem_list_t *found = wmem_map_lookup(map, &ref_num);
	if (!found)
	{
		guint64 *key = wmem_new(wmem_file_scope(), guint64);
		*key = ref_num;
		found = wmem_list_new(wmem_file_scope());
		wmem_map_insert(map, key, found);
	}

	if (wmem_list_find_custom(found, pinfo, ltp_frame_info_find_pinfo))
	{
		return;
	}
	ltp_frame_info_t *val = ltp_frame_info_new(pinfo);
	wmem_list_append(found, val);
}

/** Show cross-reference value sources as tree items.
 * @param map The map to search in.
 * @param ref_num The cross-reference value.
 * @param pinfo The frame using the reference (to avoid duplicates).
 * @param tree The tree to show references under.
 * @param hf_ref The field index to add source frame numbers.
 * @param hf_time The field index to report time differences.
 */
static void
ltp_ref_use(wmem_map_t *map, guint64 ref_num, const packet_info *pinfo, proto_tree *tree, int hf_ref, int hf_time)
{
	const wmem_list_t *found = wmem_map_lookup(map, &ref_num);
	if (!found)
	{
		return;
	}

	for (wmem_list_frame_t *it = wmem_list_head(found); it != NULL;
		it = wmem_list_frame_next(it))
	{
		const ltp_frame_info_t *frame_refd = wmem_list_frame_data(it);
		if (frame_refd->frame_num == pinfo->num)
		{
			continue;
		}
		PROTO_ITEM_SET_GENERATED(
			proto_tree_add_uint(tree, hf_ref, NULL, 0, 0, frame_refd->frame_num)
		);

		if (hf_time >= 0)
		{
			nstime_t td;
			nstime_delta(&td, &(pinfo->abs_ts), &(frame_refd->abs_ts));
			PROTO_ITEM_SET_GENERATED(
					proto_tree_add_time(tree, hf_time, NULL, 0, 0, &td)
			);
		}
	}
}

/// Tap info for single segment
typedef struct {
	/// Associated session context (optional)
	ltp_session_data_t *session;
	/// Segment type
	guint8 seg_type;
	/// Session ID
	ltp_session_id_t sess_id;
	/// Text form of session name, scoped to file
	const char *sess_name;
	/// Full segment size
	guint seg_size;
	/// If non-zero, the size of the contained block
	guint block_size;
	/// For red data segment or report, is this original
	gboolean corr_orig;
} ltp_tap_info_t;

/* For reassembling LTP segments */
static reassembly_table ltp_reassembly_table;

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

static int ltp_tap = -1;

static gboolean ltp_reassemble_block = TRUE;
static gboolean ltp_analyze_sequence = TRUE;

/* LTP Header variables */
static int hf_ltp_version       = -1;
static int hf_ltp_type          = -1;
static int hf_ltp_session_name    = -1;
static int hf_ltp_session_orig  = -1;
static int hf_ltp_session_no    = -1;
static int hf_ltp_hdr_extn_cnt  = -1;
static int hf_ltp_trl_extn_cnt  = -1;

/* LTP Data Segment variable */
static int hf_ltp_data_clid     = -1;
static int hf_ltp_data_offset   = -1;
static int hf_ltp_data_length   = -1;
static int hf_ltp_data_chkp     = -1;
static int hf_ltp_data_chkp_rpt = -1;
static int hf_ltp_data_rpt      = -1;
static int hf_ltp_data_rpt_ref  = -1;
static int hf_ltp_data_rpt_time = -1;
static int hf_ltp_data_sda_clid = -1;
static int hf_ltp_partial_packet = -1;
static int hf_ltp_data_clidata  = -1;
static int hf_ltp_data_retrans  = -1;
static int hf_ltp_data_clm_rpt  = -1;
static int hf_ltp_block_red_size = -1;
static int hf_ltp_block_green_size = -1;
static int hf_ltp_block_bundle_size = -1;
static int hf_ltp_block_bundle_cnt = -1;

/* LTP Report Segment variable */
static int hf_ltp_rpt_sno       = -1;
static int hf_ltp_rpt_sno_ack   = -1;
static int hf_ltp_rpt_sno_data  = -1;
static int hf_ltp_rpt_chkp      = -1;
static int hf_ltp_rpt_chkp_ref  = -1;
static int hf_ltp_rpt_chkp_time = -1;
static int hf_ltp_rpt_ub        = -1;
static int hf_ltp_rpt_lb        = -1;
static int hf_ltp_rpt_len       = -1;
static int hf_ltp_rpt_retrans   = -1;
static int hf_ltp_rpt_clm_cnt   = -1;
static int hf_ltp_rpt_clm_off   = -1;
static int hf_ltp_rpt_clm_len   = -1;
static int hf_ltp_rpt_clm_fst   = -1;
static int hf_ltp_rpt_clm_lst   = -1;
static int hf_ltp_rpt_clm_ref   = -1;
static int hf_ltp_rpt_gap       = -1;
static int hf_ltp_rpt_gap_ref   = -1;
static int hf_ltp_rpt_gap_total = -1;

/* LTP Report Ack Segment Variable */
static int hf_ltp_rpt_ack_sno   = -1;
static int hf_ltp_rpt_ack_dupe  = -1;
static int hf_ltp_rpt_ack_ref   = -1;
static int hf_ltp_rpt_ack_time   = -1;
static int hf_ltp_cancel_ack    = -1;

/* LTP Session Management Segment Variable */
static int hf_ltp_cancel_code   = -1;

/* LTP Header Extension Segment */
static int hf_ltp_hdr_extn_tag  = -1;
static int hf_ltp_hdr_extn_len  = -1;
static int hf_ltp_hdr_extn_val  = -1;

/* LTP Trailer Extension Segment */
static int hf_ltp_trl_extn_tag  = -1;
static int hf_ltp_trl_extn_len  = -1;
static int hf_ltp_trl_extn_val  = -1;

/*LTP reassembly */
static int hf_ltp_fragments = -1;
static int hf_ltp_fragment = -1;
static int hf_ltp_fragment_overlap = -1;
static int hf_ltp_fragment_overlap_conflicts = -1;
static int hf_ltp_fragment_multiple_tails = -1;
static int hf_ltp_fragment_too_long_fragment = -1;
static int hf_ltp_fragment_error = -1;
static int hf_ltp_fragment_count = -1;
static int hf_ltp_reassembled_in = -1;
static int hf_ltp_reassembled_length = -1;

static expert_field ei_ltp_mal_reception_claim = EI_INIT;
static expert_field ei_ltp_sdnv_length = EI_INIT;
static expert_field ei_ltp_sno_larger_than_ccsds = EI_INIT;
static expert_field ei_ltp_report_async = EI_INIT;

static dissector_handle_t bundle_handle;

static const value_string ltp_type_codes[] = {
	{0x0, "Red data, NOT {Checkpoint, EORP or EOB}"},
	{0x1, "Red data, Checkpoint, NOT {EORP or EOB}"},
	{0x2, "Red data, Checkpoint, EORP, NOT EOB"},
	{0x3, "Red data, Checkpoint, EORP, EOB"},
	{0x4, "Green data, NOT EOB"},
	{0x5, "Green data, undefined"},
	{0x6, "Green data, undefined"},
	{0x7, "Green data, EOB"},
	{0x8, "Report segment"},
	{0x9, "Report-acknowledgment segment"},
	{0xa, "Control segment, undefined"},
	{0xb, "Control segment, undefined"},
	{0xc, "Cancel segment from block sender"},
	{0xd, "Cancel-acknowledgment segment to block sender"},
	{0xe, "Cancel segment from block receiver"},
	{0xf, "Cancel-acknowledgment segment to block receiver"},
	{0,NULL}
};

static const value_string ltp_type_col_info[] = {
	{0x0, "Red data"},
	{0x1, "Red data"},
	{0x2, "Red data"},
	{0x3, "Red data"},
	{0x4, "Green data"},
	{0x5, "Green data"},
	{0x6, "Green data"},
	{0x7, "Green data"},
	{0x8, "Report segment"},
	{0x9, "Report ack segment"},
	{0xa, "Control segment"},
	{0xb, "Control segment"},
	{0xc, "Cancel segment"},
	{0xd, "Cancel ack segment"},
	{0xe, "Cancel segment"},
	{0xf, "Cancel ack segment"},
	{0, NULL}
};

static const value_string ltp_cancel_codes[] = {
	{0x00, "Client service canceled session"},
	{0x01, "Unreachable client service"},
	{0x02, "Retransmission limit exceeded"},
	{0x03, "Miscolored segment"},
	{0x04, "A system error"},
	{0x05, "Exceeded the Retransmission-Cycles limit"},
	{0, NULL}
};

static const value_string extn_tag_codes[] = {
	{0x00, "LTP authentication extension"},
	{0x01, "LTP cookie extension"},
	{0, NULL}
};

static const val64_string client_service_id_info[] = {
	{0x01, "Bundle Protocol"},
	{0x02, "CCSDS LTP Service Data Aggregation"},
	{0, NULL}
};

#define LTP_PORT    1113

/* Initialize the subtree pointers */
static gint ett_ltp             = -1;
static gint ett_ltp_hdr         = -1;
static gint ett_hdr_session     = -1;
static gint ett_hdr_extn        = -1;
static gint ett_frame_ref       = -1;
static gint ett_data_segm       = -1;
static gint ett_block           = -1;
static gint ett_rpt_segm        = -1;
static gint ett_rpt_clm         = -1;
static gint ett_rpt_gap         = -1;
static gint ett_rpt_ack_segm    = -1;
static gint ett_session_mgmt    = -1;
static gint ett_trl_extn        = -1;
static gint ett_ltp_fragment	= -1;
static gint ett_ltp_fragments	= -1;

static const fragment_items ltp_frag_items = {
	/*Fragment subtrees*/
	&ett_ltp_fragment,
	&ett_ltp_fragments,
	/*Fragment Fields*/
	&hf_ltp_fragments,
	&hf_ltp_fragment,
	&hf_ltp_fragment_overlap,
	&hf_ltp_fragment_overlap_conflicts,
	&hf_ltp_fragment_multiple_tails,
	&hf_ltp_fragment_too_long_fragment,
	&hf_ltp_fragment_error,
	&hf_ltp_fragment_count,
	/*Reassembled in field*/
	&hf_ltp_reassembled_in,
	/*Reassembled length field*/
	&hf_ltp_reassembled_length,
	/* Reassembled data field */
	NULL,
	/*Tag*/
	"LTP fragments"
};

static proto_item *
add_sdnv64_to_tree(proto_tree *tree, tvbuff_t *tvb, packet_info* pinfo, int offset, int hf_sdnv, guint64 *retval, gint *lenretval)
{
	proto_item *ti;
	ti = proto_tree_add_item_ret_varint(tree, hf_sdnv, tvb, offset, -1, ENC_VARINT_SDNV, retval, lenretval);

	if (*lenretval <= 0) {
		expert_add_info(pinfo, ti, &ei_ltp_sdnv_length);
	}
	return ti;
}

/// Summary of a data segment tree item
typedef struct {
	/// Data segment packet info
	packet_info *pinfo;
	/// Tree of the data segment
	proto_tree *ltp_data_tree;
	/// The first offset of this segment
	guint64 data_fst;
	/// The last offset of this segment
	guint64 data_lst;
} ltp_data_seg_info_t;

static void
ltp_data_seg_find_report(gpointer key _U_, gpointer value, gpointer user_data)
{
	wmem_itree_t *rpt_clms = value;
	const ltp_data_seg_info_t *data_seg = user_data;
	if (!(data_seg->data_fst <= data_seg->data_lst))
	{
		return;
	}

	wmem_list_t *found = wmem_itree_find_intervals(rpt_clms, wmem_packet_scope(), data_seg->data_fst, data_seg->data_lst);
	for (wmem_list_frame_t *it = wmem_list_head(found); it != NULL;
		it = wmem_list_frame_next(it))
	{
		const ltp_frame_info_t *frame = wmem_list_frame_data(it);
		// report must be after this data segment
		if (frame->frame_num < data_seg->pinfo->num)
		{
			continue;
		}
		PROTO_ITEM_SET_GENERATED(
			proto_tree_add_uint(data_seg->ltp_data_tree, hf_ltp_data_clm_rpt, NULL, 0, 0, frame->frame_num)
		);
	}

}

static int
dissect_data_segment(proto_tree *ltp_tree, tvbuff_t *tvb,packet_info *pinfo,int frame_offset,int ltp_type,
		int *data_len, ltp_tap_info_t *tap)
{
	ltp_session_data_t *session = tap->session;
	guint64 client_id;
	guint64 data_offset;
	guint64 data_length;
	guint64 chkp_sno = 0;
	guint64 rpt_sno = 0;
	guint64 sda_client_id = 0;

	unsigned segment_size = 0;

	int sdnv_length;

	proto_tree *ltp_data_tree;
	proto_item *ti;

	fragment_head *frag_msg = NULL;

	tvbuff_t *new_tvb = NULL;

	/* Create a subtree for data segment and add the other fields under it */
	ltp_data_tree = proto_tree_add_subtree(ltp_tree, tvb, frame_offset, tvb_captured_length_remaining(tvb, frame_offset), ett_data_segm, NULL, "Data Segment");

	/* Client ID - 0 = Bundle Protocol, 1 = CCSDS LTP Service Data Aggregation */
	add_sdnv64_to_tree(ltp_data_tree, tvb, pinfo, frame_offset, hf_ltp_data_clid, &client_id, &sdnv_length);
	frame_offset += sdnv_length;
	segment_size += sdnv_length;

	/* data segment offset */
	add_sdnv64_to_tree(ltp_data_tree, tvb, pinfo, frame_offset, hf_ltp_data_offset, &data_offset, &sdnv_length);
	if (sdnv_length > 0) {
		frame_offset += sdnv_length;
		segment_size += sdnv_length;
	} else {
		return 0;
	}

	/* data segment length */
	add_sdnv64_to_tree(ltp_data_tree, tvb, pinfo, frame_offset, hf_ltp_data_length, &data_length, &sdnv_length);
	if (sdnv_length > 0) {
		frame_offset += sdnv_length;
		segment_size += sdnv_length;

		/* add in the data length also */
		segment_size += (unsigned int) data_length;
	} else {
		return 0;
	}
	*data_len = (int) data_length;

	const guint64 data_fst = data_offset;
	const guint64 data_lst = data_offset + data_length - 1;
	gboolean newdata = TRUE;
	if (ltp_analyze_sequence && session)
	{
		if (data_fst <= data_lst)
		{
			wmem_list_t *found = wmem_itree_find_intervals(session->data_segs, wmem_packet_scope(), data_fst, data_lst);
			for (wmem_list_frame_t *it = wmem_list_head(found); it != NULL;
				it = wmem_list_frame_next(it))
			{
				const ltp_frame_info_t *frame = wmem_list_frame_data(it);
				if (frame->frame_num == pinfo->num)
				{
					continue;
				}
				PROTO_ITEM_SET_GENERATED(
					proto_tree_add_uint(ltp_data_tree, hf_ltp_data_retrans, NULL, 0, 0, frame->frame_num)
				);
				newdata = false;
			}

			if (newdata)
			{
				ltp_frame_info_t *val = ltp_frame_info_new(pinfo);
				wmem_itree_insert(session->data_segs, data_fst, data_lst, val);
			}
		}

		ltp_data_seg_info_t data_seg_info;
		data_seg_info.pinfo = pinfo;
		data_seg_info.ltp_data_tree = ltp_data_tree;
		data_seg_info.data_fst = data_fst;
		data_seg_info.data_lst = data_lst;
		wmem_map_foreach(session->rpt_segs, ltp_data_seg_find_report, &data_seg_info);

	}
	tap->corr_orig = newdata;

	if (ltp_type != 0 && ltp_type < 4)
	{
		/* checkpoint serial number - 32 bits per CCSDS */
		ti = add_sdnv64_to_tree(ltp_data_tree, tvb, pinfo, frame_offset, hf_ltp_data_chkp, &chkp_sno, &sdnv_length);
		if (sdnv_length > 0) {
			frame_offset += sdnv_length;
			segment_size += sdnv_length;

			if (chkp_sno > 4294967295U) {
				/* just a warning - continue processing */
				expert_add_info(pinfo, ti, &ei_ltp_sno_larger_than_ccsds);
			}
		} else {
			return 0;
		}
		if (ltp_analyze_sequence && session)
		{
			proto_tree *tree_chkp_sno = proto_item_add_subtree(ti, ett_frame_ref);
			ltp_ref_src(session->checkpoints, chkp_sno, pinfo);
			ltp_ref_use(session->chkp_acks, chkp_sno, pinfo, tree_chkp_sno, hf_ltp_data_chkp_rpt, -1);
		}

		/* report serial number - 32 bits per CCSDS */
		ti = add_sdnv64_to_tree(ltp_data_tree, tvb, pinfo, frame_offset, hf_ltp_data_rpt, &rpt_sno, &sdnv_length);
		if (sdnv_length > 0) {
			frame_offset += sdnv_length;
			segment_size += sdnv_length;

			if (rpt_sno > 4294967295U) {
				/* just a warning - continue processing */
				expert_add_info(pinfo, ti, &ei_ltp_sno_larger_than_ccsds);
			}
		} else {
			return 0;
		}
		if (ltp_analyze_sequence && session)
		{
			ltp_ref_src(session->rpt_datas, rpt_sno, pinfo);
			ltp_ref_use(session->reports, rpt_sno, pinfo, proto_item_add_subtree(ti, ett_frame_ref), hf_ltp_data_rpt_ref, hf_ltp_data_rpt_time);
		}
	}
	const gboolean is_green = (ltp_type >= 4) && (ltp_type <= 7);
	const gboolean is_eorp = (ltp_type == 2) || (ltp_type == 3);
	const gboolean is_eob = (ltp_type == 3) || (ltp_type == 7);
	if (session)
	{
		if ((is_green && (data_offset == 0)) && !(session->red_size))
		{
			session->red_size = wmem_new(wmem_file_scope(), guint64);
			*(session->red_size) = 0;
		}
		if (is_eorp && !(session->red_size))
		{
			session->red_size = wmem_new(wmem_file_scope(), guint64);
			*(session->red_size) = data_offset + data_length;
		}
		if (is_eob && !(session->block_size))
		{
			session->block_size = wmem_new(wmem_file_scope(), guint64);
			*(session->block_size) = data_offset + data_length;
		}
	}

	proto_tree_add_item(ltp_data_tree, hf_ltp_data_clidata, tvb, frame_offset, -1, ENC_NA);

	if (segment_size >= tvb_captured_length(tvb)) {
		/* did not capture the entire packet */
		/* XXX: expert info instead? Distinguish between segment_size
		 * >= reported_length (i.e., bogus reported data_length) and
		 * too short capture? */
		proto_tree_add_string(ltp_data_tree, hf_ltp_partial_packet, tvb, 0, 0, "<increase capture size?>");
		/* data_len is subtracted from the return value to set the
		 * header length, so report the number of data bytes available.
		 */
		*data_len = tvb_captured_length_remaining(tvb, frame_offset);
		return tvb_captured_length(tvb);
	}

	col_append_sep_fstr(pinfo->cinfo, COL_INFO, NULL,
			"range %" G_GINT64_MODIFIER "u-%" G_GINT64_MODIFIER "u",
			data_fst, data_lst);

	if (ltp_reassemble_block)
	{
		frag_msg = fragment_add_check(
			&ltp_reassembly_table,
			tvb, frame_offset, pinfo, 0, &(tap->sess_id),
			(guint32)data_offset, (guint32)data_length, !is_eob
		);
	}
	if(frag_msg)
	{
		/* Checking if the segment is completely reassembled */
		if(!(frag_msg->flags & FD_PARTIAL_REASSEMBLY))
		{
			/* if the segment has not been fragmented, then no reassembly is needed */
			if(is_eob && data_offset == 0)
			{
				new_tvb = tvb_new_subset_length(tvb, frame_offset, (int) data_length);
			}
			else
			{
				new_tvb = process_reassembled_data(tvb, frame_offset, pinfo, "Reassembled LTP Block",
									frag_msg, &ltp_frag_items,NULL, ltp_tree);

			}
		}
	}

	if(new_tvb)
	{
		guint64 data_count = 0;
		int parse_length = tvb_reported_length(new_tvb);
		int parse_offset = 0;
		proto_tree *root_tree = proto_tree_get_parent_tree(ltp_tree);

		/* Data associated with the full block, not just this segment */
		proto_tree *block_tree = proto_tree_add_subtree_format(ltp_tree, new_tvb, 0, -1, ett_block, NULL,
				"Block, size: %d bytes", parse_length);
		tap->block_size = parse_length;

		if (session && session->red_size && session->block_size)
		{
			guint64 red_size = *(session->red_size);
			guint64 green_size = *(session->block_size) - *(session->red_size);
			PROTO_ITEM_SET_GENERATED(
				proto_tree_add_uint64(block_tree, hf_ltp_block_red_size, new_tvb, 0, (gint)red_size, red_size)
			);
			PROTO_ITEM_SET_GENERATED(
				proto_tree_add_uint64(block_tree, hf_ltp_block_green_size, new_tvb, (gint)red_size, (gint)green_size, green_size)
			);
		}

		while(parse_offset < parse_length)
		{
			int bundle_size;
			tvbuff_t *datatvb;

			if (client_id == 2) {
				add_sdnv64_to_tree(ltp_data_tree, tvb, pinfo, frame_offset+parse_offset, hf_ltp_data_sda_clid, &sda_client_id, &sdnv_length);
				parse_offset += sdnv_length;
				if (parse_offset == parse_length) {
					col_set_str(pinfo->cinfo, COL_INFO, "CCSDS LTP SDA Protocol Error");
					return 0;	/* Give up*/
				}
			}

			datatvb = tvb_new_subset_remaining(new_tvb, parse_offset);
			bundle_size = call_dissector(bundle_handle, datatvb, pinfo, root_tree);
			if(bundle_size == 0) {  /*Couldn't parse bundle*/
				col_set_str(pinfo->cinfo, COL_INFO, "Dissection Failed");
				return 0;           /*Give up*/
			}
			proto_tree_add_uint64(block_tree, hf_ltp_block_bundle_size, datatvb, 0, bundle_size, bundle_size);

			parse_offset += bundle_size;
			data_count++;
		}
		PROTO_ITEM_SET_GENERATED(
			proto_tree_add_uint64(block_tree, hf_ltp_block_bundle_cnt, new_tvb, 0, parse_offset, data_count)
		);
	}
	else
	{
		if(ltp_reassemble_block && frag_msg && (frag_msg->flags & FD_DEFRAGMENTED))
		{
			col_append_frame_number(pinfo, COL_INFO, " [Reassembled in #%d] ",frag_msg->reassembled_in);
		}
		else if (!newdata)
		{
			col_append_str(pinfo->cinfo, COL_INFO, " [Retransmission] ");
		}
		else if (ltp_reassemble_block)
		{
			col_append_str(pinfo->cinfo, COL_INFO, " [Unfinished LTP Block] ");
		}
	}

	return segment_size;
}


static void
ltp_check_reception_gap(proto_tree *ltp_rpt_tree, packet_info *pinfo,
		ltp_session_data_t *session, guint64 prec_lst, guint64 next_fst,
		int *gap_count, guint64 *gap_total) {
	const guint64 gap_len = next_fst - (prec_lst + 1);
	if (gap_len <= 0) {
		return;
	}
	proto_item *gap_item = proto_tree_add_uint64_format(ltp_rpt_tree, hf_ltp_rpt_gap, NULL, 0, 0, gap_len,
		"Reception gap: %" PRIu64 "-%" PRIu64 " (%" PRIu64 " bytes)",
		prec_lst + 1, next_fst - 1, gap_len
	);
	PROTO_ITEM_SET_GENERATED(gap_item);
	*gap_count += 1;
	*gap_total += gap_len;

	if (ltp_analyze_sequence && session)
	{
		proto_tree *gap_tree = proto_item_add_subtree(gap_item, ett_rpt_gap);

		const guint64 gap_fst = prec_lst + 1;
		const guint64 gap_lst = next_fst - 1;
		wmem_list_t *found = wmem_itree_find_intervals(session->data_segs, wmem_packet_scope(), gap_fst, gap_lst);
		for (wmem_list_frame_t *it = wmem_list_head(found); it != NULL;
			it = wmem_list_frame_next(it))
		{
			const ltp_frame_info_t *frame = wmem_list_frame_data(it);
			if (frame->frame_num > pinfo->num)
			{
				continue;
			}
			PROTO_ITEM_SET_GENERATED(
				proto_tree_add_uint(gap_tree, hf_ltp_rpt_gap_ref, NULL, 0, 0, frame->frame_num)
			);
		}
	}
}


static int
dissect_report_segment(tvbuff_t *tvb, packet_info *pinfo, proto_tree *ltp_tree, int frame_offset, ltp_tap_info_t *tap) {
	ltp_session_data_t *session = tap->session;
	gint64 rpt_sno;
	gint64 chkp_sno;
	guint64 upper_bound;
	guint64 lower_bound;
	guint64 rcpt_clm_cnt;
	guint64 offset;
	guint64 length;
	guint64 clm_fst, clm_lst;

	int rpt_sno_size;
	int chkp_sno_size;
	int upper_bound_size;
	int lower_bound_size;
	int rcpt_clm_cnt_size;
	int offset_size;
	int length_size;

	int segment_offset = 0;
	int gap_count = 0;
	guint64 gap_total = 0;

	proto_item *ltp_rpt_item;
	proto_item *ltp_rpt_clm_cnt;
	proto_item *ltp_rpt_clm_item;
	proto_item *item_rpt_sno, *item_chkp_sno;

	proto_tree *ltp_rpt_tree;
	proto_tree *ltp_rpt_clm_tree;

	/* Create the subtree for report segment under the main LTP tree and all the report segment fields under it */
	ltp_rpt_tree = proto_tree_add_subtree(ltp_tree, tvb, frame_offset, -1, ett_rpt_segm, &ltp_rpt_item, "Report Segment");

	/* Extract the report segment info */
	item_rpt_sno = add_sdnv64_to_tree(ltp_rpt_tree, tvb, pinfo, frame_offset + segment_offset, hf_ltp_rpt_sno, &rpt_sno, &rpt_sno_size);
	segment_offset += rpt_sno_size;
	if (ltp_analyze_sequence && session)
	{
		proto_tree *tree_rpt_sno = proto_item_add_subtree(item_rpt_sno, ett_frame_ref);
		ltp_ref_src(session->reports, rpt_sno, pinfo);
		ltp_ref_use(session->rpt_acks, rpt_sno, pinfo, tree_rpt_sno, hf_ltp_rpt_sno_ack, -1);
		ltp_ref_use(session->rpt_datas, rpt_sno, pinfo, tree_rpt_sno, hf_ltp_rpt_sno_data, -1);
	}

	item_chkp_sno = add_sdnv64_to_tree(ltp_rpt_tree, tvb, pinfo, frame_offset + segment_offset, hf_ltp_rpt_chkp, &chkp_sno, &chkp_sno_size);
	segment_offset += chkp_sno_size;
	if (ltp_analyze_sequence && session)
	{
		proto_tree *tree_chkp_sno = proto_item_add_subtree(item_chkp_sno, ett_frame_ref);
		ltp_ref_src(session->chkp_acks, chkp_sno, pinfo);
		ltp_ref_use(session->checkpoints, chkp_sno, pinfo, tree_chkp_sno, hf_ltp_rpt_chkp_ref, hf_ltp_rpt_chkp_time);

		if (chkp_sno == 0)
		{
			expert_add_info(pinfo, item_chkp_sno, &ei_ltp_report_async);
		}
	}

	add_sdnv64_to_tree(ltp_rpt_tree, tvb, pinfo, frame_offset + segment_offset, hf_ltp_rpt_ub, &upper_bound, &upper_bound_size);
	segment_offset += upper_bound_size;

	add_sdnv64_to_tree(ltp_rpt_tree, tvb, pinfo, frame_offset + segment_offset, hf_ltp_rpt_lb, &lower_bound, &lower_bound_size);
	segment_offset += lower_bound_size;

	PROTO_ITEM_SET_GENERATED(
		proto_tree_add_uint64(ltp_rpt_tree, hf_ltp_rpt_len, tvb, 0, 0, upper_bound - lower_bound)
	);
	col_append_sep_fstr(pinfo->cinfo, COL_INFO, NULL,
		"range %" G_GINT64_MODIFIER "u-%" G_GINT64_MODIFIER "u",
		lower_bound, upper_bound-1);

	gboolean newdata = TRUE;
	if (ltp_analyze_sequence && session)
	{
		const guint64 data_fst = lower_bound;
		const guint64 data_lst = upper_bound - 1;

		// All segments for a single report ID
		wmem_itree_t *rpt = wmem_map_lookup(session->rpt_segs, &rpt_sno);
		if (!rpt)
		{
			guint64 *key = wmem_new(wmem_file_scope(), guint64);
			*key = rpt_sno;
			rpt = wmem_itree_new(wmem_file_scope());
			wmem_map_insert(session->rpt_segs, key, rpt);
		}

		if (data_fst <= data_lst) {
			wmem_list_t *found = wmem_itree_find_intervals(rpt, wmem_packet_scope(), data_fst, data_lst);
			for (wmem_list_frame_t *it = wmem_list_head(found); it != NULL;
				it = wmem_list_frame_next(it))
			{
				const ltp_frame_info_t *frame = wmem_list_frame_data(it);
				if (frame->frame_num == pinfo->num)
				{
					continue;
				}
				PROTO_ITEM_SET_GENERATED(
					proto_tree_add_uint(ltp_rpt_tree, hf_ltp_rpt_retrans, NULL, 0, 0, frame->frame_num)
				);
				newdata = false;
			}

			if (newdata)
			{
				ltp_frame_info_t *val = ltp_frame_info_new(pinfo);
				wmem_itree_insert(rpt, data_fst, data_lst, val);
			}
		}
	}
	tap->corr_orig = newdata;

	ltp_rpt_clm_cnt = add_sdnv64_to_tree(ltp_rpt_tree, tvb, pinfo, frame_offset + segment_offset, hf_ltp_rpt_clm_cnt, &rcpt_clm_cnt, &rcpt_clm_cnt_size);
	segment_offset += rcpt_clm_cnt_size;
	/* Each reception claim is at least 2 bytes, so if the count is larger than the
	 * max number of claims we can possibly squeeze into the remaining tvbuff, then
	 * the packet is malformed.
	 */
	if (rcpt_clm_cnt > (guint64)tvb_captured_length_remaining(tvb, frame_offset + segment_offset) / 2) {
		expert_add_info_format(pinfo, ltp_rpt_clm_cnt, &ei_ltp_mal_reception_claim,
				"Reception claim count impossibly large: %" G_GINT64_MODIFIER "d > %d", rcpt_clm_cnt,
				tvb_captured_length_remaining(tvb, frame_offset + segment_offset) / 2);
		return 0;
	}

	clm_lst = lower_bound - 1;

	/* There can be multiple reception claims in the same report segment */
	for(guint64 ix = 0; ix < rcpt_clm_cnt; ix++){
		/* Peek at the offset to see if there is a preceeding gap */
		tvb_get_varint(tvb, frame_offset + segment_offset, FT_VARINT_MAX_LEN, &offset, ENC_VARINT_SDNV);
		clm_fst = lower_bound + offset;
		ltp_check_reception_gap(ltp_rpt_tree, pinfo, session, clm_lst, clm_fst, &gap_count, &gap_total);

		ltp_rpt_clm_tree = proto_tree_add_subtree(ltp_rpt_tree, tvb, frame_offset + segment_offset, -1, ett_rpt_clm, &ltp_rpt_clm_item, "Reception claim");

		add_sdnv64_to_tree(ltp_rpt_clm_tree, tvb, pinfo, frame_offset + segment_offset, hf_ltp_rpt_clm_off, &offset, &offset_size);
		segment_offset += offset_size;

		add_sdnv64_to_tree(ltp_rpt_clm_tree, tvb, pinfo, frame_offset + segment_offset, hf_ltp_rpt_clm_len, &length, &length_size);
		segment_offset += length_size;

		PROTO_ITEM_SET_GENERATED(
			proto_tree_add_uint64(ltp_rpt_clm_tree, hf_ltp_rpt_clm_fst, tvb, 0, 0, clm_fst)
		);
		clm_lst = clm_fst + length - 1;
		PROTO_ITEM_SET_GENERATED(
			proto_tree_add_uint64(ltp_rpt_clm_tree, hf_ltp_rpt_clm_lst, tvb, 0, 0, clm_lst)
		);

		proto_item_append_text(ltp_rpt_clm_item,
			": %" PRIu64 "-%" PRIu64 " (%" PRIu64 " bytes)",
			clm_fst, clm_lst, length
		);
		proto_item_set_end(ltp_rpt_clm_item, tvb, frame_offset + segment_offset);

		if (ltp_analyze_sequence && session && (clm_fst <= clm_lst))
		{
			wmem_list_t *found = wmem_itree_find_intervals(session->data_segs, wmem_packet_scope(), clm_fst, clm_lst);
			for (wmem_list_frame_t *it = wmem_list_head(found); it != NULL;
				it = wmem_list_frame_next(it))
			{
				const ltp_frame_info_t *frame = wmem_list_frame_data(it);
				if (frame->frame_num > pinfo->num)
				{
					continue;
				}
				PROTO_ITEM_SET_GENERATED(
					proto_tree_add_uint(ltp_rpt_clm_tree, hf_ltp_rpt_clm_ref, NULL, 0, 0, frame->frame_num)
				);
			}
		}
	}
	proto_item_set_end(ltp_rpt_item, tvb, frame_offset + segment_offset);

	ltp_check_reception_gap(ltp_rpt_tree, pinfo, session, clm_lst, upper_bound, &gap_count, &gap_total);
	PROTO_ITEM_SET_GENERATED(
		proto_tree_add_uint64(ltp_rpt_tree, hf_ltp_rpt_gap_total, NULL, 0, 0, gap_total)
	);
	col_append_sep_fstr(pinfo->cinfo, COL_INFO, NULL, "gaps: %d, gap total: %"PRIu64, gap_count, gap_total);

	return segment_offset;
}


static int
dissect_report_ack_segment(proto_tree *ltp_tree, tvbuff_t *tvb, packet_info *pinfo, int frame_offset, ltp_tap_info_t *tap){
	ltp_session_data_t *session = tap->session;
	gint64 rpt_sno;
	int rpt_sno_size;
	int segment_offset = 0;

	proto_item *ltp_rpt_ack_item, *item_rpt_sno;
	proto_tree *ltp_rpt_ack_tree;

	/* Creating tree for the report ack segment */
	ltp_rpt_ack_tree = proto_tree_add_subtree(ltp_tree, tvb,frame_offset, -1,
												ett_rpt_ack_segm, &ltp_rpt_ack_item, "Report Ack Segment");

	/* Extracing receipt serial number info */
	item_rpt_sno = add_sdnv64_to_tree(ltp_rpt_ack_tree, tvb, pinfo, frame_offset + segment_offset, hf_ltp_rpt_ack_sno, &rpt_sno, &rpt_sno_size);
	segment_offset += rpt_sno_size;

	proto_item_set_end(ltp_rpt_ack_item, tvb, frame_offset + segment_offset);

	if (ltp_analyze_sequence && session)
	{
		proto_tree *tree_rpt_sno = proto_item_add_subtree(item_rpt_sno, ett_frame_ref);
		ltp_ref_src(session->rpt_acks, rpt_sno, pinfo);
		ltp_ref_use(session->rpt_acks, rpt_sno, pinfo, tree_rpt_sno, hf_ltp_rpt_ack_dupe, -1);
		ltp_ref_use(session->reports, rpt_sno, pinfo, tree_rpt_sno, hf_ltp_rpt_ack_ref, hf_ltp_rpt_ack_time);
	}

	return segment_offset;
}


static int
dissect_cancel_segment(proto_tree * ltp_tree, tvbuff_t *tvb, int frame_offset, ltp_tap_info_t *tap _U_){
	guint8 reason_code;

	proto_tree *ltp_cancel_tree;

	/* The cancel segment has only one byte, which contains the reason code. */
	reason_code = tvb_get_guint8(tvb,frame_offset);

	/* Creating tree for the cancel segment */
	ltp_cancel_tree = proto_tree_add_subtree(ltp_tree, tvb,frame_offset, 1, ett_session_mgmt, NULL, "Cancel Segment");

	proto_tree_add_uint_format_value(ltp_cancel_tree, hf_ltp_cancel_code, tvb, frame_offset, 1, reason_code,
			"%x (%s)", reason_code, val_to_str_const(reason_code,ltp_cancel_codes,"Reserved"));
	return 1;
}


static int
dissect_header_extn(proto_tree *ltp_tree, tvbuff_t *tvb, packet_info *pinfo, int frame_offset,int hdr_extn_cnt){
	gint64 length;
	int length_size;

	int extn_offset = 0;

	proto_item *ltp_hdr_extn_item;
	proto_tree *ltp_hdr_extn_tree;

	ltp_hdr_extn_tree = proto_tree_add_subtree(ltp_tree, tvb,frame_offset, -1, ett_hdr_extn, &ltp_hdr_extn_item, "Header Extension");

	for(int ix = 0; ix < hdr_extn_cnt; ix++){
		/* From RFC-5326, the total length of the Header Extension Tree will be length of the following:
			a) Extension type length (1 byte)
			b) The length of the 'length' field (as defined by the SDNV which handles dynamic size)
			c) The length of the value field which is the decoded length */
		proto_tree_add_item(ltp_hdr_extn_tree, hf_ltp_hdr_extn_tag, tvb, frame_offset + extn_offset, 1, ENC_NA);
		extn_offset += 1;

		add_sdnv64_to_tree(ltp_hdr_extn_tree, tvb, pinfo, frame_offset + extn_offset, hf_ltp_hdr_extn_len, &length, &length_size);
		extn_offset += length_size;

		proto_tree_add_item(ltp_hdr_extn_tree, hf_ltp_hdr_extn_val, tvb, frame_offset + extn_offset, (int)length, ENC_NA);
		extn_offset += (int)length;
	}

	proto_item_set_end(ltp_hdr_extn_item, tvb, frame_offset + extn_offset);
	return extn_offset;
}

static int
dissect_trailer_extn(proto_tree *ltp_tree, tvbuff_t *tvb, packet_info *pinfo, int frame_offset,int trl_extn_cnt){
	gint64 length;
	int length_size;

	int extn_offset = 0;

	proto_item *ltp_trl_extn_item;
	proto_tree *ltp_trl_extn_tree;

	ltp_trl_extn_tree = proto_tree_add_subtree(ltp_tree, tvb,frame_offset, -1, ett_trl_extn, &ltp_trl_extn_item, "Trailer Extension");

	for(int ix = 0; ix < trl_extn_cnt; ix++){
		proto_tree_add_item(ltp_trl_extn_tree, hf_ltp_trl_extn_tag, tvb, frame_offset + extn_offset, 1, ENC_NA);
		frame_offset += 1;

		add_sdnv64_to_tree(ltp_trl_extn_tree, tvb, pinfo, frame_offset + extn_offset, hf_ltp_hdr_extn_len, &length, &length_size);
		frame_offset += length_size;

		proto_tree_add_item(ltp_trl_extn_tree, hf_ltp_trl_extn_val, tvb, frame_offset + extn_offset, (int)length, ENC_NA);
		frame_offset += (int)length;
	}

	proto_item_set_end(ltp_trl_extn_item, tvb, frame_offset + extn_offset);
	return extn_offset;
}


static int
dissect_ltp_segment(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree)
{
	proto_item *ti = NULL;
	proto_tree *ltp_tree = NULL;
	int frame_offset = offset;
	int segment_offset = 0;
	int data_len = 0;

	gint    ltp_type;
	guint64 bitsval;
	gint    hdr_extn_cnt;
	gint    trl_extn_cnt;

	ltp_session_id_t sess_id;
	int engine_id_size;
	int session_num_size;
	const char *sess_name;
	ltp_session_data_t *session = NULL;

	proto_tree *ltp_header_tree = NULL;
	proto_item *ltp_header_item = NULL;
	proto_tree *ltp_session_tree = NULL;
	proto_item *ltp_session_item = NULL;

	/* Check that there's enough data */
	if(tvb_captured_length(tvb) < LTP_MIN_DATA_BUFFER){
		return 0;
	}

	/* Extract all the header info from the packet */
	ti = proto_tree_add_item(tree, proto_ltp, tvb, offset, -1, ENC_NA);
	ltp_tree = proto_item_add_subtree(ti, ett_ltp);

	ltp_tap_info_t *tap = wmem_new0(wmem_packet_scope(), ltp_tap_info_t);

	/* Adding Header Subtree */
	ltp_header_tree = proto_tree_add_subtree(ltp_tree, tvb, frame_offset, 0, ett_ltp_hdr, NULL, "LTP Header");
	ltp_header_item = proto_tree_get_parent(ltp_header_tree);

	proto_tree_add_bits_ret_val(ltp_header_tree, hf_ltp_version, tvb, frame_offset, 4, &bitsval, ENC_BIG_ENDIAN);
	proto_tree_add_bits_ret_val(ltp_header_tree, hf_ltp_type, tvb, frame_offset+4, 4, &bitsval, ENC_BIG_ENDIAN);
	ltp_type = (int)bitsval;
	tap->seg_type = ltp_type;
	frame_offset++;

	/* Adding the session id subtree */
	ltp_session_tree = proto_tree_add_subtree(ltp_header_tree, tvb, frame_offset, 0, ett_hdr_session, NULL, "Session ID");
	ltp_session_item = proto_tree_get_parent(ltp_session_tree);

	add_sdnv64_to_tree(ltp_session_tree, tvb, pinfo, frame_offset, hf_ltp_session_orig, &sess_id.orig_eng_id, &engine_id_size);
	frame_offset += engine_id_size;

	add_sdnv64_to_tree(ltp_session_tree, tvb, pinfo, frame_offset, hf_ltp_session_no, &sess_id.sess_num, &session_num_size);
	frame_offset += session_num_size;

	proto_item_set_end(ltp_session_item, tvb, frame_offset);
	tap->sess_id = sess_id;

	sess_name = wmem_strdup_printf(
		wmem_file_scope(),
		"%" PRId64 "/%" PRIu64,
		sess_id.orig_eng_id, sess_id.sess_num
	);
	tap->sess_name = sess_name;
	PROTO_ITEM_SET_GENERATED(
		proto_tree_add_string(ltp_session_tree, hf_ltp_session_name, tvb,
			frame_offset - engine_id_size - session_num_size,
			engine_id_size + session_num_size, sess_name)
	);
	proto_item_append_text(ltp_session_item,": %s", sess_name);
	proto_item_append_text(ti,", Session: %s", sess_name);
	p_add_proto_data(pinfo->pool, pinfo, proto_ltp, pinfo->curr_layer_num, (void *)sess_name);

	if (ltp_analyze_sequence)
	{
		// LTP sessions exist independently of network addresses and transport ports
		conversation_element_t *conv_key = wmem_alloc_array(pinfo->pool, conversation_element_t, 3);
		conv_key[0].type = CE_UINT64;
		conv_key[0].uint64_val = sess_id.orig_eng_id;
		conv_key[1].type = CE_UINT64;
		conv_key[1].uint64_val = sess_id.sess_num;
		conv_key[2].type = CE_CONVERSATION_TYPE;
		conv_key[2].conversation_type_val = CONVERSATION_LTP;

		pinfo->use_conv_addr_port_endpoints = FALSE;
		pinfo->conv_addr_port_endpoints = NULL;
		pinfo->conv_elements = conv_key;
		conversation_t *convo = find_or_create_conversation(pinfo);

		session = conversation_get_proto_data(convo, proto_ltp);
		if (!session)
		{
			session = wmem_new0(wmem_file_scope(), ltp_session_data_t);
			session->data_segs = wmem_itree_new(wmem_file_scope());
			session->rpt_segs = wmem_map_new(wmem_file_scope(), g_int64_hash, g_int64_equal);
			session->checkpoints = wmem_map_new(wmem_file_scope(), g_int64_hash, g_int64_equal);
			session->chkp_acks = wmem_map_new(wmem_file_scope(), g_int64_hash, g_int64_equal);
			session->reports = wmem_map_new(wmem_file_scope(), g_int64_hash, g_int64_equal);
			session->rpt_acks = wmem_map_new(wmem_file_scope(), g_int64_hash, g_int64_equal);
			session->rpt_datas = wmem_map_new(wmem_file_scope(), g_int64_hash, g_int64_equal);

			conversation_add_proto_data(convo, proto_ltp, session);
		}
	}
	tap->session = session;

	/* Adding Extension count to the header tree */
	proto_tree_add_bits_ret_val(ltp_header_tree, hf_ltp_hdr_extn_cnt, tvb, 8*frame_offset, 4, &bitsval, ENC_BIG_ENDIAN);
	hdr_extn_cnt = (int)bitsval;
	proto_tree_add_bits_ret_val(ltp_header_tree, hf_ltp_trl_extn_cnt, tvb, 8*frame_offset+4, 4, &bitsval, ENC_BIG_ENDIAN);
	trl_extn_cnt = (int)bitsval;
	frame_offset++;

	proto_item_set_end(ltp_header_item, tvb, frame_offset);

	col_add_fstr(pinfo->cinfo, COL_INFO, "Session %s, %s", sess_name, val_to_str_const(ltp_type,ltp_type_col_info,"Protocol Error"));

	/* Check if there are any header extensions */
	if(hdr_extn_cnt > 0)
	{
		int hdr_extn_offset = dissect_header_extn(ltp_tree, tvb, pinfo, frame_offset,hdr_extn_cnt);
		frame_offset += hdr_extn_offset;
	}

	/* Call sub routines to handle the segment content*/
	if((ltp_type >= 0) && (ltp_type < 8)){
		segment_offset = dissect_data_segment(ltp_tree,tvb,pinfo,frame_offset,ltp_type, &data_len, tap);
		if(segment_offset == 0){
			col_set_str(pinfo->cinfo, COL_INFO, "Protocol Error");
			return 0;
		}
	}
	else if(ltp_type == 8){
		segment_offset = dissect_report_segment(tvb, pinfo, ltp_tree,frame_offset, tap);
		if(segment_offset == 0){
			col_set_str(pinfo->cinfo, COL_INFO, "Protocol Error");
			return 0;
		}
	}
	else if(ltp_type == 9){
		segment_offset = dissect_report_ack_segment(ltp_tree,tvb, pinfo, frame_offset, tap);
		if(segment_offset == 0){
			col_set_str(pinfo->cinfo, COL_INFO, "Protocol Error");
			return 0;
		}
	}
	else if(ltp_type == 12 || ltp_type == 14){
		segment_offset = dissect_cancel_segment(ltp_tree,tvb,frame_offset, tap);
		if(segment_offset == 0){
			col_set_str(pinfo->cinfo, COL_INFO, "Protocol Error");
			return 0;
		}
	}
	else if(ltp_type == 13 || ltp_type == 15){
		proto_tree_add_string(ltp_tree, hf_ltp_cancel_ack, tvb, 0, 0, "(No Data)");
	}
	frame_offset += segment_offset;

	/* Check to see if there are any trailer extensions */
	const int trl_start = frame_offset;
	if(trl_extn_cnt > 0)
	{
		int trl_length = dissect_trailer_extn(ltp_tree, tvb, pinfo, frame_offset,trl_extn_cnt);
		frame_offset += trl_length;
	}

	const int frame_len = frame_offset - offset;
	proto_item_set_len(ti, trl_start - data_len);
	proto_tree_set_appendix(ltp_tree, tvb, trl_start, frame_offset - trl_start);
	tap->seg_size = frame_len;
	tap_queue_packet(ltp_tap, pinfo, tap);

	/* Return the amount of data this dissector was able to dissect */
	return frame_len;
}

static int
dissect_ltp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
	const int packet_len = tvb_reported_length(tvb);
	int offset = 0;

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

	while (offset < packet_len)
	{
		const int sublen = dissect_ltp_segment(tvb, offset, pinfo, tree);
		if (sublen == 0)
		{
			break;
		}
		offset += sublen;
	}
	return offset;
}

/// Conversation address for the session receiver
static const char *const ltp_conv_receiver = "receiver";
/// Assigned during proto_register_ltp()
static address ltp_addr_receiver = ADDRESS_INIT_NONE;

static const char *
ltp_conv_get_filter_type(conv_item_t *conv _U_, conv_filter_type_e filter)
{
	switch (conv->dst_address.type)
	{
	case AT_STRINGZ:
		switch (filter)
		{
		case CONV_FT_SRC_ADDRESS:
		case CONV_FT_DST_ADDRESS:
		case CONV_FT_ANY_ADDRESS:
			return "ltp.session.name";
		default:
			break;
		}
		break;
	default:
		break;
	}

	return CONV_FILTER_INVALID;
}

static ct_dissector_info_t ltp_ct_dissector_info = {
	&ltp_conv_get_filter_type
};

static tap_packet_status
ltp_conv_packet(void *tapdata, packet_info *pinfo, epan_dissect_t *edt _U_, const void *data, tap_flags_t flags _U_)
{
	conv_hash_t *hash = (conv_hash_t*) tapdata;
	ltp_tap_info_t *ltp = (ltp_tap_info_t *)data;
	address *src = wmem_new0(pinfo->pool, address);
	address *dst = wmem_new0(pinfo->pool, address);

	address *diraddr, *othaddr;
	switch (ltp->seg_type) {
	case 0x8:
	case 0xd:
	case 0xe:
		// report, cancel ack to sender, cancel from receiver
		diraddr = dst;
		othaddr = src;
		break;
	default:
		diraddr = src;
		othaddr = dst;
		break;
	}
	set_address(diraddr, AT_STRINGZ, (int) strlen(ltp->sess_name) + 1, ltp->sess_name);
	copy_address_shallow(othaddr, &ltp_addr_receiver);

	add_conversation_table_data(hash, src, dst, 0, 0, 1, pinfo->fd->pkt_len, &pinfo->rel_ts, &pinfo->abs_ts,
			&ltp_ct_dissector_info, ENDPOINT_NONE);

	return TAP_PACKET_REDRAW;
}

static const char *
ltp_endp_get_filter_type(endpoint_item_t *host, conv_filter_type_e filter)
{
	switch (filter)
	{
	case CONV_FT_SRC_ADDRESS:
	case CONV_FT_DST_ADDRESS:
	case CONV_FT_ANY_ADDRESS:
		if (host->myaddress.type == AT_NUMERIC)
		{
			return "ltp.session.orig";
		}
		break;
	default:
		break;
	}

	return CONV_FILTER_INVALID;
}

static et_dissector_info_t  ltp_endp_dissector_info = {
	&ltp_endp_get_filter_type
};

static tap_packet_status
ltp_endp_packet(void *tapdata _U_, packet_info *pinfo _U_, epan_dissect_t *edt _U_, const void *data _U_, tap_flags_t flags _U_)
{
	conv_hash_t *hash = (conv_hash_t*) tapdata;
	ltp_tap_info_t *ltp = (ltp_tap_info_t *)data;
	address *diraddr = wmem_new0(pinfo->pool, address);

	set_address(diraddr, AT_NUMERIC, (int) sizeof(ltp->sess_id.orig_eng_id), &(ltp->sess_id.orig_eng_id));
	gboolean sender;
	switch (ltp->seg_type) {
	case 0x8:
	case 0xd:
	case 0xe:
		// report, cancel ack to sender, cancel from receiver
		sender = FALSE;
		break;
	default:
		sender = TRUE;
		break;
	}

	add_endpoint_table_data(hash, diraddr, 0, sender, 1, pinfo->fd->pkt_len,
			&ltp_endp_dissector_info, ENDPOINT_NONE);

	return TAP_PACKET_REDRAW;
}

static gboolean
ltp_filter_valid(packet_info *pinfo, void *user_data _U_)
{
	return proto_is_frame_protocol(pinfo->layers, "ltp");
}

static gchar*
ltp_build_filter(packet_info *pinfo, void *user_data _U_)
{
	gchar *result = NULL;
	int layer_num = 1;
	for (wmem_list_frame_t *protos = wmem_list_head(pinfo->layers);
		protos != NULL; protos = wmem_list_frame_next(protos), ++layer_num)
	{
		const int proto_id = GPOINTER_TO_INT(wmem_list_frame_data(protos));
		if (proto_id != proto_ltp)
		{
			continue;
		}
		const char *sess_name = p_get_proto_data(pinfo->pool, pinfo, proto_ltp, layer_num);
		if (!sess_name)
		{
			continue;
		}

		gchar *filter = g_strdup_printf(
			"ltp.session.name == \"%s\"",
			sess_name
		);

		if (result)
		{
			gchar *oldresult = result;
			result = g_strjoin(" || ", oldresult, filter, NULL);
			g_free(oldresult);
			g_free(filter);
		}
		else
		{
			result = filter;
		}
	}

	return result;
}

static const gchar* st_str_segs = "Segment Size (by Type)";
static const gchar* st_str_red = "Red Data";
static const gchar* st_str_corr_orig = "Original";
static const gchar* st_str_corr_ret = "Retransmission seen";
static const gchar* st_str_green = "Green Data";
static const gchar* st_str_rpt = "Report";
static const gchar* st_str_canc_src = "Cancel by Sender";
static const gchar* st_str_canc_dst = "Cancel by Receiver";
static const gchar* st_str_ack = "Report/Cancel Ack";
static const gchar* st_str_engs = "Segment Addr (by Engine ID)";
static const gchar* st_str_blks = "Block Size (by Engine ID)";
static int st_node_segs = -1;
static int st_node_red = -1;
static int st_node_green = -1;
static int st_node_rpt = -1;
static int st_node_engs = -1;
static int st_node_blks = -1;

static void
ltp_stats_tree_init(stats_tree *st)
{
	st_node_segs = stats_tree_create_node(st, st_str_segs, 0, STAT_DT_INT, FALSE);
	st_node_red = stats_tree_create_node(st, st_str_red, st_node_segs, STAT_DT_INT, TRUE);
	stats_tree_create_node(st, st_str_corr_orig, st_node_red, STAT_DT_INT, FALSE);
	stats_tree_create_node(st, st_str_corr_ret, st_node_red, STAT_DT_INT, FALSE);
	st_node_green = stats_tree_create_node(st, st_str_green, st_node_segs, STAT_DT_INT, FALSE);
	st_node_rpt = stats_tree_create_node(st, st_str_rpt, st_node_segs, STAT_DT_INT, TRUE);
	stats_tree_create_node(st, st_str_corr_orig, st_node_rpt, STAT_DT_INT, FALSE);
	stats_tree_create_node(st, st_str_corr_ret, st_node_rpt, STAT_DT_INT, FALSE);
	stats_tree_create_node(st, st_str_canc_src, st_node_segs, STAT_DT_INT, FALSE);
	stats_tree_create_node(st, st_str_canc_dst, st_node_segs, STAT_DT_INT, FALSE);
	stats_tree_create_node(st, st_str_ack, st_node_segs, STAT_DT_INT, FALSE);

	st_node_engs = stats_tree_create_pivot(st, st_str_engs, 0);
	st_node_blks = stats_tree_create_pivot(st, st_str_blks, 0);
}

static tap_packet_status
ltp_stats_tree_packet(stats_tree *st, packet_info *pinfo _U_, epan_dissect_t *edt _U_, const void *p, tap_flags_t flags _U_)
{
	const ltp_tap_info_t *tap = (const ltp_tap_info_t *)p;

	tick_stat_node(st, st_str_segs, 0, FALSE);

	switch (tap->seg_type)
	{
	case 0x0:
	case 0x1:
	case 0x2:
	case 0x3:
		avg_stat_node_add_value_int(st, st_str_red, 0, FALSE, tap->seg_size);
		avg_stat_node_add_value_int(st, tap->corr_orig ? st_str_corr_orig : st_str_corr_ret, st_node_red, TRUE, tap->seg_size);
		break;
	case 0x4:
	case 0x7:
		avg_stat_node_add_value_int(st, st_str_green, 0, FALSE, tap->seg_size);
		break;
	case 0x8:
		avg_stat_node_add_value_int(st, st_str_rpt, 0, FALSE, tap->seg_size);
		avg_stat_node_add_value_int(st, tap->corr_orig ? st_str_corr_orig : st_str_corr_ret, st_node_rpt, TRUE, tap->seg_size);
		break;
	case 0xc:
		avg_stat_node_add_value_int(st, st_str_canc_src, 0, FALSE, tap->seg_size);
		break;
	case 0xe:
		avg_stat_node_add_value_int(st, st_str_canc_dst, 0, FALSE, tap->seg_size);
		break;
	case 0x9:
	case 0xd:
	case 0xf:
		avg_stat_node_add_value_int(st, st_str_ack, 0, FALSE, tap->seg_size);
		break;
	}

	tick_stat_node(st, st_str_engs, 0, TRUE);
	const char *eng_id = wmem_strdup_printf(pinfo->pool, "%" PRIu64, tap->sess_id.orig_eng_id);
	int st_eng_id = tick_stat_node(st, eng_id, st_node_engs, TRUE);
	if (tap->block_size > 0)
	{
		avg_stat_node_add_value_int(st, st_str_blks, 0, TRUE, tap->block_size);
		avg_stat_node_add_value_int(st, eng_id, st_node_blks, FALSE, tap->block_size);
	}

	const address *eng_addr = NULL;
	switch (tap->seg_type)
	{
	case 0x0:
	case 0x1:
	case 0x2:
	case 0x3:
	case 0x4:
	case 0x7:
	case 0x9:
	case 0xc: // cancel from sender
	case 0xf:
		eng_addr = &(pinfo->src);
		break;
	case 0x8: // report
	case 0xd:
	case 0xe: // cancel from receiver
		eng_addr = &(pinfo->dst);
		break;
	}
	const gchar *eng_addr_str = eng_addr ? address_to_display(pinfo->pool, eng_addr) : NULL;
	if (eng_addr_str)
	{
		tick_stat_node(st, eng_addr_str, st_eng_id, FALSE);
	}

	return TAP_PACKET_REDRAW;
}

/* Register the protocol with Wireshark */
void
proto_register_ltp(void)
{
	static hf_register_info hf[] = {
	  {&hf_ltp_version,
		  {"LTP Version","ltp.version",
		  FT_UINT8,BASE_DEC,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_type,
		  {"LTP Type","ltp.type",
		  FT_UINT8,BASE_HEX,VALS(ltp_type_codes), 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_session_orig,
		  {"Session originator","ltp.session.orig",
		  FT_UINT64,BASE_DEC,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_session_no,
		  {"Session number","ltp.session.number",
		  FT_UINT64,BASE_DEC,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_session_name,
		  {"Session Name","ltp.session.name",
		  FT_STRING,BASE_NONE,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_hdr_extn_cnt,
		  {"Header Extension Count","ltp.hdr.extn.cnt",
		  FT_UINT8,BASE_DEC,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_trl_extn_cnt,
		  {"Trailer Extension Count","ltp.trl.extn.cnt",
		  FT_UINT8,BASE_DEC,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_data_clid,
		  {"Client service ID","ltp.data.client.id",
		  FT_UINT64,BASE_DEC | BASE_VAL64_STRING, VALS64(client_service_id_info), 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_data_offset,
		  {"Offset","ltp.data.offset",
		  FT_UINT64,BASE_DEC,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_data_length,
		  {"Length","ltp.data.length",
		  FT_UINT64,BASE_DEC|BASE_UNIT_STRING, &units_byte_bytes, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_data_chkp,
		  {"Checkpoint serial number","ltp.data.chkp",
		  FT_UINT64,BASE_DEC,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_data_chkp_rpt,
		  {"Checkpoint report segment in frame","ltp.data.chkp.rpt",
		  FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_data_rpt,
		  {"Report serial number","ltp.data.rpt",
		  FT_UINT64,BASE_DEC,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_data_rpt_ref,
		  {"Response to report segment in frame","ltp.data.rpt.ref",
		  FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_data_rpt_time,
		  {"Time since report","ltp.data.rpt.time",
		  FT_RELATIVE_TIME, BASE_NONE, NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_data_clidata,
		  {"Client service data","ltp.data.data",
		  FT_BYTES,BASE_NONE,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_data_retrans,
		  {"Retransmission of data in frame","ltp.data.retrans",
		  FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_RETRANS_PREV), 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_data_clm_rpt,
		  {"Claimed in report segment in frame","ltp.data.clm_rpt",
		  FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_NONE), 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_block_red_size,
		  {"Red part size", "ltp.block.red_size",
		  FT_UINT64,BASE_DEC|BASE_UNIT_STRING, &units_byte_bytes, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_block_green_size,
		  {"Green part size", "ltp.block.green_size",
		  FT_UINT64,BASE_DEC|BASE_UNIT_STRING, &units_byte_bytes, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_block_bundle_size,
		  {"Bundle size", "ltp.block.bundle_size",
		  FT_UINT64,BASE_DEC|BASE_UNIT_STRING, &units_byte_bytes, 0x0,
		  "The dissected bundle is below in the protocol tree", HFILL}
	  },
	  {&hf_ltp_block_bundle_cnt,
		  {"Bundles within the block", "ltp.block.bundle_cnt",
		  FT_UINT64,BASE_DEC,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_sno,
		  {"Report serial number","ltp.rpt.sno",
		  FT_UINT64,BASE_DEC,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_sno_ack,
		  {"Report ack segment in frame","ltp.rpt.sno.ack",
		  FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_sno_data,
		  {"Responding data segment in frame","ltp.rpt.sno.data",
		  FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_chkp,
		  {"Checkpoint serial number","ltp.rpt.chkp",
		  FT_UINT64,BASE_DEC,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_chkp_ref,
		  {"Checkpoint data segment in frame","ltp.rpt.chkp.ref",
		  FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_ACK), 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_chkp_time,
		  {"Time since checkpoint","ltp.rpt.chkp.time",
		  FT_RELATIVE_TIME, BASE_NONE, NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_ub,
		  {"Upper bound","ltp.rpt.ub",
		  FT_UINT64,BASE_DEC|BASE_UNIT_STRING, &units_byte_bytes, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_lb,
		  {"Lower bound","ltp.rpt.lb",
		  FT_UINT64,BASE_DEC|BASE_UNIT_STRING, &units_byte_bytes, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_len,
		  {"Report bound length","ltp.rpt.bound_len",
		  FT_UINT64,BASE_DEC|BASE_UNIT_STRING, &units_byte_bytes, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_retrans,
		  {"Retransmission of report in frame","ltp.rpt.retrans",
		  FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_RETRANS_PREV), 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_clm_cnt,
		  {"Reception claim count","ltp.rpt.clm.cnt",
		  FT_UINT64,BASE_DEC,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_clm_off,
		  {"Offset","ltp.rpt.clm.off",
		  FT_UINT64,BASE_DEC|BASE_UNIT_STRING, &units_byte_bytes, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_clm_len,
		  {"Length","ltp.rpt.clm.len",
		  FT_UINT64,BASE_DEC|BASE_UNIT_STRING, &units_byte_bytes, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_clm_fst,
		  {"First block index","ltp.rpt.clm.first",
		  FT_UINT64,BASE_DEC,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_clm_lst,
		  {"Last block index","ltp.rpt.clm.last",
		  FT_UINT64,BASE_DEC,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_clm_ref,
		  {"Data segment in frame","ltp.rpt.clm.ref",
		  FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_NONE), 0x0,
		  "Which previous data segment is this an ACK for", HFILL}
	  },
	  {&hf_ltp_rpt_gap,
		  {"Reception gap","ltp.rpt.gap",
		  FT_UINT64,BASE_DEC|BASE_UNIT_STRING, &units_byte_bytes, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_gap_ref,
		  {"Data segment in frame","ltp.rpt.gap.ref",
		  FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_NONE), 0x0,
		  "Which previous data segment is this an NACK for", HFILL}
	  },
	  {&hf_ltp_rpt_gap_total,
		  {"Total gap length","ltp.rpt.gap_total",
		  FT_UINT64,BASE_DEC|BASE_UNIT_STRING, &units_byte_bytes, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_ack_sno,
		  {"Report serial number","ltp.rpt.ack.sno",
		  FT_UINT64,BASE_DEC,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_ack_dupe,
		  {"Same ack report number in frame","ltp.rpt.ack.sno.dupe",
		  FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_NONE), 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_ack_ref,
		  {"Report segment in frame","ltp.rpt.ack.sno.ref",
		  FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_ACK), 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_rpt_ack_time,
		  {"Time since report","ltp.rpt.ack.sno.time",
		  FT_RELATIVE_TIME, BASE_NONE, NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_cancel_code,
		  {"Cancel code","ltp.cancel.code",
		  FT_UINT8,BASE_HEX,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_hdr_extn_tag,
		  {"Extension tag","ltp.hdr.extn.tag",
		  FT_UINT8,BASE_HEX,VALS(extn_tag_codes), 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_hdr_extn_len,
		  {"Length","ltp.hdr.extn.len",
		  FT_UINT64,BASE_DEC,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_hdr_extn_val,
		  {"Value","ltp.hdr.extn.val",
		  FT_BYTES,BASE_NONE,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_trl_extn_tag,
		  {"Extension tag","ltp.trl.extn.tag",
		  FT_UINT8,BASE_HEX,VALS(extn_tag_codes), 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_trl_extn_len,
		  {"Length","ltp.trl.extn.len",
		  FT_UINT64,BASE_DEC,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_trl_extn_val,
		  {"Value","ltp.trl.extn.val",
		  FT_BYTES,BASE_NONE,NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_fragments,
		  {"LTP Fragments", "ltp.fragments",
		  FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_fragment,
		  {"LTP Fragment", "ltp.fragment",
		  FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_fragment_overlap,
		  {"LTP fragment overlap", "ltp.fragment.overlap",
		  FT_BOOLEAN, BASE_NONE, NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_fragment_overlap_conflicts,
		  {"LTP fragment overlapping with conflicting data",
		   "ltp.fragment.overlap.conflicts",
		  FT_BOOLEAN, BASE_NONE, NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_fragment_multiple_tails,
		  {"LTP has multiple tails", "ltp.fragment.multiple_tails",
		  FT_BOOLEAN, BASE_NONE, NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_fragment_too_long_fragment,
		  {"LTP fragment too long", "ltp.fragment.too_long_fragment",
		  FT_BOOLEAN, BASE_NONE, NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_fragment_error,
		  {"LTP defragmentation error", "ltp.fragment.error",
		  FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_fragment_count,
		  {"LTP fragment count", "ltp.fragment.count",
		  FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_reassembled_in,
		  {"LTP reassembled in", "ltp.reassembled.in",
		  FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_reassembled_length,
		  {"LTP reassembled length", "ltp.reassembled.length",
		  FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_data_sda_clid,
		  {"Client service ID", "ltp.data.sda.client.id",
		  FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_partial_packet,
		  {"<partial packet>", "ltp.partial_packet",
		  FT_STRINGZPAD, BASE_NONE, NULL, 0x0, NULL, HFILL}
	  },
	  {&hf_ltp_cancel_ack,
		  {"<Cancel Ack>", "ltp.cancel_ack",
		  FT_STRINGZPAD, BASE_NONE, NULL, 0x0, NULL, HFILL}
	  }
	};

/* Setup protocol subtree array */
	static gint *ett[] = {
		&ett_ltp,
		&ett_ltp_hdr,
		&ett_hdr_session,
		&ett_hdr_extn,
		&ett_frame_ref,
		&ett_data_segm,
		&ett_block,
		&ett_rpt_segm,
		&ett_rpt_clm,
		&ett_rpt_gap,
		&ett_rpt_ack_segm,
		&ett_session_mgmt,
		&ett_trl_extn,
		&ett_ltp_fragment,
		&ett_ltp_fragments
	};

	static ei_register_info ei[] = {
		{ &ei_ltp_mal_reception_claim, { "ltp.mal_reception_claim", PI_MALFORMED, PI_ERROR, "Reception claim count impossibly large", EXPFILL }},
		{ &ei_ltp_sdnv_length, { "ltp.sdnv_length_invalid", PI_PROTOCOL, PI_ERROR, "SDNV length error", EXPFILL }},
		{ &ei_ltp_sno_larger_than_ccsds, { "ltp.serial_number_too_large", PI_PROTOCOL, PI_WARN, "Serial number larger than CCSDS specification", EXPFILL }},
		{ &ei_ltp_report_async, { "ltp.report_async", PI_SEQUENCE, PI_CHAT, "Report segment not sent in response to a data checkpoint", EXPFILL }}
	};

	expert_module_t* expert_ltp;

	/* Register the protocol name and description */
	proto_ltp = proto_register_protocol("Licklider Transmission Protocol", "LTP", "ltp");

	module_t *module_ltp = prefs_register_protocol(proto_ltp, NULL);
	prefs_register_bool_preference(
		module_ltp,
		"analyze_sequence",
		"Analyze segment sequences",
		"Whether the dissector should analyze the sequencing and "
		"cross-references of the segments within each session.",
		&ltp_analyze_sequence
	);
	prefs_register_bool_preference(
		module_ltp,
		"reassemble_block",
		"Reassemble block segments",
		"Whether the dissector should combine block segments "
		"together into a full block.",
		&ltp_reassemble_block
	);

	proto_register_field_array(proto_ltp, hf, array_length(hf));
	proto_register_subtree_array(ett, array_length(ett));
	expert_ltp = expert_register_protocol(proto_ltp);
	expert_register_field_array(expert_ltp, ei, array_length(ei));

	ltp_handle = register_dissector("ltp", dissect_ltp, proto_ltp);

	set_address(&ltp_addr_receiver, AT_STRINGZ, (int) strlen(ltp_conv_receiver) + 1, ltp_conv_receiver);
	register_conversation_table(proto_ltp, TRUE, ltp_conv_packet, ltp_endp_packet);
	register_conversation_filter("ltp", "LTP", ltp_filter_valid, ltp_build_filter, NULL);
	ltp_tap = register_tap("ltp");

	static const reassembly_table_functions ltp_session_reassembly_table_functions = {
		ltp_session_id_hash,
		ltp_session_id_equal,
		ltp_session_new_key,
		ltp_session_new_key,
		ltp_session_free_key,
		ltp_session_free_key
	};
	reassembly_table_register(&ltp_reassembly_table,
		&ltp_session_reassembly_table_functions);
}

void
proto_reg_handoff_ltp(void)
{
	bundle_handle = find_dissector_add_dependency("bundle", proto_ltp);

	dissector_add_uint_with_preference("udp.port", LTP_PORT, ltp_handle);
	dissector_add_uint_with_preference("dccp.port", LTP_PORT, ltp_handle);

	stats_tree_register("ltp", "ltp", "LTP", ST_SORT_COL_COUNT, ltp_stats_tree_packet, ltp_stats_tree_init, NULL);
}

/*
 * 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:
 */
