/* packet-xip.c
 * Routines for XIP dissection
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * The eXpressive Internet Protocol (XIP) is the network layer protocol for
 * the eXpressive Internet Architecture (XIA), a future Internet architecture
 * project. The addresses in XIP are directed acyclic graphs, so some of the
 * code in this file verifies the correctness of the DAGs and displays them
 * in human-readable form.
 *
 * More information about XIA can be found here:
 *  https://www.cs.cmu.edu/~xia/
 *
 * And here:
 *  https://github.com/AltraMayor/XIA-for-Linux/wiki
 *
 * More information about the format of the DAG can be found here:
 *  https://github.com/AltraMayor/XIA-for-Linux/wiki/Human-readable-XIP-address-format
 */

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

void proto_register_xip(void);
void proto_reg_handoff_xip(void);

/* Next dissector handles. */
static dissector_handle_t xip_serval_handle;

static int proto_xip;

static int hf_xip_version;
static int hf_xip_next_hdr;
static int hf_xip_payload_len;
static int hf_xip_hop_limit;
static int hf_xip_num_dst;
static int hf_xip_num_src;
static int hf_xip_last_node;
static int hf_xip_dst_dag;
static int hf_xip_dst_dag_entry;
static int hf_xip_src_dag;
static int hf_xip_src_dag_entry;

static int ett_xip_tree;
static int ett_xip_ddag;
static int ett_xip_sdag;

static expert_field ei_xip_invalid_len;
static expert_field ei_xip_next_header;
static expert_field ei_xip_bad_num_dst;
static expert_field ei_xip_bad_num_src;

static dissector_handle_t xip_handle;

/* XIA principals. */
#define XIDTYPE_NAT		0x00
#define XIDTYPE_AD		0x10
#define XIDTYPE_HID		0x11
#define XIDTYPE_CID		0x12
#define XIDTYPE_SID		0x13
#define XIDTYPE_UNI4ID		0x14
#define XIDTYPE_I4ID		0x15
#define XIDTYPE_U4ID		0x16
#define XIDTYPE_XDP		0x17
#define XIDTYPE_SRVCID		0x18
#define XIDTYPE_FLOWID		0x19
#define XIDTYPE_ZF		0x20

/* Principal string values. */
static const value_string xidtype_vals[] = {
	{ XIDTYPE_AD,		"ad" },
	{ XIDTYPE_HID,		"hid" },
	{ XIDTYPE_CID,		"cid" },
	{ XIDTYPE_SID,		"sid" },
	{ XIDTYPE_UNI4ID,	"uni4id" },
	{ XIDTYPE_I4ID,		"i4id" },
	{ XIDTYPE_U4ID,		"u4id" },
	{ XIDTYPE_XDP,		"xdp" },
	{ XIDTYPE_SRVCID,	"serval" },
	{ XIDTYPE_FLOWID,	"flowid" },
	{ XIDTYPE_ZF,		"zf" },
	{ 0,			NULL }
};

enum xia_addr_error {
	/* There's a non-XIDTYPE_NAT node after an XIDTYPE_NAT node. */
	XIAEADDR_NAT_MISPLACED = 1,
	/* Edge-selected bit is only valid in packets. */
	XIAEADDR_CHOSEN_EDGE,
	/* There's a non-empty edge after an Empty Edge.
	 * This error can also occur if an empty edge is selected. */
	XIAEADDR_EE_MISPLACED,
	/* An edge of a node is out of range. */
	XIAEADDR_EDGE_OUT_RANGE,
	/* The nodes are not in topological order. Notice that being in
	 * topological guarantees that the graph is acyclic, and has a simple,
	 * cheap test. */
	XIAEADDR_NOT_TOPOLOGICAL,
	/* No single component. */
	XIAEADDR_MULTI_COMPONENTS,
	/* Entry node is not present. */
	XIAEADDR_NO_ENTRY
};

/* Maximum number of nodes in a DAG. */
#define XIA_NODES_MAX		9

/* Number of outgoing edges for each node. */
#define XIA_OUTDEGREE_MAX	4

/* Sizes of an XIA node and its components. */
#define XIA_TYPE_SIZE		4
#define XIA_XID_SIZE		20
#define XIA_EDGES_SIZE		4
#define XIA_NODE_SIZE		(XIA_TYPE_SIZE + XIA_XID_SIZE + XIA_EDGES_SIZE)

