/* packet-lon.c
 * Traffic analyzer for Lontalk/EIA-709.1 networks
 * Daniel Willmann <daniel@totalueberwachung.de>
 * (c) 2011 Daniel Willmann
 *
 * Used some code by habibi_khalid <khalidhabibi@gmx.de> and
 * Honorine_KEMGNE_NGUIFFO <honorinekemgne@yahoo.fr> from
 * https://gitlab.com/wireshark/wireshark/-/issues/4704
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "config.h"

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

void proto_register_lon(void);
void proto_reg_handoff_lon(void);

static const value_string pdu_fmt_vs[]=
{
	{0x00, "TPDU"},
	{0x01, "SPDU"},
	{0x02, "AuthPDU"},
	{0x03, "APDU"},
	{0, NULL}
};

static const value_string addr_fmt_vs[]=
{
	{0x00, "Broadcast (0)"},
	{0x01, "Multicast (1)"},
	{0x02, "Unicast (2a)/Multicast (2b)"},
	{0x03, "Unicast (3)"},
	{0, NULL}
};

static const value_string domain_length_vs[]=
{
	{0x00, "0 bit"},
	{0x01, "8 bit"},
	{0x02, "24 bit"},
	{0x03, "48 bit"},
	{0, NULL}
};

static const value_string tpdu_type_vs[]=
{
	{0x00, "ACKD"},
	{0x01, "UnACKD_RPT"},
	{0x02, "ACK"},
	{0x04, "REMINDER"},
	{0x05, "REM/MSG"},
	{0, NULL}
};

static const value_string spdu_type_vs[]=
{
	{0x00, "REQUEST"},
	{0x02, "RESPONSE"},
	{0x04, "REMINDER"},
	{0x05, "REM/MSG"},
	{0, NULL}
};

static const value_string authpdu_type_vs[]=
{
	{0x00, "CHALLENGE"},
	{0x02, "REPLY"},
	{0, NULL}
};

static const value_string nm_code_vs[]=
{
	{0x61, "NM_QUERY_ID"},
	{0x62, "NM_RESPOND_TO_QUERY"},
	{0x63, "NM_UPDATE_DOMAIN"},
	{0x64, "NM_LEAVE_DOMAIN"},
	{0x65, "NM_UPDATE_KEY"},
	{0x66, "NM_UPDATE_ADDR"},
	{0x67, "NM_QUERY_ADDR"},
	{0x68, "NM_QUERY_NV_CNFG"},
	{0x69, "NM_UPDATE_GROUP_ADDR"},
	{0x6A, "NM_QUERY_DOMAIN"},
	{0x6B, "NM_UPDATE_NV_CNFG"},
	{0x6C, "NM_SET_NODE_MODE"},
	{0x6D, "NM_READ_MEMORY"},
	{0x6E, "NM_WRITE_MEMORY"},
	{0x6F, "NM_CHECKSUM_RECALC"},
	{0x70, "NM_WINK"},
	{0x71, "NM_MEMORY_REFRESH"},
	{0x72, "NM_QUERY_SNVT"},
	{0x73, "NM_NV_FETCH"},
	{0x7F, "NM_MANUAL_SERVICE_REQUEST"},
	{ 0, NULL}
};

static const value_string nd_code_vs[]=
{
	{0x51, "ND_QUERY_STATUS"},
	{0x52, "ND_PROXY_COMMAND"},
	{0x53, "ND_CLEAR_STATUS"},
	{0x54, "ND_QUERY_XCVR"},
	{0, NULL}
};

static gint hf_lon_ppdu			= -1;
static gint hf_lon_ppdu_prio		= -1;
static gint hf_lon_ppdu_alt		= -1;
static gint hf_lon_ppdu_deltabl		= -1;
static gint hf_lon_npdu			= -1;
static gint hf_lon_npdu_version		= -1;
static gint hf_lon_npdu_pdu_fmt		= -1;
static gint hf_lon_npdu_addr_fmt	= -1;
static gint hf_lon_npdu_dom_len		= -1;
static gint hf_lon_addr_srcsub		= -1;
static gint hf_lon_addr_srcnode		= -1;
static gint hf_lon_addr_dstsub		= -1;
static gint hf_lon_addr_dstgrp		= -1;
static gint hf_lon_addr_dstnode		= -1;
static gint hf_lon_addr_grp		= -1;
static gint hf_lon_addr_grpmem		= -1;
static gint hf_lon_addr_uid		= -1;
static gint hf_lon_name			= -1;
static gint hf_lon_domain		= -1;
static gint hf_lon_tpdu			= -1;
static gint hf_lon_auth			= -1;
static gint hf_lon_tpdu_tpdu_type	= -1;
static gint hf_lon_trans_no		= -1;
static gint hf_lon_spdu			= -1;
static gint hf_lon_spdu_spdu_type	= -1;
static gint hf_lon_mlen			= -1;
static gint hf_lon_mlist		= -1;
static gint hf_lon_authpdu		= -1;
static gint hf_lon_authpdu_fmt		= -1;
static gint hf_lon_authpdu_authpdu_type	= -1;
static gint hf_lon_nv_dir		= -1;
static gint hf_lon_nv_selector		= -1;
static gint hf_lon_app_code		= -1;
static gint hf_lon_nm_code		= -1;
static gint hf_lon_nd_code		= -1;
static gint hf_lon_ff_code		= -1;
static gint hf_lon_nv			= -1;
static gint hf_lon_app			= -1;
static gint hf_lon_nm			= -1;
static gint hf_lon_nd			= -1;
static gint hf_lon_ff			= -1;
/* static gint hf_lon_checksum		= -1; */
static gint proto_lon			= -1;


static gint ett_lon                     = -1;
static gint ett_ppdu			= -1;
static gint ett_npdu			= -1;
static gint ett_tpdu                    = -1;
static gint ett_spdu                    = -1;
static gint ett_authpdu                 = -1;
static gint ett_apdu                    = -1;
static gint ett_nv                      = -1;
static gint ett_app                     = -1;
static gint ett_nm                      = -1;
static gint ett_nd                      = -1;
static gint ett_ff                      = -1;

static gint ett_address			= -1;

static expert_field ei_lon_tpdu_tpdu_type_unknown = EI_INIT;
static expert_field ei_lon_tpdu_spdu_type_unknown = EI_INIT;
static expert_field ei_lon_tpdu_authpdu_type_unknown = EI_INIT;
static expert_field ei_lon_tpdu_apdu_dest_type = EI_INIT;

static gint dissect_apdu(proto_tree *tree, packet_info *pinfo, tvbuff_t *tvb,
		gint offset);

static gint
dissect_lon(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
	gint offset = 0;

	gint pdu_fmt, addr_fmt, dom_len, pdutype, length;
	gint addr_a;

	proto_tree *ti;
	proto_tree *lon_tree;
	gint npdu, type;

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

	npdu = tvb_get_guint8(tvb, 0);
	type = tvb_get_guint8(tvb, 1);
	type = (type&0x30)>>4;
	col_add_fstr(pinfo->cinfo, COL_INFO,
			     "%sDelta_BL: %i Type: %s",
			     npdu&0x80?"Priority ":"",
			     npdu&0x3F,
			     val_to_str_const(type, pdu_fmt_vs, "Unknown"));

	ti = proto_tree_add_item(tree, proto_lon, tvb, offset, -1, ENC_NA);
	lon_tree = proto_item_add_subtree(ti, ett_lon);

	{
		static int * const ppdu_fields[] = {
			&hf_lon_ppdu_prio,
			&hf_lon_ppdu_alt,
			&hf_lon_ppdu_deltabl,
			NULL
		};
		proto_tree_add_bitmask(lon_tree, tvb, offset, hf_lon_ppdu,
					ett_ppdu, ppdu_fields, ENC_BIG_ENDIAN);
		offset++;
	}
	{
		static int * const npdu_fields[] = {
			&hf_lon_npdu_version,
			&hf_lon_npdu_pdu_fmt,
			&hf_lon_npdu_addr_fmt,
			&hf_lon_npdu_dom_len,
			NULL
		};
		proto_tree_add_bitmask(lon_tree, tvb, offset, hf_lon_npdu,
					ett_npdu, npdu_fields, ENC_BIG_ENDIAN);

		pdu_fmt  = (tvb_get_guint8(tvb, offset) >> 4) & 0x03;
		addr_fmt = (tvb_get_guint8(tvb, offset) >> 2) & 0x03;
		dom_len  = tvb_get_guint8(tvb, offset) & 0x03;
		offset++;
	}
	/* Address part */
	switch(addr_fmt)
	{
	case 0: /* Broadcast */
		ti = proto_tree_add_subtree(lon_tree, tvb, offset, 3, ett_address, NULL, "Address type 0 (broadcast)");
		proto_tree_add_item(ti, hf_lon_addr_srcsub, tvb, offset, 1, ENC_BIG_ENDIAN);
		proto_tree_add_item(ti, hf_lon_addr_srcnode, tvb, offset+1, 1, ENC_BIG_ENDIAN);
		proto_tree_add_item(ti, hf_lon_addr_dstsub, tvb, offset+2, 1, ENC_BIG_ENDIAN);
		offset += 3;
		break;
	case 1: /* Multicast */
		ti = proto_tree_add_subtree(lon_tree, tvb, offset, 3, ett_address, NULL, "Address type 1 (multicast)");
		proto_tree_add_item(ti, hf_lon_addr_srcsub, tvb, offset, 1, ENC_BIG_ENDIAN);
		proto_tree_add_item(ti, hf_lon_addr_srcnode, tvb, offset+1, 1, ENC_BIG_ENDIAN);
		proto_tree_add_item(ti, hf_lon_addr_dstgrp, tvb, offset+2, 1, ENC_BIG_ENDIAN);
		offset += 3;
		break;
	case 2: /* Unicast/Multicast */
		addr_a = tvb_get_guint8(tvb, offset+1) >> 7;
		if (addr_a) { /* Type 2a */
			ti = proto_tree_add_subtree(lon_tree, tvb, offset, 4, ett_address, NULL, "Address type 2a (unicast)");
			proto_tree_add_item(ti, hf_lon_addr_srcsub, tvb, offset, 1, ENC_BIG_ENDIAN);
			proto_tree_add_item(ti, hf_lon_addr_srcnode, tvb, offset+1, 1, ENC_BIG_ENDIAN);
			proto_tree_add_item(ti, hf_lon_addr_dstsub, tvb, offset+2, 1, ENC_BIG_ENDIAN);
			proto_tree_add_item(ti, hf_lon_addr_dstnode, tvb, offset+3, 1, ENC_BIG_ENDIAN);
			offset += 4;
		} else { /* Type 2b */
			ti = proto_tree_add_subtree(lon_tree, tvb, offset, 6, ett_address, NULL, "Address type 2b (multicast)");
			proto_tree_add_item(ti, hf_lon_addr_srcsub, tvb, offset, 1, ENC_BIG_ENDIAN);
			proto_tree_add_item(ti, hf_lon_addr_srcnode, tvb, offset+1, 1, ENC_BIG_ENDIAN);
			proto_tree_add_item(ti, hf_lon_addr_dstgrp, tvb, offset+2, 1, ENC_BIG_ENDIAN);
			proto_tree_add_item(ti, hf_lon_addr_dstnode, tvb, offset+3, 1, ENC_BIG_ENDIAN);
			proto_tree_add_item(ti, hf_lon_addr_grp, tvb, offset+4, 1, ENC_BIG_ENDIAN);
			proto_tree_add_item(ti, hf_lon_addr_grpmem, tvb, offset+5, 1, ENC_BIG_ENDIAN);
			offset += 6;
		}
		break;
	case 3: /* UID */
		ti = proto_tree_add_subtree(lon_tree, tvb, offset, 9, ett_address, NULL, "Address type 3 (UID)");
		proto_tree_add_item(ti, hf_lon_addr_srcsub, tvb, offset, 1, ENC_BIG_ENDIAN);
		proto_tree_add_item(ti, hf_lon_addr_srcnode, tvb, offset+1, 1, ENC_BIG_ENDIAN);
		proto_tree_add_item(ti, hf_lon_addr_dstsub, tvb, offset+2, 1, ENC_BIG_ENDIAN);
		proto_tree_add_item(ti, hf_lon_addr_uid, tvb, offset+3, 6, ENC_NA);
		offset += 9;
		break;
	}
	/* END Address part */
	/* Domain */
	switch(dom_len)
	{
	case 0: /* Domain-wide */
		proto_tree_add_bytes_format(lon_tree, hf_lon_domain, tvb, offset, 0, NULL, "Domain wide addressing");
		break;
	case 1:
		proto_tree_add_item(lon_tree, hf_lon_domain, tvb, offset, 1, ENC_NA);
		offset++;
		break;
	case 2:
		proto_tree_add_item(lon_tree, hf_lon_domain, tvb, offset, 3, ENC_NA);
		offset += 3;
		break;
	case 3:
		proto_tree_add_item(lon_tree, hf_lon_domain, tvb, offset, 6, ENC_NA);
		offset += 6;
		break;
	}
	/* END Domain */
	/* *PDU */
	switch(pdu_fmt)
	{
	case 0:        /* TPDU */
		{
		static int * const tpdu_fields[] = {
			&hf_lon_auth,
			&hf_lon_tpdu_tpdu_type,
			&hf_lon_trans_no,
			NULL
		};
		proto_tree_add_bitmask(lon_tree, tvb, offset, hf_lon_tpdu,
					ett_tpdu, tpdu_fields, ENC_BIG_ENDIAN);

		pdutype = (tvb_get_guint8(tvb, offset)>>4)& 0x07;
		offset++;
		switch(pdutype)
		{
		case 0:
		case 1: /* ACKD and UnACKD_RPT */
			offset += dissect_apdu(lon_tree, pinfo, tvb, offset);
			break;
		case 2: /* ACK */
			break;
		case 4: /* REMINDER */
			length = tvb_get_guint8(tvb, offset);
			proto_tree_add_item(lon_tree, hf_lon_mlen, tvb, offset, 1, ENC_BIG_ENDIAN);
			offset++;
			proto_tree_add_item(lon_tree, hf_lon_mlist, tvb, offset, length, ENC_BIG_ENDIAN);
			offset += length;
			break;
		case 5: /* REM/MSG */
			length = tvb_get_guint8(tvb, offset);
			proto_tree_add_item(lon_tree, hf_lon_mlen, tvb, offset, 1, ENC_BIG_ENDIAN);
			offset++;
			if (length > 0)
				proto_tree_add_item(lon_tree, hf_lon_mlist, tvb, offset, length, ENC_BIG_ENDIAN);
			offset += length;
			offset += dissect_apdu(lon_tree, pinfo, tvb, offset);
			break;
		default:
			expert_add_info_format(pinfo, lon_tree, &ei_lon_tpdu_tpdu_type_unknown, "Unexpected TPDU type %i", pdutype);
			break;
		}
		}
		break;
	case 1: /* SPDU */
		{
		static int * const spdu_fields[] = {
			&hf_lon_auth,
			&hf_lon_spdu_spdu_type,
			&hf_lon_trans_no,
			NULL
		};
		proto_tree_add_bitmask(lon_tree, tvb, offset, hf_lon_spdu,
					ett_spdu, spdu_fields, ENC_BIG_ENDIAN);
		pdutype = (tvb_get_guint8(tvb, offset)>>4)& 0x07;
		offset++;
		switch(pdutype)
		{
		case 0: /* REQUEST */
			offset += dissect_apdu(lon_tree, pinfo, tvb, offset);
			break;
		case 2: /* RESPONSE */
			offset += dissect_apdu(lon_tree, pinfo, tvb, offset);
			break;
		case 4: /* REMINDER */
			length = tvb_get_guint8(tvb, offset);
			proto_tree_add_item(lon_tree, hf_lon_mlen, tvb, offset, 1, ENC_BIG_ENDIAN);
			offset++;
			proto_tree_add_item(lon_tree, hf_lon_mlist, tvb, offset, length, ENC_BIG_ENDIAN);
			offset += length;
			break;
		case 5: /* REM/MSG */
			length = tvb_get_guint8(tvb, offset);
			proto_tree_add_item(lon_tree, hf_lon_mlen, tvb, offset, 1, ENC_BIG_ENDIAN);
			offset++;
			if (length > 0)
				proto_tree_add_item(lon_tree, hf_lon_mlist, tvb, offset, length, ENC_BIG_ENDIAN);
			offset += length;
			offset += dissect_apdu(lon_tree, pinfo, tvb, offset);
			break;
		default:
			expert_add_info_format(pinfo, lon_tree, &ei_lon_tpdu_spdu_type_unknown, "Unexpected SPDU type %i", pdutype);
			break;
		}
		}
		break;
	case 2: /* AuthPDU */
		{
		static int * const authpdu_fields[] = {
			&hf_lon_authpdu_fmt,
			&hf_lon_authpdu_authpdu_type,
			&hf_lon_trans_no,
			NULL
		};
		proto_tree_add_bitmask(lon_tree, tvb, offset, hf_lon_authpdu,
					ett_authpdu, authpdu_fields, ENC_BIG_ENDIAN);

		pdutype = (tvb_get_guint8(tvb, offset)>>4)& 0x03;
		offset++;
		switch(pdutype)
		{
		case 0: /* CHALLENGE */
		case 2: /* REPLY */
			offset += 9;
			break;
		default:
			expert_add_info_format(pinfo, lon_tree, &ei_lon_tpdu_authpdu_type_unknown, "Unexpected AuthPDU type %i", pdutype);
			break;
		}
		}
		break;
	case 3: /* APDU */
		offset += dissect_apdu(lon_tree, pinfo, tvb, offset);
		break;
	}
	/* END *PDU */

	return offset;
}