/* Split XID up into 4 byte chunks. */
#define XIA_XID_CHUNK_SIZE	4

typedef uint32_t xid_type_t;

struct xia_xid {
	/* XID type. */
	xid_type_t	xid_type;

	/* XID, represented as 4 byte ints. */
	uint32_t		xid_id[XIA_XID_SIZE / XIA_XID_CHUNK_SIZE];
};

struct xia_row {
	struct xia_xid	s_xid;
	/* Outgoing edges. */
	union {
		uint8_t	a[XIA_OUTDEGREE_MAX];
		uint32_t	i;
	} s_edge;
};

struct xia_addr {
	struct xia_row s_row[XIA_NODES_MAX];
};

/* XIA_MAX_STRADDR_SIZE - The maximum size of an XIA address as a string
 * in bytes. It's the recommended size to call xia_ntop with. It includes space
 * for an invalid sign (i.e. '!'), the type and name of a nodes in
 * hexadecimal, the out-edges, the two separators (i.e. '-') per node,
 * the edge-chosen sign (i.e. '>') for each selected edge,
 * the node separators (i.e. ':' or ":\n"), a string terminator (i.e. '\0'),
 * and an extra '\n' at the end the caller may want to add.
 */
#define MAX_PPAL_NAME_SIZE	32
#define XIA_MAX_STRID_SIZE	(XIA_XID_SIZE * 2 + 1)
#define XIA_MAX_STRXID_SIZE	(MAX_PPAL_NAME_SIZE + XIA_MAX_STRID_SIZE)
#define XIA_MAX_STRADDR_SIZE	(1 + XIA_NODES_MAX * \
	(XIA_MAX_STRXID_SIZE + XIA_OUTDEGREE_MAX * 2 + 2) + 1)

/*
 *	Validating addresses
 */

#define XIA_CHOSEN_EDGE		0x80
#define XIA_EMPTY_EDGE		0x7f
#define XIA_ENTRY_NODE_INDEX	0x7e

#define XIA_EMPTY_EDGES (XIA_EMPTY_EDGE << 24 | XIA_EMPTY_EDGE << 16 |\
			 XIA_EMPTY_EDGE <<  8 | XIA_EMPTY_EDGE)
#define XIA_CHOSEN_EDGES (XIA_CHOSEN_EDGE << 24 | XIA_CHOSEN_EDGE << 16 |\
			 XIA_CHOSEN_EDGE <<  8 | XIA_CHOSEN_EDGE)

static inline int
is_edge_chosen(uint8_t e)
{
	return e & XIA_CHOSEN_EDGE;
}

static inline int
is_any_edge_chosen(const struct xia_row *row)
{
	return row->s_edge.i & XIA_CHOSEN_EDGES;
}

static inline int
is_empty_edge(uint8_t e)
{
	return (e & XIA_EMPTY_EDGE) == XIA_EMPTY_EDGE;
}

static inline int
xia_is_nat(xid_type_t ty)
{
	return ty == XIDTYPE_NAT;
}

static int
xia_are_edges_valid(const struct xia_row *row,
	uint8_t node, uint8_t num_node, uint32_t *pvisited)
{
	const uint8_t *edge;
	uint32_t all_edges, bits;
	int i;

	if (is_any_edge_chosen(row)) {
		/* Since at least an edge of last_node has already
		 * been chosen, the address is corrupted.
		 */
		return -XIAEADDR_CHOSEN_EDGE;
	}

	edge = row->s_edge.a;
	all_edges = g_ntohl(row->s_edge.i);
	bits = 0xffffffff;
	for (i = 0; i < XIA_OUTDEGREE_MAX; i++, edge++) {
		uint8_t e;
		e = *edge;
		if (e == XIA_EMPTY_EDGE) {
			if ((all_edges & bits) !=
				(XIA_EMPTY_EDGES & bits))
				return -XIAEADDR_EE_MISPLACED;
			else
				break;
		} else if (e >= num_node) {
			return -XIAEADDR_EDGE_OUT_RANGE;
		} else if (node < (num_node - 1) && e <= node) {
			/* Notice that if (node == XIA_ENTRY_NODE_INDEX)
			 * it still works fine because XIA_ENTRY_NODE_INDEX
			 * is greater than (num_node - 1).
			 */
			return -XIAEADDR_NOT_TOPOLOGICAL;
		}
		bits >>= 8;
		*pvisited |= 1 << e;
	}
	return 0;
}

static int
xia_test_addr(const struct xia_addr *addr)
{
	int i, n;
	int saw_nat = 0;
	uint32_t visited = 0;

	/* Test that XIDTYPE_NAT is present only on last rows. */
	n = XIA_NODES_MAX;
	for (i = 0; i < XIA_NODES_MAX; i++) {
		xid_type_t ty;
		ty = addr->s_row[i].s_xid.xid_type;
		if (saw_nat) {
			if (!xia_is_nat(ty))
				return -XIAEADDR_NAT_MISPLACED;
		} else if (xia_is_nat(ty)) {
			n = i;
			saw_nat = 1;
		}
	}
	/* n = number of nodes from here. */

	/* Test edges are well formed. */
	for (i = 0; i < n; i++) {
		int rc;
		rc = xia_are_edges_valid(&addr->s_row[i], i, n, &visited);
		if (rc)
			return rc;
	}

	if (n >= 1) {
		/* Test entry point is present. Notice that it's just a
		 * friendlier error since it's also XIAEADDR_MULTI_COMPONENTS.
		 */
		uint32_t all_edges;
		all_edges = addr->s_row[n - 1].s_edge.i;
		if (all_edges == XIA_EMPTY_EDGES)
			return -XIAEADDR_NO_ENTRY;

		if (visited != ((1U << n) - 1))
			return -XIAEADDR_MULTI_COMPONENTS;
	}

	return n;
}

/*
 *	Printing addresses out
 */

#define INDEX_BASE 36

static inline char
edge_to_char(uint8_t e)
{
	const char *ch_edge = "0123456789abcdefghijklmnopqrstuvwxyz";
	e &= ~XIA_CHOSEN_EDGE;
	if (e < INDEX_BASE)
		return ch_edge[e];
	else if (is_empty_edge(e))
		return '*';
	else
		return '+';
}

static void
add_edges_to_buf(int valid, wmem_strbuf_t *buf, const uint8_t *edges)
{
	int i;
	wmem_strbuf_append_c(buf, '-');
	for (i = 0; i < XIA_OUTDEGREE_MAX; i++) {
		if (valid && edges[i] == XIA_EMPTY_EDGE)
			return;

		if (is_edge_chosen(edges[i]))
			wmem_strbuf_append_c(buf, '>');

		wmem_strbuf_append_c(buf, edge_to_char(edges[i]));
	}
}

static void
add_type_to_buf(xid_type_t ty, wmem_strbuf_t *buf)
{
	const char *xid_name;
	size_t buflen = wmem_strbuf_get_len(buf);

	if (XIA_MAX_STRADDR_SIZE - buflen - 1 < MAX_PPAL_NAME_SIZE)
		return;

	xid_name = try_val_to_str(ty, xidtype_vals);
	if (xid_name)
		wmem_strbuf_append_printf(buf, "%s-", xid_name);
	else
		wmem_strbuf_append_printf(buf, "0x%x-", ty);
}

static inline void
add_id_to_buf(const struct xia_xid *src, wmem_strbuf_t *buf)
{
	wmem_strbuf_append_printf(buf, "%08x%08x%08x%08x%08x",
		src->xid_id[0],
		src->xid_id[1],
		src->xid_id[2],
		src->xid_id[3],
		src->xid_id[4]);
}

/* xia_ntop - convert an XIA address to a string.
 * @src can be ill-formed, but xia_ntop won't report an error and will return
 * a string that approximates that ill-formed address.
 */
static int
xia_ntop(const struct xia_addr *src, wmem_strbuf_t *buf)
{
	int valid, i;

	valid = xia_test_addr(src) >= 1;
	if (!valid)
		wmem_strbuf_append_c(buf, '!');

	for (i = 0; i < XIA_NODES_MAX; i++) {
		const struct xia_row *row = &src->s_row[i];

		if (xia_is_nat(row->s_xid.xid_type))
			break;

		if (i > 0)
			wmem_strbuf_append(buf, ":\n");

		/* Add the type, ID, and edges for this node. */
		add_type_to_buf(row->s_xid.xid_type, buf);
		add_id_to_buf(&row->s_xid, buf);
		add_edges_to_buf(valid, buf, row->s_edge.a);
	}

	return 0;
}