static gint
dissect_apdu(proto_tree *tree, packet_info *pinfo, tvbuff_t *tvb,
		gint offset)
{
	tvbuff_t *next_tvb;
	gint old_offset = offset, dest_type;

	dest_type = tvb_get_guint8(tvb, offset);

	if ((dest_type&0x80) == 0x80) { /* Network variable */
		static int * const nv_fields[] = {
			&hf_lon_nv_dir,
			&hf_lon_nv_selector,
			NULL
		};
		proto_tree_add_bitmask(tree, tvb, offset, hf_lon_nv,
					ett_nv, nv_fields, ENC_BIG_ENDIAN);
		offset += 2;
	} else if ((dest_type&0xc0) == 0) { /* Application */
		static int * const app_fields[] = {
			&hf_lon_app_code,
			NULL
		};
		proto_tree_add_bitmask(tree, tvb, offset, hf_lon_app,
					ett_app, app_fields, ENC_BIG_ENDIAN);
		offset++;
	} else if ((dest_type&0xe0) == 0x60) { /* Network Management */
		static int * const nm_fields[] = {
			&hf_lon_nm_code,
			NULL
		};
		proto_tree_add_bitmask(tree, tvb, offset, hf_lon_nm,
					ett_nm, nm_fields, ENC_BIG_ENDIAN);
		offset++;

		if (dest_type == 0x7F) {
			proto_tree_add_item(tree, hf_lon_addr_uid, tvb, offset, 6, ENC_NA);
			offset += 6;
			proto_tree_add_item(tree, hf_lon_name, tvb, offset, 8, ENC_NA);
			offset += 8;
		}

	} else if ((dest_type&0xf0) == 0x50) { /* Network Diagnostic */
		static int * const nd_fields[] = {
			&hf_lon_nd_code,
			NULL
		};
		proto_tree_add_bitmask(tree, tvb, offset, hf_lon_nd,
					ett_nd, nd_fields, ENC_BIG_ENDIAN);
		offset++;
	} else if ((dest_type&0xf0) == 0x40) { /* Foreign Frame */
		static int * const ff_fields[] = {
			&hf_lon_ff_code,
			NULL
		};
		proto_tree_add_bitmask(tree, tvb, offset, hf_lon_ff,
					ett_ff, ff_fields, ENC_BIG_ENDIAN);
		offset++;
	} else { /* Shouldn't get here */
		expert_add_info_format(pinfo, tree, &ei_lon_tpdu_apdu_dest_type, "Malformed APDU destin&type %i", dest_type);
	}

	next_tvb = tvb_new_subset_remaining(tvb, offset);

	return offset - old_offset + call_data_dissector(next_tvb, pinfo, tree);
}