/*
 *	Dissection
 */

#define XIPH_MIN_LEN		36
#define ETHERTYPE_XIP		0xC0DE
#define XIA_NEXT_HEADER_DATA	0

/* Offsets of XIP fields in bytes. */
#define XIPH_VERS		0
#define XIPH_NXTH		1
#define XIPH_PLEN		2
#define XIPH_HOPL		4
#define XIPH_NDST		5
#define XIPH_NSRC		6
#define XIPH_LSTN		7
#define XIPH_DSTD		8

static void
construct_dag(tvbuff_t *tvb, packet_info *pinfo, proto_tree *xip_tree,
	const int ett, const int hf, const int hf_entry,
	const uint8_t num_nodes, int offset)
{
	proto_tree *dag_tree;
	proto_item *ti;
	struct xia_addr dag;
	wmem_strbuf_t *buf;
	const char *dag_str;
	unsigned i, j;
	int dag_offset = offset;

	ti = proto_tree_add_item(xip_tree, hf, tvb, offset,
		num_nodes * XIA_NODE_SIZE, ENC_BIG_ENDIAN);

	buf = wmem_strbuf_new_sized(pinfo->pool, XIA_MAX_STRADDR_SIZE);

	dag_tree = proto_item_add_subtree(ti, ett);

	memset(&dag, 0, sizeof(dag));
	for (i = 0; i < num_nodes; i++) {
		struct xia_row *row = &dag.s_row[i];

		row->s_xid.xid_type = tvb_get_ntohl(tvb, offset);
		offset += XIA_TYPE_SIZE;

		/* Process the ID 32 bits at a time. */
		for (j = 0; j < XIA_XID_SIZE / XIA_XID_CHUNK_SIZE; j++) {
			row->s_xid.xid_id[j] = tvb_get_ntohl(tvb, offset);
			offset += XIA_XID_CHUNK_SIZE;
		}

		/* Need to process the edges byte-by-byte,
		 * so keep the bytes in network order.
		 */
		tvb_memcpy(tvb, row->s_edge.a, offset, XIA_EDGES_SIZE);
		offset += XIA_EDGES_SIZE;
	}

	xia_ntop(&dag, buf);
	dag_str = wmem_strbuf_get_str(buf);
	proto_tree_add_string_format(dag_tree, hf_entry, tvb, dag_offset,
		XIA_NODE_SIZE * num_nodes, dag_str, "%s", dag_str);
}

static int
dissect_xip_sink_node(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
	int offset, uint8_t sink_node)
{
	tvbuff_t *next_tvb;

	switch (sink_node) {
	/* Serval XID types. */
	case XIDTYPE_FLOWID:
	case XIDTYPE_SRVCID:
		next_tvb = tvb_new_subset_remaining(tvb, offset);
		return call_dissector(xip_serval_handle, next_tvb, pinfo, tree);
	/* No special sink processing. */
	default:
		return 0;
	}
}

static int
dissect_xip_next_header(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
	proto_item *next_ti, int offset)
{
	tvbuff_t *next_tvb;
	uint8_t next_header = tvb_get_uint8(tvb, XIPH_NXTH);

	switch (next_header) {
	case XIA_NEXT_HEADER_DATA:
		next_tvb = tvb_new_subset_remaining(tvb, offset);
		return call_data_dissector(next_tvb, pinfo, tree);
	default:
		expert_add_info_format(pinfo, next_ti, &ei_xip_next_header,
		 "Unrecognized next header type: 0x%02x", next_header);
		return 0;
	}
}

static void
display_xip(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
	proto_tree *xip_tree = NULL;

	proto_item *ti = NULL;
	proto_item *payload_ti = NULL;
	proto_item *next_ti = NULL;
	proto_item *num_ti = NULL;

	int offset;
	uint16_t xiph_len, payload_len;
	uint8_t num_dst_nodes, num_src_nodes, last_node;

	num_dst_nodes = tvb_get_uint8(tvb, XIPH_NDST);
	num_src_nodes = tvb_get_uint8(tvb, XIPH_NSRC);
	xiph_len = 8 + (XIA_NODE_SIZE * num_dst_nodes) +
		(XIA_NODE_SIZE * num_src_nodes);

	/* Construct protocol tree. */
	ti = proto_tree_add_item(tree, proto_xip, tvb, 0, xiph_len, ENC_NA);
	xip_tree = proto_item_add_subtree(ti, ett_xip_tree);

	/* Add XIP version. */
	proto_tree_add_item(xip_tree, hf_xip_version, tvb,
		XIPH_VERS, 1, ENC_BIG_ENDIAN);

	/* Add XIP next header. */
	next_ti = proto_tree_add_item(xip_tree, hf_xip_next_hdr, tvb,
		XIPH_NXTH, 1, ENC_BIG_ENDIAN);

	/* Add XIP payload length. */
	payload_len = tvb_get_ntohs(tvb, XIPH_PLEN);
	payload_ti = proto_tree_add_uint_format(xip_tree, hf_xip_payload_len,
		tvb, XIPH_PLEN, 2, payload_len, "Payload Length: %u bytes",
		payload_len);
	if (tvb_captured_length_remaining(tvb, xiph_len) != payload_len)
		expert_add_info_format(pinfo, payload_ti, &ei_xip_invalid_len,
		"Payload length field (%d bytes) does not match actual payload length (%d bytes)",
		payload_len, tvb_captured_length_remaining(tvb, xiph_len));

	/* Add XIP hop limit. */
	proto_tree_add_item(xip_tree, hf_xip_hop_limit, tvb,
		XIPH_HOPL, 1, ENC_BIG_ENDIAN);

	/* Add XIP number of destination DAG nodes. */
	num_ti = proto_tree_add_item(xip_tree, hf_xip_num_dst, tvb,
		XIPH_NDST, 1, ENC_BIG_ENDIAN);
	if (num_dst_nodes > XIA_NODES_MAX) {
		expert_add_info_format(pinfo, num_ti, &ei_xip_bad_num_dst,
		"The number of destination DAG nodes (%d) must be less than XIA_NODES_MAX (%d)",
		num_dst_nodes, XIA_NODES_MAX);
		num_dst_nodes = XIA_NODES_MAX;
	}

	/* Add XIP number of source DAG nodes. */
	num_ti = proto_tree_add_item(xip_tree, hf_xip_num_src, tvb,
		XIPH_NSRC, 1, ENC_BIG_ENDIAN);
	if (num_src_nodes > XIA_NODES_MAX) {
		expert_add_info_format(pinfo, num_ti, &ei_xip_bad_num_src,
		"The number of source DAG nodes (%d) must be less than XIA_NODES_MAX (%d)",
		num_src_nodes, XIA_NODES_MAX);
		num_src_nodes = XIA_NODES_MAX;
	}

	/* Add XIP last node. */
	last_node = tvb_get_uint8(tvb, XIPH_LSTN);
	proto_tree_add_uint_format_value(xip_tree, hf_xip_last_node, tvb,
		XIPH_LSTN, 1, last_node, "%d%s", last_node,
		last_node == XIA_ENTRY_NODE_INDEX ? " (entry node)" : "");

	/* Construct Destination DAG subtree. */
	if (num_dst_nodes > 0)
		construct_dag(tvb, pinfo, xip_tree, ett_xip_ddag,
			hf_xip_dst_dag, hf_xip_dst_dag_entry,
			num_dst_nodes, XIPH_DSTD);

	/* Construct Source DAG subtree. */
	if (num_src_nodes > 0)
		construct_dag(tvb, pinfo, xip_tree, ett_xip_sdag,
			hf_xip_src_dag, hf_xip_src_dag_entry,
			num_src_nodes,
			XIPH_DSTD + num_dst_nodes * XIA_NODE_SIZE);

	/* First byte after XIP header. */
	offset = XIPH_DSTD + XIA_NODE_SIZE * (num_dst_nodes + num_src_nodes);

	/* Dissect other headers according to the sink node, if needed. */
	offset += dissect_xip_sink_node(tvb, pinfo, tree, offset,
			tvb_get_ntohl(tvb, XIPH_DSTD +
			(num_dst_nodes - 1) * XIA_NODE_SIZE));

	dissect_xip_next_header(tvb, pinfo, tree, next_ti, offset);
}