void
proto_register_lon(void)
{
	static hf_register_info hf[] =
	{
		{&hf_lon_ppdu,
			{"PPDU", "lon.ppdu",
			FT_UINT8, BASE_HEX, NULL, 0,
			NULL, HFILL }
		},
		{&hf_lon_ppdu_prio,
			{"Priority", "lon.prio",
			FT_UINT8, BASE_DEC, NULL, 0x80,
			"Priority packet", HFILL }
		},
		{&hf_lon_ppdu_alt,
			{"Alt path", "lon.alt_path",
			FT_UINT8, BASE_DEC, NULL, 0x40,
			"Alternate path", HFILL }
		},
		{&hf_lon_ppdu_deltabl,
			{"Delta BL", "lon.delta_bl",
			FT_UINT8, BASE_DEC, NULL, 0x3f,
			"How many packets to expect from this one", HFILL }
		},
		{&hf_lon_npdu,
			{"NPDU", "lon.npdu",
			FT_UINT8, BASE_DEC, NULL, 0,
			NULL, HFILL }
		},
		{&hf_lon_npdu_version,
			{"version", "lon.vers",
			FT_UINT8, BASE_HEX, NULL, 0xc0,
			"LON protocol version", HFILL }
		},
		{&hf_lon_npdu_pdu_fmt,
			{"PDU format", "lon.pdufmt",
			FT_UINT8, BASE_HEX, VALS(pdu_fmt_vs), 0x30,
			NULL, HFILL }
		},
		{&hf_lon_npdu_addr_fmt,
			{"Address format", "lon.addrfmt",
			FT_UINT8, BASE_HEX, VALS(addr_fmt_vs), 0x0c,
			NULL, HFILL }
		},
		{&hf_lon_npdu_dom_len,
			{"Domain length", "lon.domainlen",
			FT_UINT8, BASE_HEX, VALS(domain_length_vs), 0x03,
			NULL, HFILL }
		},
		{&hf_lon_addr_srcsub,
			{"Source subnet", "lon.srcnet",
			FT_UINT8, BASE_HEX, NULL, 0,
			NULL, HFILL }
		},
		{&hf_lon_addr_srcnode,
			{"Source node", "lon.srcnode",
			FT_UINT8, BASE_HEX, NULL, 0x7f,
			NULL, HFILL }
		},
		{&hf_lon_addr_dstsub,
			{"Destination subnet", "lon.dstnet",
			FT_UINT8, BASE_HEX, NULL, 0,
			NULL, HFILL }
		},
		{&hf_lon_addr_dstgrp,
			{"Destination group", "lon.dstgrp",
			FT_UINT8, BASE_HEX, NULL, 0,
			NULL, HFILL }
		},
		{&hf_lon_addr_dstnode,
			{"Destination node", "lon.dstnode",
			FT_UINT8, BASE_HEX, NULL, 0x7f,
			NULL, HFILL }
		},
		{&hf_lon_addr_grp,
			{"Group", "lon.grp",
			FT_UINT8, BASE_HEX, NULL, 0,
			NULL, HFILL }
		},
		{&hf_lon_addr_grpmem,
			{"Group member", "lon.grpmem",
			FT_UINT8, BASE_HEX, NULL, 0,
			NULL, HFILL }
		},
		{&hf_lon_addr_uid,
			{"Unique node ID", "lon.uid",
			FT_BYTES, BASE_NONE, NULL, 0,
			NULL, HFILL }
		},
		{&hf_lon_domain,
			{"Domain", "lon.domain",
			FT_BYTES, BASE_NONE, NULL , 0,
			NULL, HFILL }
		},
		{&hf_lon_tpdu,
			{"TPDU", "lon.tpdu",
			FT_UINT8, BASE_HEX, NULL, 0,
			NULL, HFILL }
		},
		{&hf_lon_auth,
			{"Auth", "lon.auth",
			FT_UINT8, BASE_HEX, NULL, 0x80,
			NULL, HFILL }
		},
		{&hf_lon_tpdu_tpdu_type,
			{"TPDU type", "lon.tpdu_type",
			FT_UINT8, BASE_HEX, VALS(tpdu_type_vs), 0x70,
			NULL, HFILL }
		},
		{&hf_lon_trans_no,
			{"Transaction number", "lon.trans_no",
			FT_UINT8, BASE_HEX, NULL, 0x0f,
			NULL, HFILL }
		},
		{&hf_lon_spdu,
			{"SPDU", "lon.spdu",
			FT_UINT8, BASE_HEX, NULL, 0,
			NULL, HFILL }
		},
		{&hf_lon_spdu_spdu_type,
			{"SPDU type", "lon.spdu_type",
			FT_UINT8, BASE_HEX, VALS(spdu_type_vs), 0x70,
			NULL, HFILL }
		},
		{&hf_lon_mlen,
			{"Length of M_List", "lon.spdu.mlen",
			FT_UINT8, BASE_HEX, NULL, 0,
			NULL, HFILL }
		},
		{&hf_lon_mlist,
			{"M_List", "lon.spdu.mlist",
			FT_UINT8, BASE_HEX, NULL, 0,
			NULL, HFILL }
		},
		{&hf_lon_authpdu,
			{"AuthPDU", "lon.authpdu",
			FT_UINT8, BASE_HEX, NULL, 0,
			NULL, HFILL }
		},
		{&hf_lon_authpdu_fmt,
			{"FMT (same as AddrFmt)", "lon.authpdu_addrfmt",
			FT_UINT8, BASE_HEX, NULL, 0xc,
			NULL, HFILL }
		},
		{&hf_lon_authpdu_authpdu_type,
			{"AuthPDU type", "lon.authpdu_type",
			FT_UINT8, BASE_HEX, VALS(authpdu_type_vs), 0x2,
			NULL, HFILL }
		},
		{&hf_lon_nv,
			{"Network Variable", "lon.nv",
			FT_UINT16, BASE_HEX, NULL, 0,
			NULL, HFILL }
		},
		{&hf_lon_nv_dir,
			{"NV direction", "lon.nv.dir",
			FT_UINT16, BASE_HEX, NULL, 0x4000,
			NULL, HFILL }
		},
		{&hf_lon_nv_selector,
			{"NV selector", "lon.nv.selector",
			FT_UINT16, BASE_HEX, NULL, 0x3fff,
			NULL, HFILL }
		},
		{&hf_lon_app,
			{"Application", "lon.application",
			FT_UINT8, BASE_HEX, NULL, 0,
			NULL, HFILL }
		},
		{&hf_lon_app_code,
			{"Code", "lon.code",
			FT_UINT8, BASE_HEX, NULL, 0x3f,
			NULL, HFILL }
		},
		{&hf_lon_nm,
			{"Network Management", "lon.nm",
			FT_UINT8, BASE_HEX, NULL, 0,
			NULL, HFILL }
		},
		{&hf_lon_nm_code,
			{"Code", "lon.code",
			FT_UINT8, BASE_HEX, VALS(nm_code_vs), 0xff,
			NULL, HFILL }
		},
		{&hf_lon_nd,
			{"Network Diagnostic", "lon.nd",
			FT_UINT8, BASE_HEX, NULL, 0,
			NULL, HFILL }
		},
		{&hf_lon_nd_code,
			{"Code", "lon.code",
			FT_UINT8, BASE_HEX, VALS(nd_code_vs), 0xff,
			NULL, HFILL }
		},
		{&hf_lon_ff,
			{"Foreign Frame", "lon.ff",
			FT_UINT8, BASE_HEX, NULL, 0,
			NULL, HFILL }
		},
		{&hf_lon_ff_code,
			{"Code", "lon.code",
			FT_UINT8, BASE_HEX, NULL, 0x0f,
			NULL, HFILL }
		},
		{&hf_lon_name,
			{"Node name", "lon.name",
			FT_BYTES, BASE_NONE, NULL, 0,
			NULL, HFILL }
		},
#if 0
		{&hf_lon_checksum,
			{"Checksum", "lon.chksum",
			FT_BYTES, BASE_NONE, NULL, 0,
			NULL, HFILL }
		}
#endif
	};

	static gint *ett[] =
	{
		&ett_lon,
		&ett_address,
		&ett_ppdu,
		&ett_npdu,
		&ett_tpdu,
		&ett_spdu,
		&ett_authpdu,
		&ett_apdu,
		&ett_nv,
		&ett_app,
		&ett_nm,
		&ett_nd,
		&ett_ff
	};

	static ei_register_info ei[] = {
		{ &ei_lon_tpdu_tpdu_type_unknown, { "lon.tpdu_type.unknown", PI_PROTOCOL, PI_WARN, "Unexpected TPDU type", EXPFILL }},
		{ &ei_lon_tpdu_spdu_type_unknown, { "lon.spdu_type.unknown", PI_PROTOCOL, PI_WARN, "Unexpected SPDU type", EXPFILL }},
		{ &ei_lon_tpdu_authpdu_type_unknown, { "lon.authpdu_type.unknown", PI_PROTOCOL, PI_WARN, "Unexpected AuthPDU type", EXPFILL }},
		{ &ei_lon_tpdu_apdu_dest_type, { "lon.authpdu_dest_type.unknown", PI_PROTOCOL, PI_WARN, "Malformed APDU destin&type", EXPFILL }},
	};

	expert_module_t* expert_lon;

	proto_lon = proto_register_protocol("Local Operating Network",
			"LON", "lon");

	proto_register_field_array (proto_lon, hf, array_length (hf));
	proto_register_subtree_array (ett, array_length (ett));
	expert_lon = expert_register_protocol(proto_lon);
	expert_register_field_array(expert_lon, ei, array_length(ei));
}


void
proto_reg_handoff_lon(void)
{
	dissector_handle_t lon_handle;

	lon_handle = create_dissector_handle(dissect_lon, proto_lon);

	dissector_add_uint("cnip.protocol", 0, lon_handle);
}

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