static int
dissect_xip(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
	void *data _U_)
{
	/* Not large enough to be valid XIP packet. */
	if (tvb_reported_length(tvb) < XIPH_MIN_LEN)
		return 0;

	col_set_str(pinfo->cinfo, COL_PROTOCOL, "XIP");
	col_set_str(pinfo->cinfo, COL_INFO, "XIP Packet");

	display_xip(tvb, pinfo, tree);
	return tvb_captured_length(tvb);
}

void
proto_register_xip(void)
{
	static hf_register_info hf[] = {

		/* XIP Header. */

		{ &hf_xip_version,
		{ "Version", "xip.version", FT_UINT8,
		   BASE_DEC, NULL, 0x0,	NULL, HFILL }},

		{ &hf_xip_next_hdr,
		{ "Next Header", "xip.next_hdr", FT_UINT8,
		   BASE_DEC, NULL, 0x0, NULL, HFILL }},

		{ &hf_xip_payload_len,
		{ "Payload Length", "xip.payload_len", FT_UINT16,
		   BASE_DEC, NULL, 0x0,	NULL, HFILL }},

		{ &hf_xip_hop_limit,
		{ "Hop Limit", "xip.hop_limit", FT_UINT8,
		   BASE_DEC, NULL, 0x0,	NULL, HFILL }},

		{ &hf_xip_num_dst,
		{ "Number of Destination Nodes", "xip.num_dst", FT_UINT8,
		   BASE_DEC, NULL, 0x0, NULL, HFILL }},

		{ &hf_xip_num_src,
		{ "Number of Source Nodes", "xip.num_src", FT_UINT8,
		   BASE_DEC, NULL, 0x0, NULL, HFILL }},

		{ &hf_xip_last_node,
		{ "Last Node", "xip.last_node", FT_UINT8,
		   BASE_DEC, NULL, 0x0, NULL, HFILL }},

		{ &hf_xip_dst_dag,
		{ "Destination DAG", "xip.dst_dag", FT_NONE,
		   BASE_NONE, NULL, 0x0, NULL, HFILL }},

		{ &hf_xip_dst_dag_entry,
		{ "Destination DAG Entry", "xip.dst_dag_entry", FT_STRING,
		   BASE_NONE, NULL, 0x0, NULL, HFILL }},

		{ &hf_xip_src_dag,
		{ "Source DAG", "xip.src_dag", FT_NONE,
		   BASE_NONE, NULL, 0x0, NULL, HFILL }},

		{ &hf_xip_src_dag_entry,
		{ "Source DAG Entry", "xip.src_dag_entry", FT_STRING,
		   BASE_NONE, NULL, 0x0, NULL, HFILL }}
	};

	static int *ett[] = {
		&ett_xip_tree,
		&ett_xip_ddag,
		&ett_xip_sdag
	};

	static ei_register_info ei[] = {
		{ &ei_xip_invalid_len,
		{ "xip.invalid.len", PI_MALFORMED, PI_ERROR,
		  "Invalid length", EXPFILL }},

		{ &ei_xip_next_header,
		{ "xip.next.header", PI_MALFORMED, PI_ERROR,
		  "Invalid next header", EXPFILL }},

		{ &ei_xip_bad_num_dst,
		{ "xip.bad_num_dst", PI_MALFORMED, PI_ERROR,
		  "Invalid number of destination DAG nodes", EXPFILL }},

		{ &ei_xip_bad_num_src,
		{ "xip.bad_num_src", PI_MALFORMED, PI_ERROR,
		  "Invalid number of source DAG nodes", EXPFILL }}
	};

	expert_module_t* expert_xip;

	proto_xip = proto_register_protocol("eXpressive Internet Protocol", "XIP", "xip");

	xip_handle = register_dissector("xip", dissect_xip, proto_xip);
	proto_register_field_array(proto_xip, hf, array_length(hf));
	proto_register_subtree_array(ett, array_length(ett));

	expert_xip = expert_register_protocol(proto_xip);
	expert_register_field_array(expert_xip, ei, array_length(ei));
}

void
proto_reg_handoff_xip(void)
{
	dissector_add_uint("ethertype", ETHERTYPE_XIP, xip_handle);

	xip_serval_handle = find_dissector_add_dependency("xipserval", proto_xip);
}

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