/* packet-iec104.c
 * Routines for IEC-60870-5-101 & 104 Protocol disassembly
 *
 * Copyright (c) 2008 by Joan Ramio <joan@ramio.cat>
 * Joan is a masculine catalan name. Search the Internet for Joan Pujol (alias Garbo).
 *
 * Copyright (c) 2009 by Kjell Hultman <kjell.hultman@gmail.com>
 * Added dissection of signal (ASDU) information.
 * Kjell is also a masculine name, but a Scandinavian one.
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1999 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "config.h"

#include <math.h> /* floor */

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

void proto_register_iec60870_104(void);
void proto_reg_handoff_iec60870_104(void);

void proto_register_iec60870_101(void);
void proto_reg_handoff_iec60870_101(void);

void proto_register_iec60870_asdu(void);

static dissector_handle_t iec60870_asdu_handle;

/* the asdu header structure */
struct asduheader {
	guint32 Addr;
	guint8 OA;
	guint8 TypeId;
	guint8 TNCause;
	guint32 IOA;
	guint8 NumIx;
	guint8 SQ;
	guint8 DataLength;
};

struct asdu_parms {
	guint cot_len;
	guint asdu_addr_len;
	guint ioa_len;
};

/* ASDU command value/status structure */
typedef struct {
	gboolean OFF;
	gboolean ON;

	gboolean UP;
	gboolean DOWN;

	/* QOC qualifier-bits */
	guint16  QU;      /* qualifier-value */
	gboolean ZeroP;   /* No pulse */
	gboolean ShortP;  /* Short Pulse */
	gboolean LongP;   /* Long Pulse */
	gboolean Persist; /* Persistent output */
	gboolean SE;      /* Select (1) / Execute (0) */
} td_CmdInfo;

#define IEC104_PORT     2404

/* Define the iec101/104 protos */
static int proto_iec60870_101  = -1;
static int proto_iec60870_104  = -1;
static int proto_iec60870_asdu = -1;

/* Protocol constants */
#define APCI_START	0x68
#define APCI_LEN	6
#define APCI_START_LEN	2
#define APCI_DATA_LEN	(APCI_LEN - APCI_START_LEN)
#define APDU_MIN_LEN	4
#define APDU_MAX_LEN	253

/* ASDU_HEAD_LEN: Includes Asdu head and first IOA */
#define ASDU_HEAD_LEN	9
#define F_TEST  0x80
#define F_NEGA  0x40
#define F_CAUSE 0x3F
#define F_SQ    0x80

/* APCI types */

/* Type I is only lowest bit set to 0 */
#define I_TYPE		0
#define S_TYPE		1
#define U_TYPE		3
#define APCI_TYPE_UNKNOWN 4

static const value_string apci_types [] = {
	{ I_TYPE,		"I" },
	{ S_TYPE,		"S" },
	{ U_TYPE,		"U" },
	{ 0, NULL }
};

/* Constants relative to the filed, independent of the field position in the byte */
/* U (Unnumbered) constants */
#define U_STARTDT_ACT 		0x01
#define U_STARTDT_CON	 	0x02
#define U_STOPDT_ACT 		0x04
#define U_STOPDT_CON	 	0x08
#define U_TESTFR_ACT 		0x10
#define U_TESTFR_CON	 	0x20
static const value_string u_types[] = {
	{ U_STARTDT_ACT,		"STARTDT act" },
	{ U_STARTDT_CON, 		"STARTDT con" },
	{ U_STOPDT_ACT,			"STOPDT act" },
	{ U_STOPDT_CON, 		"STOPDT con" },
	{ U_TESTFR_ACT, 		"TESTFR act" },
	{ U_TESTFR_CON, 		"TESTFR con" },
	{ 0, NULL }
};

/* ASDU types (TypeId) */
#define M_SP_NA_1  1    /* single-point information 								*/
#define M_SP_TA_1  2    /* single-point information with time tag 	 					*/
#define M_DP_NA_1  3    /* double-point information 								*/
#define M_DP_TA_1  4    /* double-point information with time tag 						*/
#define M_ST_NA_1  5    /* step position information 								*/
#define M_ST_TA_1  6    /* step position information with time tag 						*/
#define M_BO_NA_1  7    /* bitstring of 32 bits 								*/
#define M_BO_TA_1  8    /* bitstring of 32 bits with time tag 							*/
#define M_ME_NA_1  9    /* measured value, normalized value 							*/
#define M_ME_TA_1  10    /* measured value, normalized value with time tag 					*/
#define M_ME_NB_1  11    /* measured value, scaled value 							*/
#define M_ME_TB_1  12    /* measured value, scaled value with time tag 						*/
#define M_ME_NC_1  13    /* measured value, short floating point number 					*/
#define M_ME_TC_1  14    /* measured value, short floating point number with time tag 				*/
#define M_IT_NA_1  15    /* integrated totals 									*/
#define M_IT_TA_1  16    /* integrated totals with time tag 							*/
#define M_PS_NA_1  20    /* packed single-point information with status change detection 			*/
#define M_ME_ND_1  21    /* measured value, normalized value without quality descriptor 			*/
#define M_SP_TB_1  30    /* single-point information with time tag CP56Time2a 					*/
#define M_DP_TB_1  31    /* double-point information with time tag CP56Time2a 					*/
#define M_ST_TB_1  32    /* step position information with time tag CP56Time2a 					*/
#define M_BO_TB_1  33    /* bitstring of 32 bit with time tag CP56Time2a 					*/
#define M_ME_TD_1  34    /* measured value, normalized value with time tag CP56Time2a 				*/
#define M_ME_TE_1  35    /* measured value, scaled value with time tag CP56Time2a 				*/
#define M_ME_TF_1  36    /* measured value, short floating point number with time tag CP56Time2a 		*/
#define M_IT_TB_1  37    /* integrated totals with time tag CP56Time2a 						*/
#define M_EP_TD_1  38    /* event of protection equipment with time tag CP56Time2a 				*/
#define M_EP_TE_1  39    /* packed start events of protection equipment with time tag CP56Time2a 		*/
#define M_EP_TF_1  40    /* packed output circuit information of protection equipment with time tag CP56Time2a 	*/
#define S_IT_TC_1  41    /* integrated totals containing time tagged security statistics			*/
#define C_SC_NA_1  45    /* single command 									*/
#define C_DC_NA_1  46    /* double command 									*/
#define C_RC_NA_1  47    /* regulating step command 								*/
#define C_SE_NA_1  48    /* set point command, normalized value 						*/
#define C_SE_NB_1  49    /* set point command, scaled value 							*/
#define C_SE_NC_1  50    /* set point command, short floating point number 					*/
#define C_BO_NA_1  51    /* bitstring of 32 bits 								*/
#define C_SC_TA_1  58    /* single command with time tag CP56Time2a 						*/
#define C_DC_TA_1  59    /* double command with time tag CP56Time2a 						*/
#define C_RC_TA_1  60    /* regulating step command with time tag CP56Time2a 					*/
#define C_SE_TA_1  61    /* set point command, normalized value with time tag CP56Time2a 			*/
#define C_SE_TB_1  62    /* set point command, scaled value with time tag CP56Time2a 				*/
#define C_SE_TC_1  63    /* set point command, short floating-point number with time tag CP56Time2a 		*/
#define C_BO_TA_1  64    /* bitstring of 32 bits with time tag CP56Time2a 					*/
#define M_EI_NA_1  70    /* end of initialization 								*/
#define S_CH_NA_1  81    /* authentication challenge								*/
#define S_RP_NA_1  82    /* authentication reply								*/
#define S_AR_NA_1  83    /* aggressive mode authentication request session key status request			*/
#define S_KR_NA_1  84    /* session key status request								*/
#define S_KS_NA_1  85    /* session key status									*/
#define S_KC_NA_1  86    /* session key change									*/
#define S_ER_NA_1  87    /* authentication error								*/
#define S_US_NA_1  90    /* user status change									*/
#define S_UQ_NA_1  91    /* update key change request								*/
#define S_UR_NA_1  92    /* update key change reply								*/
#define S_UK_NA_1  93    /* update key change symmetric								*/
#define S_UA_NA_1  94    /* update key change asymmetric							*/
#define S_UC_NA_1  95    /* update key change confirmation							*/
#define C_IC_NA_1  100    /* interrogation command 								*/
#define C_CI_NA_1  101    /* counter interrogation command 							*/
#define C_RD_NA_1  102    /* read command 									*/
#define C_CS_NA_1  103    /* clock synchronization command 							*/
#define C_RP_NA_1  105    /* reset process command 								*/
#define C_TS_TA_1  107    /* test command with time tag CP56Time2a 						*/
#define P_ME_NA_1  110    /* parameter of measured value, normalized value 					*/
#define P_ME_NB_1  111    /* parameter of measured value, scaled value 						*/
#define P_ME_NC_1  112    /* parameter of measured value, short floating-point number 				*/
#define P_AC_NA_1  113    /* parameter activation 								*/
#define F_FR_NA_1  120    /* file ready 									*/
#define F_SR_NA_1  121    /* section ready 									*/
#define F_SC_NA_1  122    /* call directory, select file, call file, call section 				*/
#define F_LS_NA_1  123    /* last section, last segment 							*/
#define F_AF_NA_1  124    /* ack file, ack section 								*/
#define F_SG_NA_1  125    /* segment 										*/
#define F_DR_TA_1  126    /* directory 										*/
#define F_SC_NB_1  127    /* Query Log - Request archive file 							*/
static const value_string asdu_types [] = {
	{  M_SP_NA_1,		"M_SP_NA_1" },
	{  M_SP_TA_1,		"M_SP_TA_1" },
	{  M_DP_NA_1,		"M_DP_NA_1" },
	{  M_DP_TA_1,		"M_DP_TA_1" },
	{  M_ST_NA_1,		"M_ST_NA_1" },
	{  M_ST_TA_1,		"M_ST_TA_1" },
	{  M_BO_NA_1,		"M_BO_NA_1" },
	{  M_BO_TA_1,		"M_BO_TA_1" },
	{  M_ME_NA_1,		"M_ME_NA_1" },
	{  M_ME_TA_1,		"M_ME_TA_1" },
	{  M_ME_NB_1,		"M_ME_NB_1" },
	{  M_ME_TB_1,		"M_ME_TB_1" },
	{  M_ME_NC_1,		"M_ME_NC_1" },
	{  M_ME_TC_1,		"M_ME_TC_1" },
	{  M_IT_NA_1,		"M_IT_NA_1" },
	{  M_IT_TA_1,		"M_IT_TA_1" },
	{  M_PS_NA_1,		"M_PS_NA_1" },
	{  M_ME_ND_1,		"M_ME_ND_1" },
	{  M_SP_TB_1,		"M_SP_TB_1" },
	{  M_DP_TB_1,		"M_DP_TB_1" },
	{  M_ST_TB_1,		"M_ST_TB_1" },
	{  M_BO_TB_1,		"M_BO_TB_1" },
	{  M_ME_TD_1,		"M_ME_TD_1" },
	{  M_ME_TE_1,		"M_ME_TE_1" },
	{  M_ME_TF_1,		"M_ME_TF_1" },
	{  M_IT_TB_1,		"M_IT_TB_1" },
	{  M_EP_TD_1,		"M_EP_TD_1" },
	{  M_EP_TE_1,		"M_EP_TE_1" },
	{  M_EP_TF_1,		"M_EP_TF_1" },
	{  S_IT_TC_1,		"S_IT_TC_1" },
	{  C_SC_NA_1,		"C_SC_NA_1" },
	{  C_DC_NA_1,		"C_DC_NA_1" },
	{  C_RC_NA_1,		"C_RC_NA_1" },
	{  C_SE_NA_1,		"C_SE_NA_1" },
	{  C_SE_NB_1,		"C_SE_NB_1" },
	{  C_SE_NC_1,		"C_SE_NC_1" },
	{  C_BO_NA_1,		"C_BO_NA_1" },
	{  C_SC_TA_1,		"C_SC_TA_1" },
	{  C_DC_TA_1,		"C_DC_TA_1" },
	{  C_RC_TA_1,		"C_RC_TA_1" },
	{  C_SE_TA_1,		"C_SE_TA_1" },
	{  C_SE_TB_1,		"C_SE_TB_1" },
	{  C_SE_TC_1,		"C_SE_TC_1" },
	{  C_BO_TA_1,		"C_BO_TA_1" },
	{  M_EI_NA_1,		"M_EI_NA_1" },
	{  S_CH_NA_1,		"S_CH_NA_1" },
	{  S_RP_NA_1,		"S_RP_NA_1" },
	{  S_AR_NA_1,		"S_AR_NA_1" },
	{  S_KR_NA_1,		"S_KR_NA_1" },
	{  S_KS_NA_1,		"S_KS_NA_1" },
	{  S_KC_NA_1,		"S_KC_NA_1" },
	{  S_ER_NA_1,		"S_ER_NA_1" },
	{  S_US_NA_1,		"S_US_NA_1" },
	{  S_UQ_NA_1,		"S_UQ_NA_1" },
	{  S_UR_NA_1,		"S_UR_NA_1" },
	{  S_UK_NA_1,		"S_UK_NA_1" },
	{  S_UA_NA_1,		"S_UA_NA_1" },
	{  S_UC_NA_1,		"S_UC_NA_1" },
	{  C_IC_NA_1,		"C_IC_NA_1" },
	{  C_CI_NA_1,		"C_CI_NA_1" },
	{  C_RD_NA_1,		"C_RD_NA_1" },
	{  C_CS_NA_1,		"C_CS_NA_1" },
	{  C_RP_NA_1,		"C_RP_NA_1" },
	{  C_TS_TA_1,		"C_TS_TA_1" },
	{  P_ME_NA_1,		"P_ME_NA_1" },
	{  P_ME_NB_1,		"P_ME_NB_1" },
	{  P_ME_NC_1,		"P_ME_NC_1" },
	{  P_AC_NA_1,		"P_AC_NA_1" },
	{  F_FR_NA_1,		"F_FR_NA_1" },
	{  F_SR_NA_1,		"F_SR_NA_1" },
	{  F_SC_NA_1,		"F_SC_NA_1" },
	{  F_LS_NA_1,		"F_LS_NA_1" },
	{  F_AF_NA_1,		"F_AF_NA_1" },
	{  F_SG_NA_1,		"F_SG_NA_1" },
	{  F_DR_TA_1,		"F_DR_TA_1" },
	{  F_SC_NB_1,		"F_SC_NB_1" },
	{ 0, NULL }
};

static const value_string asdu_lngtypes [] = {
	{  M_SP_NA_1,		"single-point information" },
	{  M_SP_TA_1,		"single-point information with time tag" },
	{  M_DP_NA_1,		"double-point information" },
	{  M_DP_TA_1,		"double-point information with time tag" },
	{  M_ST_NA_1,		"step position information" },
	{  M_ST_TA_1,		"step position information with time tag" },
	{  M_BO_NA_1,		"bitstring of 32 bits" },
	{  M_BO_TA_1,		"bitstring of 32 bits with time tag" },
	{  M_ME_NA_1,		"measured value, normalized value" },
	{  M_ME_TA_1,		"measured value, normalized value with time tag" },
	{  M_ME_NB_1,		"measured value, scaled value" },
	{  M_ME_TB_1,		"measured value, scaled value with time tag" },
	{  M_ME_NC_1,		"measured value, short floating point number" },
	{  M_ME_TC_1,		"measured value, short floating point number with time tag" },
	{  M_IT_NA_1,		"integrated totals" },
	{  M_IT_TA_1,		"integrated totals with time tag" },
	{  M_PS_NA_1,		"packed single-point information with status change detection" },
	{  M_ME_ND_1,		"measured value, normalized value without quality descriptor" },
	{  M_SP_TB_1,		"single-point information with time tag CP56Time2a" },
	{  M_DP_TB_1,		"double-point information with time tag CP56Time2a" },
	{  M_ST_TB_1,		"step position information with time tag CP56Time2a" },
	{  M_BO_TB_1,		"bitstring of 32 bit with time tag CP56Time2a" },
	{  M_ME_TD_1,		"measured value, normalized value with time tag CP56Time2a" },
	{  M_ME_TE_1,		"measured value, scaled value with time tag CP56Time2a" },
	{  M_ME_TF_1,		"measured value, short floating point number with time tag CP56Time2a" },
	{  M_IT_TB_1,		"integrated totals with time tag CP56Time2a" },
	{  M_EP_TD_1,		"event of protection equipment with time tag CP56Time2a" },
	{  M_EP_TE_1,		"packed start events of protection equipment with time tag CP56Time2a" },
	{  M_EP_TF_1,		"packed output circuit information of protection equipment with time tag CP56Time2a" },
	{  S_IT_TC_1,		"integrated totals containing time tagged security statistics" },
	{  C_SC_NA_1,		"single command" },
	{  C_DC_NA_1,		"double command" },
	{  C_RC_NA_1,		"regulating step command" },
	{  C_SE_NA_1,		"set point command, normalized value" },
	{  C_SE_NB_1,		"set point command, scaled value" },
	{  C_SE_NC_1,		"set point command, short floating point number" },
	{  C_BO_NA_1,		"bitstring of 32 bits" },
	{  C_SC_TA_1,		"single command with time tag CP56Time2a" },
	{  C_DC_TA_1,		"double command with time tag CP56Time2a" },
	{  C_RC_TA_1,		"regulating step command with time tag CP56Time2a" },
	{  C_SE_TA_1,		"set point command, normalized value with time tag CP56Time2a" },
	{  C_SE_TB_1,		"set point command, scaled value with time tag CP56Time2a" },
	{  C_SE_TC_1,		"set point command, short floating-point number with time tag CP56Time2a" },
	{  C_BO_TA_1,		"bitstring of 32 bits with time tag CP56Time2a" },
	{  M_EI_NA_1,		"end of initialization" },
	{  S_CH_NA_1,		"authentication challenge" },
	{  S_RP_NA_1,		"authentication reply" },
	{  S_AR_NA_1,		"aggressive mode authentication request session key status request" },
	{  S_KR_NA_1,		"session key status request" },
	{  S_KS_NA_1,		"session key status" },
	{  S_KC_NA_1,		"session key change" },
	{  S_ER_NA_1,		"authentication error" },
	{  S_US_NA_1,		"user status change" },
	{  S_UQ_NA_1,		"update key change request" },
	{  S_UR_NA_1,		"update key change reply" },
	{  S_UK_NA_1,		"update key change symmetric" },
	{  S_UA_NA_1,		"update key change asymmetric" },
	{  S_UC_NA_1,		"update key change confirmation" },
	{  C_IC_NA_1,		"interrogation command" },
	{  C_CI_NA_1,		"counter interrogation command" },
	{  C_RD_NA_1,		"read command" },
	{  C_CS_NA_1,		"clock synchronization command" },
	{  C_RP_NA_1,		"reset process command" },
	{  C_TS_TA_1,		"test command with time tag CP56Time2a" },
	{  P_ME_NA_1,		"parameter of measured value, normalized value" },
	{  P_ME_NB_1,		"parameter of measured value, scaled value" },
	{  P_ME_NC_1,		"parameter of measured value, short floating-point number" },
	{  P_AC_NA_1,		"parameter activation" },
	{  F_FR_NA_1,		"file ready" },
	{  F_SR_NA_1,		"section ready" },
	{  F_SC_NA_1,		"call directory, select file, call file, call section" },
	{  F_LS_NA_1,		"last section, last segment" },
	{  F_AF_NA_1,		"ack file, ack section" },
	{  F_SG_NA_1,		"segment" },
	{  F_DR_TA_1,		"directory" },
	{  F_SC_NB_1,		"Query Log - Request archive file" },
	{ 0, NULL }
};

typedef struct {
	guint8  value;
	guint8  length;
} td_asdu_length;

static const td_asdu_length asdu_length [] = {
	{  M_SP_NA_1,	 1 },
	{  M_SP_TA_1,	 4 },
	{  M_DP_NA_1,	 1 },
	{  M_DP_TA_1,	 4 },
	{  M_ST_NA_1,	 2 },
	{  M_ST_TA_1,	 5 },
	{  M_BO_NA_1,	 5 },
	{  M_BO_TA_1,	 8 },
	{  M_ME_NA_1,	 3 },
	{  M_ME_TA_1,	 6 },
	{  M_ME_NB_1,	 3 },
	{  M_ME_TB_1,	 6 },
	{  M_ME_NC_1,	 5 },
	{  M_ME_TC_1,	 8 },
	{  M_IT_NA_1,	 5 },
	{  M_IT_TA_1,	 8 },
	{  M_PS_NA_1,	 5 },
	{  M_ME_ND_1,	 2 },
	{  M_SP_TB_1,	 8 },
	{  M_DP_TB_1,	 8 },
	{  M_ST_TB_1,	 9 },
	{  M_BO_TB_1,	12 },
	{  M_ME_TD_1,	10 },
	{  M_ME_TE_1,	10 },
	{  M_ME_TF_1,	12 },
	{  M_IT_TB_1,	12 },
	{  M_EP_TD_1,	10 },
	{  M_EP_TE_1,	11 },
	{  M_EP_TF_1,	11 },
	{  S_IT_TC_1,    0 },
	{  C_SC_NA_1,	 1 },
	{  C_DC_NA_1,	 1 },
	{  C_RC_NA_1,	 1 },
	{  C_SE_NA_1,	 3 },
	{  C_SE_NB_1,	 3 },
	{  C_SE_NC_1,	 5 },
	{  C_BO_NA_1,	 4 },
	{  C_SC_TA_1,	 8 },
	{  C_DC_TA_1,	 8 },
	{  C_RC_TA_1,	 8 },
	{  C_SE_TA_1,	10 },
	{  C_SE_TB_1,	10 },
	{  C_SE_TC_1,	12 },
	{  C_BO_TA_1,	11 },
	{  M_EI_NA_1,	 1 },
	{  S_CH_NA_1,    0 },
	{  S_RP_NA_1,    0 },
	{  S_AR_NA_1,    0 },
	{  S_KR_NA_1,    0 },
	{  S_KS_NA_1,    0 },
	{  S_KC_NA_1,    0 },
	{  S_ER_NA_1,    0 },
	{  S_US_NA_1,    0 },
	{  S_UQ_NA_1,    0 },
	{  S_UR_NA_1,    0 },
	{  S_UK_NA_1,    0 },
	{  S_UA_NA_1,    0 },
	{  S_UC_NA_1,    0 },
	{  C_IC_NA_1,	 1 },
	{  C_CI_NA_1,	 1 },
	{  C_RD_NA_1,	 0 },
	{  C_CS_NA_1,	 7 },
	{  C_RP_NA_1,	 1 },
	{  C_TS_TA_1,	 9 },
	{  P_ME_NA_1,	 3 },
	{  P_ME_NB_1,	 3 },
	{  P_ME_NC_1,	 5 },
	{  P_AC_NA_1,	 1 },
	{  F_FR_NA_1,	 6 },
	{  F_SR_NA_1,	 7 },
	{  F_SC_NA_1,	 4 },
	{  F_LS_NA_1,	 5 },
	{  F_AF_NA_1,	 4 },
	{  F_SG_NA_1,	 0 },
	{  F_DR_TA_1,	13 },
	{  F_SC_NB_1,	16 },
	{ 0, 0 }
};

/* Cause of Transmission (CauseTx) */
#define Per_Cyc         1
#define Back            2
#define Spont           3
#define Init            4
#define Req             5
#define Act             6
#define ActCon          7
#define Deact           8
#define DeactCon        9
#define ActTerm         10
#define Retrem          11
#define Retloc          12
#define File            13
#define Auth            14
#define Seskey          15
#define Usrkey          16
#define Inrogen         20
#define Inro1           21
#define Inro2           22
#define Inro3           23
#define Inro4           24
#define Inro5           25
#define Inro6           26
#define Inro7           27
#define Inro8           28
#define Inro9           29
#define Inro10          30
#define Inro11          31
#define Inro12          32
#define Inro13          33
#define Inro14          34
#define Inro15          35
#define Inro16          36
#define Reqcogen        37
#define Reqco1          38
#define Reqco2          39
#define Reqco3          40
#define Reqco4          41
#define UkTypeId        44
#define UkCauseTx       45
#define UkComAdrASDU    46
#define UkIOA           47

static const value_string causetx_types [] = {
	{ Per_Cyc         ,"Per/Cyc" },
	{ Back            ,"Back" },
	{ Spont           ,"Spont" },
	{ Init            ,"Init" },
	{ Req             ,"Req" },
	{ Act             ,"Act" },
	{ ActCon          ,"ActCon" },
	{ Deact           ,"Deact" },
	{ DeactCon        ,"DeactCon" },
	{ ActTerm         ,"ActTerm" },
	{ Retrem          ,"Retrem" },
	{ Retloc          ,"Retloc" },
	{ File            ,"File" },
	{ Auth            ,"Auth" },
	{ Seskey          ,"Seskey" },
	{ Usrkey          ,"Usrkey" },
	{ Inrogen         ,"Inrogen" },
	{ Inro1           ,"Inro1" },
	{ Inro2           ,"Inro2" },
	{ Inro3           ,"Inro3" },
	{ Inro4           ,"Inro4" },
	{ Inro5           ,"Inro5" },
	{ Inro6           ,"Inro6" },
	{ Inro7           ,"Inro7" },
	{ Inro8           ,"Inro8" },
	{ Inro9           ,"Inro9" },
	{ Inro10          ,"Inro10" },
	{ Inro11          ,"Inro11" },
	{ Inro12          ,"Inro12" },
	{ Inro13          ,"Inro13" },
	{ Inro14          ,"Inro14" },
	{ Inro15          ,"Inro15" },
	{ Inro16          ,"Inro16" },
	{ Reqcogen        ,"Reqcogen" },
	{ Reqco1          ,"Reqco1" },
	{ Reqco2          ,"Reqco2" },
	{ Reqco3          ,"Reqco3" },
	{ Reqco4          ,"Reqco4" },
	{ UkTypeId        ,"UkTypeId" },
	{ UkCauseTx       ,"UkCauseTx" },
	{ UkComAdrASDU    ,"UkComAdrASDU" },
	{ UkIOA           ,"UkIOA" },
	{ 0, NULL }
};

static const value_string diq_types[] = {
	{ 0,		"Indeterminate or Intermediate" },
	{ 1,		"OFF" },
	{ 2,		"ON" },
	{ 3,		"Indeterminate" },
	{ 0, NULL }
};

static const value_string qos_qu_types[] = {
	{ 0,		"No pulse defined" },
	{ 1,		"Short Pulse" },
	{ 2,		"Long Pulse" },
	{ 3,		"Persistent Output" },
	{ 0, NULL }
};

static const value_string dco_on_types[] = {
	{ 0,		"(None)" },
	{ 1,		"OFF" },
	{ 2,		"ON" },
	{ 3,		"Error: On/Off not defined" },
	{ 0, NULL }
};

static const value_string rco_up_types[] = {
	{ 0,		"(None)" },
	{ 1,		"DOWN" },
	{ 2,		"UP" },
	{ 3,		"Error: Up/Down not defined" },
	{ 0, NULL }
};

static const value_string qpm_kpa_types[] = {
	{ 0,		"Not used" },
	{ 1,		"Threshold value" },
	{ 2,		"Smoothing factor (filter time constant)" },
	{ 0, NULL }
};

static const value_string qpm_lpc_types[] = {
	{ 0,		"No change" },
	{ 1,		"Change" },
	{ 0, NULL }
};

static const value_string qpm_pop_types[] = {
	{ 0,		"Operation" },
	{ 1,		"Not in operation" },
	{ 0, NULL }
};

static const value_string coi_r_types[] = {
	{ 0,		"Local power switch on" },
	{ 1,		"Local manual reset" },
	{ 2,		"Remote reset" },
	{ 0, NULL }
};

static const value_string qoi_r_types[] = {
	{ 0,		"Not specified" },
	{ 20,		"Station interrogation (global)" },
	{ 21,		"Group 1 interrogation" },
	{ 22,		"Group 2 interrogation" },
	{ 23,		"Group 3 interrogation" },
	{ 24,		"Group 4 interrogation" },
	{ 25,		"Group 5 interrogation" },
	{ 26,		"Group 6 interrogation" },
	{ 27,		"Group 7 interrogation" },
	{ 28,		"Group 8 interrogation" },
	{ 29,		"Group 9 interrogation" },
	{ 30,		"Group 10 interrogation" },
	{ 31,		"Group 11 interrogation" },
	{ 32,		"Group 12 interrogation" },
	{ 33,		"Group 13 interrogation" },
	{ 34,		"Group 14 interrogation" },
	{ 35,		"Group 15 interrogation" },
	{ 36,		"Group 16 interrogation" },
	{ 0, NULL }
};

static const value_string qrp_r_types[] = {
	{ 0,		"Not used" },
	{ 1,		"General reset of process" },
	{ 2,		"Reset of pending information with time tag of the event buffer" },
	{ 0, NULL }
};

static const true_false_string tfs_blocked_not_blocked = { "Blocked", "Not blocked" };
static const true_false_string tfs_substituted_not_substituted = { "Substituted", "Not Substituted" };
static const true_false_string tfs_not_topical_topical = { "Not Topical", "Topical" };
static const true_false_string tfs_transient_not_transient = { "Transient", "Not Transient" };
static const true_false_string tfs_overflow_no_overflow = { "Overflow", "No overflow" };
static const true_false_string tfs_select_execute = { "Select", "Execute" };
static const true_false_string tfs_local_dst = { "DST", "Local" };
static const true_false_string tfs_coi_i = { "Initialisation after change of local parameters", "Initialisation with unchanged local parameters" };
static const true_false_string tfs_adjusted_not_adjusted = { "Adjusted", "Not Adjusted" };

static guint global_iec60870_cot_len = 1;
static guint global_iec60870_asdu_addr_len = 1;
static guint global_iec60870_ioa_len = 2;

/* Protocol fields to be filtered */
static int hf_apdulen = -1;
static int hf_apcitype = -1;
static int hf_apciutype = -1;
static int hf_apcitx = -1;
static int hf_apcirx = -1;
static int hf_apcidata = -1;

static int hf_addr    = -1;
static int hf_oa  = -1;
static int hf_typeid   = -1;
static int hf_causetx  = -1;
static int hf_nega  = -1;
static int hf_test  = -1;
static int hf_ioa    = -1;
static int hf_numix  = -1;
static int hf_sq  = -1;
static int hf_cp24time  = -1;
static int hf_cp24time_ms  = -1;
static int hf_cp24time_min  = -1;
static int hf_cp24time_iv  = -1;
static int hf_cp56time  = -1;
static int hf_cp56time_ms  = -1;
static int hf_cp56time_min  = -1;
static int hf_cp56time_iv  = -1;
static int hf_cp56time_hour  = -1;
static int hf_cp56time_su  = -1;
static int hf_cp56time_day  = -1;
static int hf_cp56time_dow  = -1;
static int hf_cp56time_month  = -1;
static int hf_cp56time_year  = -1;
static int hf_siq  = -1;
static int hf_siq_spi  = -1;
static int hf_siq_bl  = -1;
static int hf_siq_sb  = -1;
static int hf_siq_nt  = -1;
static int hf_siq_iv  = -1;
static int hf_diq  = -1;
static int hf_diq_dpi  = -1;
static int hf_diq_bl  = -1;
static int hf_diq_sb  = -1;
static int hf_diq_nt  = -1;
static int hf_diq_iv  = -1;
static int hf_qds  = -1;
static int hf_qds_ov  = -1;
static int hf_qds_bl  = -1;
static int hf_qds_sb  = -1;
static int hf_qds_nt  = -1;
static int hf_qds_iv  = -1;
static int hf_vti  = -1;
static int hf_vti_v  = -1;
static int hf_vti_t  = -1;
static int hf_qos  = -1;
static int hf_qos_ql  = -1;
static int hf_qos_se  = -1;
static int hf_sco  = -1;
static int hf_sco_on  = -1;
static int hf_sco_qu  = -1;
static int hf_sco_se  = -1;
static int hf_dco  = -1;
static int hf_dco_on  = -1;
static int hf_dco_qu  = -1;
static int hf_dco_se  = -1;
static int hf_rco  = -1;
static int hf_rco_up  = -1;
static int hf_rco_qu  = -1;
static int hf_rco_se  = -1;
static int hf_qpm = -1;
static int hf_qpm_kpa = -1;
static int hf_qpm_lpc = -1;
static int hf_qpm_pop = -1;
static int hf_coi  = -1;
static int hf_coi_r  = -1;
static int hf_coi_i  = -1;
static int hf_qoi  = -1;
static int hf_qrp  = -1;
static int hf_bcr_count = -1;
static int hf_bcr_sq = -1;
static int hf_bcr_cy = -1;
static int hf_bcr_ca = -1;
static int hf_bcr_iv = -1;
static int hf_start = -1;

static int hf_asdu_bitstring = -1;
static int hf_asdu_float = -1;
static int hf_asdu_normval = -1;
static int hf_asdu_scalval = -1;
static int hf_asdu_raw_data = -1;

static gint ett_apci = -1;
static gint ett_asdu = -1;
static gint ett_asdu_objects = -1;
static gint ett_siq = -1;
static gint ett_diq = -1;
static gint ett_vti = -1;
static gint ett_qds = -1;
static gint ett_qos = -1;
static gint ett_sco = -1;
static gint ett_dco = -1;
static gint ett_rco = -1;
static gint ett_qpm = -1;
static gint ett_coi = -1;
static gint ett_cp24time = -1;
static gint ett_cp56time = -1;

static expert_field ei_iec104_short_asdu = EI_INIT;
static expert_field ei_iec104_apdu_min_len = EI_INIT;
static expert_field ei_iec104_apdu_invalid_len = EI_INIT;

/* IEC 101 stuff */
/* Initialize the protocol and registered fields */
static int hf_iec60870_101_frame                 = -1;
static int hf_iec60870_101_length                = -1;
static int hf_iec60870_101_num_user_octets       = -1;
static int hf_iec60870_101_ctrlfield             = -1;
static int hf_iec60870_101_ctrl_prm              = -1;
static int hf_iec60870_101_ctrl_fcb              = -1;
static int hf_iec60870_101_ctrl_fcv              = -1;
static int hf_iec60870_101_ctrl_dfc              = -1;
static int hf_iec60870_101_ctrl_func_pri_to_sec  = -1;
static int hf_iec60870_101_ctrl_func_sec_to_pri  = -1;
static int hf_iec60870_101_linkaddr              = -1;
static int hf_iec60870_101_checksum              = -1;
static int hf_iec60870_101_stopchar              = -1;

/* Initialize the subtree pointers */
static gint ett_iec60870_101                     = -1;
static gint ett_iec60870_101_ctrlfield           = -1;

/* Frame Format */
#define IEC101_VAR_LEN        0x68
#define IEC101_FIXED_LEN      0x10
#define IEC101_SINGLE_CHAR    0xE5

static const value_string iec60870_101_frame_vals[] = {
	{ IEC101_VAR_LEN,         "Variable Length" },
	{ IEC101_FIXED_LEN,       "Fixed Length" },
	{ IEC101_SINGLE_CHAR,     "Single Character" },
	{ 0,                         NULL }
};

static const value_string iec60870_101_ctrl_prm_values[] = {
	{ 0,      "Message from Secondary (Responding) Station" },
	{ 1,      "Message from Primary (Initiating) Station" },
	{ 0,      NULL }
};

static const value_string iec60870_101_ctrl_func_pri_to_sec_values[] = {
	{ 0,      "Reset of Remote Link" },
	{ 1,      "Reset of User Process" },
	{ 2,      "Reserved for Balanced Mode" },
	{ 3,      "User Data" },
	{ 4,      "User Data" },
	{ 5,      "Reserved" },
	{ 6,      "Reserved" },
	{ 7,      "Reserved" },
	{ 8,      "Expected Response Specifies Access Demand" },
	{ 9,      "Request Status of Link" },
	{ 10,     "Request User Data Class 1" },
	{ 11,     "Request User Data Class 2" },
	{ 12,     "Reserved" },
	{ 13,     "Reserved" },
	{ 14,     "Reserved" },
	{ 15,     "Reserved" },
	{ 0,      NULL }
};

static const value_string iec60870_101_ctrl_func_sec_to_pri_values[] = {
	{ 0,      "ACK: Positive Acknowledgement" },
	{ 1,      "NACK: Message Not Accepted, Link Busy" },
	{ 2,      "Reserved" },
	{ 3,      "Reserved" },
	{ 4,      "Reserved" },
	{ 5,      "Reserved" },
	{ 6,      "Reserved" },
	{ 7,      "Reserved" },
	{ 8,      "User Data" },
	{ 9,      "NACK: Requested Data not Available" },
	{ 10,     "Reserved" },
	{ 11,     "Status of Link" },
	{ 12,     "Reserved" },
	{ 13,     "Reserved" },
	{ 14,     "Link Service not Functioning" },
	{ 15,     "Link Service not Implemented" },
	{ 0,      NULL }
};

/* Misc. functions for dissection of signal values */

/* ====================================================================
   Dissects the CP24Time2a time (Three octet binary time)
   that starts 'offset' bytes in 'tvb'.
   ==================================================================== */
static void get_CP24Time(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	guint16 ms;
	guint8 min;
	nstime_t time;
	proto_item* ti;
	proto_tree* cp24time_tree;

	ms = tvb_get_letohs(tvb, *offset);
	time.nsecs = (ms % 1000) * 1000000;
	time.secs = ms / 1000;
	(*offset) += 2;

	min = tvb_get_guint8(tvb, *offset);
	time.secs += (min & 0x3F) * 60;
	(*offset)++;

	(*offset) -= 3;

	ti = proto_tree_add_time(iec104_header_tree, hf_cp24time, tvb, *offset, 3, &time);
	cp24time_tree = proto_item_add_subtree(ti, ett_cp24time);

	proto_tree_add_item(cp24time_tree, hf_cp24time_ms, tvb, *offset, 2, ENC_LITTLE_ENDIAN);
	(*offset) += 2;

	proto_tree_add_item(cp24time_tree, hf_cp24time_min, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(cp24time_tree, hf_cp24time_iv, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	(*offset) ++;
}

/* ====================================================================
   Dissects the CP56Time2a time (Seven octet binary time)
   that starts 'offset' bytes in 'tvb'.
   ==================================================================== */
static void get_CP56Time(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	guint16 ms;
	guint8 value;
	guint8 su;
	struct tm tm;
	nstime_t  datetime;
	proto_item* ti;
	proto_tree* cp56time_tree;

	ms = tvb_get_letohs(tvb, *offset);
	tm.tm_sec = ms / 1000;
	datetime.nsecs = (ms % 1000) * 1000000;
	(*offset) += 2;

	value = tvb_get_guint8(tvb, *offset);
	tm.tm_min = value & 0x3F;
	(*offset)++;

	value = tvb_get_guint8(tvb, *offset);
	tm.tm_hour = value & 0x1F;
	su = value & 0x80;
	(*offset)++;

	value = tvb_get_guint8(tvb, *offset);
	tm.tm_mday = value & 0x1F;
	(*offset)++;

	value = tvb_get_guint8(tvb, *offset);
	tm.tm_mon = (value & 0x0F) - 1;
	(*offset)++;

	value = tvb_get_guint8(tvb, *offset);
	tm.tm_year = value & 0x7F;
	if (tm.tm_year < 70)
		tm.tm_year += 100;

	(*offset)++;

	if (su)
		tm.tm_isdst = 1;
	else
		tm.tm_isdst = -1; /* there's no info on whether DST was in force; assume it's
				   * the same as currently */

	datetime.secs = mktime(&tm);

	(*offset) -= 7;

	ti = proto_tree_add_time(iec104_header_tree, hf_cp56time, tvb, *offset, 7, &datetime);
	cp56time_tree = proto_item_add_subtree(ti, ett_cp56time);

	proto_tree_add_item(cp56time_tree, hf_cp56time_ms, tvb, *offset, 2, ENC_LITTLE_ENDIAN);
	(*offset) += 2;

	proto_tree_add_item(cp56time_tree, hf_cp56time_min, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(cp56time_tree, hf_cp56time_iv, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	(*offset) ++;

	proto_tree_add_item(cp56time_tree, hf_cp56time_hour, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(cp56time_tree, hf_cp56time_su, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	(*offset) ++;

	proto_tree_add_item(cp56time_tree, hf_cp56time_day, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(cp56time_tree, hf_cp56time_dow, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	(*offset) ++;

	proto_tree_add_item(cp56time_tree, hf_cp56time_month, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	(*offset) ++;

	proto_tree_add_item(cp56time_tree, hf_cp56time_year, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	(*offset) ++;
}

/* ====================================================================
   Information object address (Identifier)
   ASDU -> Inform Object #1 -> Information object address
   ==================================================================== */
static proto_item* get_InfoObjectAddress(guint32 *asdu_info_obj_addr, tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree, guint ioa_len)
{
	proto_item* ti = NULL;

	/* Information object address */
	/* Support both 16 and 24-bit IOA addresses */
	ti = proto_tree_add_item_ret_uint(iec104_header_tree, hf_ioa, tvb, *offset, ioa_len, ENC_LITTLE_ENDIAN, asdu_info_obj_addr);
	(*offset) += ioa_len;

	return ti;
}

/* ====================================================================
   TypeId length
   ==================================================================== */
static guint8 get_TypeIdLength(guint8 TypeId)
{
	guint8 ret = 0;
	const td_asdu_length *item;

	item = asdu_length;
	while (item->value)
	{
		if (item->value == TypeId)
		{
			ret = item->length;
			break;
		}
		item++;
	}

	return ret;
}

/* ====================================================================
   SIQ: Single-point information (IEV 371-02-07) w quality descriptor
   ==================================================================== */
static void get_SIQ(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	proto_item* ti;
	proto_tree* siq_tree;

	ti = proto_tree_add_item(iec104_header_tree, hf_siq, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	siq_tree = proto_item_add_subtree(ti, ett_siq);

	proto_tree_add_item(siq_tree, hf_siq_spi, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(siq_tree, hf_siq_bl, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(siq_tree, hf_siq_sb, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(siq_tree, hf_siq_nt, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(siq_tree, hf_siq_iv, tvb, *offset, 1, ENC_LITTLE_ENDIAN);

	(*offset)++;
}

/* ====================================================================
   DIQ: Double-point information (IEV 371-02-08) w quality descriptor
   ==================================================================== */
static void get_DIQ(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	proto_item* ti;
	proto_tree* diq_tree;

	ti = proto_tree_add_item(iec104_header_tree, hf_diq, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	diq_tree = proto_item_add_subtree(ti, ett_diq);

	proto_tree_add_item(diq_tree, hf_diq_dpi, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(diq_tree, hf_diq_bl, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(diq_tree, hf_diq_sb, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(diq_tree, hf_diq_nt, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(diq_tree, hf_diq_iv, tvb, *offset, 1, ENC_LITTLE_ENDIAN);

	(*offset)++;
}

/* ====================================================================
   QDS: Quality descriptor (separate octet)
   ==================================================================== */
static void get_QDS(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	proto_item* ti;
	proto_tree* qds_tree;

	ti = proto_tree_add_item(iec104_header_tree, hf_qds, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	qds_tree = proto_item_add_subtree(ti, ett_qds);

	proto_tree_add_item(qds_tree, hf_qds_ov, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(qds_tree, hf_qds_bl, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(qds_tree, hf_qds_sb, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(qds_tree, hf_qds_nt, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(qds_tree, hf_qds_iv, tvb, *offset, 1, ENC_LITTLE_ENDIAN);

	(*offset)++;
}

/* ====================================================================
   QDP: Quality descriptor for events of protection equipment
   (separate octet)
   ==================================================================== */
#if 0
static void get_QDP(tvbuff_t *tvb _U_, guint8 *offset _U_, proto_tree *iec104_header_tree _U_)
{
	/* todo */

}
#endif

/* ====================================================================
   VTI: Value with transient state indication
   ==================================================================== */
static void get_VTI(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	proto_item* ti;
	proto_tree* vti_tree;

	ti = proto_tree_add_item(iec104_header_tree, hf_vti, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	vti_tree = proto_item_add_subtree(ti, ett_vti);

	proto_tree_add_item(vti_tree, hf_vti_v, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(vti_tree, hf_vti_t, tvb, *offset, 1, ENC_LITTLE_ENDIAN);

	(*offset)++;
}

/* ====================================================================
   NVA: Normalized value
   ==================================================================== */
static void get_NVA(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	gint16 value;
	float fvalue;

	value = tvb_get_letohis(tvb, *offset);
	fvalue = (float)value / 32768;

	/* Normalized value F16[1..16]<-1..+1-2^-15> */
	proto_tree_add_float_format_value(iec104_header_tree, hf_asdu_normval, tvb, *offset, 2, fvalue, "%." G_STRINGIFY(FLT_DIG) "g (%d)", fvalue, value);

	(*offset) += 2;
}

static void get_NVAspt(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	gint16 value;
	float fvalue;

	value = tvb_get_letohis(tvb, *offset);
	fvalue = (float)value / 32768;

	/* Normalized value F16[1..16]<-1..+1-2^-15> */
	proto_tree_add_float_format_value(iec104_header_tree, hf_asdu_normval, tvb, *offset, 2, fvalue, "%." G_STRINGIFY(FLT_DIG) "g (%d)", fvalue, value);

	(*offset) += 2;
}

/* ====================================================================
   SVA: Scaled value
   ==================================================================== */
static void get_SVA(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	/* Scaled value I16[1..16]<-2^15..+2^15-1> */
	proto_tree_add_item(iec104_header_tree, hf_asdu_scalval, tvb, *offset, 2, ENC_LITTLE_ENDIAN);

	(*offset) += 2;
}

static void get_SVAspt(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	/* Scaled value I16[1..16]<-2^15..+2^15-1> */
	proto_tree_add_item(iec104_header_tree, hf_asdu_scalval, tvb, *offset, 2, ENC_LITTLE_ENDIAN);

	(*offset) += 2;
}

/* ====================================================================
   "FLT": Short floating point number
   ==================================================================== */
static void get_FLT(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	/* --------  IEEE 754 float value */
	proto_tree_add_item(iec104_header_tree, hf_asdu_float, tvb, *offset, 4, ENC_LITTLE_ENDIAN);

	(*offset) += 4;
}

static void get_FLTspt(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	/* --------  IEEE 754 float value */
	proto_tree_add_item(iec104_header_tree, hf_asdu_float, tvb, *offset, 4, ENC_LITTLE_ENDIAN);

	(*offset) += 4;
}

/* ====================================================================
   "BSI": Binary state information, 32 bit
   ==================================================================== */
static void get_BSI(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	proto_tree_add_bits_item(iec104_header_tree, hf_asdu_bitstring, tvb, *offset*8, 32, ENC_BIG_ENDIAN);

	(*offset) += 4;
}

static void get_BSIspt(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	proto_tree_add_bits_item(iec104_header_tree, hf_asdu_bitstring, tvb, *offset*8, 32, ENC_BIG_ENDIAN);

	(*offset) += 4;
}

/* ====================================================================
    BCR: Binary counter reading
   ==================================================================== */
static void get_BCR(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	proto_tree_add_item(iec104_header_tree, hf_bcr_count, tvb, *offset, 4, ENC_LITTLE_ENDIAN);
	*offset += 4;

	proto_tree_add_item(iec104_header_tree, hf_bcr_sq, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(iec104_header_tree, hf_bcr_cy, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(iec104_header_tree, hf_bcr_ca, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(iec104_header_tree, hf_bcr_iv, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	*offset += 1;
}

/* ====================================================================
    todo -- SEP: Single event of protection equipment
   ==================================================================== */
#if 0
static void get_SEP(tvbuff_t *tvb _U_, guint8 *offset _U_, proto_tree *iec104_header_tree _U_)
{
	/* todo */

}
#endif

/* ====================================================================
    QOS: Qualifier Of Set-point command
   ==================================================================== */
static void get_QOS(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	proto_item* ti;
	proto_tree* qos_tree;

	ti = proto_tree_add_item(iec104_header_tree, hf_qos, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	qos_tree = proto_item_add_subtree(ti, ett_qos);

	proto_tree_add_item(qos_tree, hf_qos_ql, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(qos_tree, hf_qos_se, tvb, *offset, 1, ENC_LITTLE_ENDIAN);

	(*offset)++;
}

/* ====================================================================
    SCO: Single Command (IEV 371-03-02)
   ==================================================================== */
static void get_SCO(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	proto_item* ti;
	proto_tree* sco_tree;

	ti = proto_tree_add_item(iec104_header_tree, hf_sco, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	sco_tree = proto_item_add_subtree(ti, ett_sco);

	proto_tree_add_item(sco_tree, hf_sco_on, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(sco_tree, hf_sco_qu, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(sco_tree, hf_sco_se, tvb, *offset, 1, ENC_LITTLE_ENDIAN);

	(*offset)++;
}

/* ====================================================================
    DCO: Double Command (IEV 371-03-03)
   ==================================================================== */
static void get_DCO(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	proto_item* ti;
	proto_tree* dco_tree;

	ti = proto_tree_add_item(iec104_header_tree, hf_dco, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	dco_tree = proto_item_add_subtree(ti, ett_dco);

	proto_tree_add_item(dco_tree, hf_dco_on, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(dco_tree, hf_dco_qu, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(dco_tree, hf_dco_se, tvb, *offset, 1, ENC_LITTLE_ENDIAN);

	(*offset)++;
}

/* ====================================================================
    RCO: Regulating step command (IEV 371-03-13)
   ==================================================================== */
static void get_RCO(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	proto_item* ti;
	proto_tree* rco_tree;

	ti = proto_tree_add_item(iec104_header_tree, hf_rco, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	rco_tree = proto_item_add_subtree(ti, ett_rco);

	proto_tree_add_item(rco_tree, hf_rco_up, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(rco_tree, hf_rco_qu, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(rco_tree, hf_rco_se, tvb, *offset, 1, ENC_LITTLE_ENDIAN);

	(*offset)++;
}

/* ====================================================================
    QPM: Qualifier of parameter of measured value
   ==================================================================== */
static void get_QPM(tvbuff_t* tvb, guint8* offset, proto_tree* iec104_header_tree)
{
	proto_item* ti;
	proto_tree* qpm_tree;

	ti = proto_tree_add_item(iec104_header_tree, hf_qpm, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	qpm_tree = proto_item_add_subtree(ti, ett_qpm);

	proto_tree_add_item(qpm_tree, hf_qpm_kpa, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(qpm_tree, hf_qpm_lpc, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(qpm_tree, hf_qpm_pop, tvb, *offset, 1, ENC_LITTLE_ENDIAN);

	(*offset)++;
}

/* ====================================================================
    COI: Cause of initialisation
   ==================================================================== */
static void get_COI(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	proto_item* ti;
	proto_tree* coi_tree;

	ti = proto_tree_add_item(iec104_header_tree, hf_coi, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	coi_tree = proto_item_add_subtree(ti, ett_coi);

	proto_tree_add_item(coi_tree, hf_coi_r, tvb, *offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(coi_tree, hf_coi_i, tvb, *offset, 1, ENC_LITTLE_ENDIAN);

	(*offset)++;
}

/* ====================================================================
    QOI: Qualifier of interrogation
   ==================================================================== */
static void get_QOI(tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree)
{
	proto_tree_add_item(iec104_header_tree, hf_qoi, tvb, *offset, 1, ENC_LITTLE_ENDIAN);

	(*offset)++;
}

/* ====================================================================
    QRP: Qualifier of reset process command
   ==================================================================== */
static void get_QRP(tvbuff_t* tvb, guint8* offset, proto_tree* iec104_header_tree)
{
	proto_tree_add_item(iec104_header_tree, hf_qrp, tvb, *offset, 1, ENC_LITTLE_ENDIAN);

	(*offset)++;
}
/* .... end Misc. functions for dissection of signal values */


/* Find the IEC60870-5-104 APDU (APDU=APCI+ASDU) length.
Includes possible tvb_length-1 bytes that don't form an APDU */
static guint get_iec104apdu_len(packet_info *pinfo _U_, tvbuff_t *tvb,
                                int offset, void *data _U_)
{
	guint8 Val;
	guint32 Off;

	for (Off = 0; Off <= tvb_reported_length(tvb) - 2; Off++) {
		Val = tvb_get_guint8(tvb, offset + Off);
		if (Val == APCI_START) {
			return (guint)(Off + tvb_get_guint8(tvb, offset + Off + 1) + 2);
		}
	}

	return (guint)(tvb_reported_length(tvb));
}


/* Is is called twice: For 'Packet List' and for 'Packet Details' */
/* This dissection is shared by the IEC '101 and '104 dissectors */
static int dissect_iec60870_asdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
{
	guint Len = tvb_reported_length(tvb);
	guint8 Bytex;
	const char *cause_str;
	size_t Ind;
	struct asduheader asduh = { .OA = 0, .Addr = 0, .IOA = 0};
	struct asdu_parms* parms = (struct asdu_parms*)data;
	proto_item *it104;
	proto_tree *it104tree;
	wmem_strbuf_t * res;

	guint8 offset = 0;  /* byte offset, signal dissection */
	guint8 i;
	guint32 asdu_info_obj_addr = 0;
	proto_item * itSignal = NULL;
	proto_tree * trSignal;

	col_set_str(pinfo->cinfo, COL_PROTOCOL, "IEC 60870-5 ASDU");

	it104 = proto_tree_add_item(tree, proto_iec60870_asdu, tvb, offset, -1, ENC_NA);
	it104tree = proto_item_add_subtree(it104, ett_asdu);

	res = wmem_strbuf_new_label(wmem_packet_scope());

	/* Type identification */
	asduh.TypeId = tvb_get_guint8(tvb, offset);
	proto_tree_add_item(it104tree, hf_typeid, tvb, offset, 1, ENC_LITTLE_ENDIAN);
	asduh.DataLength = get_TypeIdLength(asduh.TypeId);
	offset += 1;

	/* Variable structure qualifier */
	Bytex = tvb_get_guint8(tvb, 1);
	asduh.SQ = Bytex & F_SQ;
	asduh.NumIx = Bytex & 0x7F;
	proto_tree_add_item(it104tree, hf_sq, tvb, offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(it104tree, hf_numix, tvb, offset, 1, ENC_LITTLE_ENDIAN);
	offset += 1;

	/* Cause of transmission */
	asduh.TNCause = tvb_get_guint8(tvb, offset);
	proto_tree_add_item(it104tree, hf_causetx, tvb, offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(it104tree, hf_nega, tvb, offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(it104tree, hf_test, tvb, offset, 1, ENC_LITTLE_ENDIAN);
	offset += 1;

	/* Originator address */
	/* This is only present if the Cause of Tx field is 2 octets */
	if (parms->cot_len == 2) {
		asduh.OA = tvb_get_guint8(tvb, offset);
		proto_tree_add_item(it104tree, hf_oa, tvb, offset, 1, ENC_LITTLE_ENDIAN);
		offset += 1;
	}

	/* Common address of ASDU */
	proto_tree_add_item_ret_uint(it104tree, hf_addr, tvb, offset, parms->asdu_addr_len, ENC_LITTLE_ENDIAN, &asduh.Addr);
	offset += parms->asdu_addr_len;

	/* Information object address */
	/* Support both 16 and 24-bit IOA addresses */
	/* Don't increment offset, as we'll want to be at this position later */
	if (parms->ioa_len == 3) {
		asduh.IOA = tvb_get_letoh24(tvb, offset);
	}
	else if (parms->ioa_len == 2) {
		asduh.IOA = tvb_get_letohs(tvb, offset);
	}

	cause_str = val_to_str(asduh.TNCause & F_CAUSE, causetx_types, " <CauseTx=%u>");

	wmem_strbuf_append_printf(res, "ASDU=%u %s %s", asduh.Addr, val_to_str(asduh.TypeId, asdu_types, "<TypeId=%u>"), cause_str);

	if (asduh.TNCause & F_NEGA)
		wmem_strbuf_append(res, "_NEGA");
	if (asduh.TNCause & F_TEST)
		wmem_strbuf_append(res, "_TEST");

	if ((asduh.TNCause & (F_TEST | F_NEGA)) == 0) {
		for (Ind=strlen(cause_str); Ind< 7; Ind++)
			wmem_strbuf_append(res, " ");
	}

	if (asduh.NumIx > 1) {
		wmem_strbuf_append_printf(res, " IOA[%d]=%d", asduh.NumIx, asduh.IOA);
		if (asduh.SQ == F_SQ)
			wmem_strbuf_append_printf(res, "-%d", asduh.IOA + asduh.NumIx - 1);
		else
			wmem_strbuf_append(res, ",...");
	} else {
		wmem_strbuf_append_printf(res, " IOA=%d", asduh.IOA);
	}

	col_append_str(pinfo->cinfo, COL_INFO, wmem_strbuf_get_str(res));
	col_set_fence(pinfo->cinfo, COL_INFO);

	/* 'ASDU Details': ROOT ITEM */
	proto_item_append_text(it104, ": %s '%s'", wmem_strbuf_get_str(res),
		Len >= offset + parms->ioa_len ? val_to_str_const(asduh.TypeId, asdu_lngtypes, "<Unknown TypeId>") : "");

	/* 'Signal Details': TREE */
	/* -------- get signal value and status based on ASDU type id */

	switch (asduh.TypeId) {
		case M_SP_NA_1:
		case M_SP_TA_1:
		case M_DP_NA_1:
		case M_DP_TA_1:
		case M_ST_NA_1:
		case M_ST_TA_1:
		case M_BO_NA_1:
		case M_BO_TA_1:
		case M_SP_TB_1:
		case M_DP_TB_1:
		case M_ST_TB_1:
		case M_BO_TB_1:
		case M_ME_NA_1:
		case M_ME_TA_1:
		case M_ME_NB_1:
		case M_ME_TB_1:
		case M_ME_NC_1:
		case M_ME_TC_1:
		case M_ME_ND_1:
		case M_ME_TD_1:
		case M_ME_TE_1:
		case M_ME_TF_1:
		case M_IT_NA_1:
		case M_IT_TA_1:
		case M_IT_TB_1:
		case C_SC_NA_1:
		case C_DC_NA_1:
		case C_RC_NA_1:
		case C_SE_NA_1:
		case C_SE_NB_1:
		case C_SE_NC_1:
		case C_BO_NA_1:
		case C_SC_TA_1:
		case C_DC_TA_1:
		case C_RC_TA_1:
		case C_SE_TA_1:
		case C_SE_TB_1:
		case C_SE_TC_1:
		case C_BO_TA_1:
		case M_EI_NA_1:
		case C_IC_NA_1:
		case C_CS_NA_1:
		case C_RP_NA_1:
		case P_ME_NA_1:
		case P_ME_NB_1:
		case P_ME_NC_1:

			/* -- object values */
			for(i = 0; i < asduh.NumIx; i++)
			{
				/* create subtree for the signal values ... */
				if (i == 0 || !asduh.SQ)
					trSignal = proto_tree_add_subtree(it104tree, tvb, offset, asduh.DataLength + parms->ioa_len,
														ett_asdu_objects, &itSignal, "IOA:s");
				else
					trSignal = proto_tree_add_subtree(it104tree, tvb, offset, asduh.DataLength,
														ett_asdu_objects, &itSignal, "IOA:s");

				/* --------  First Information object address */
				if (i == 0)
				{
					/* --------  Information object address */
					/* check length */
					if(Len < (guint)(offset + 3)) {
						expert_add_info(pinfo, itSignal, &ei_iec104_short_asdu);
						return offset;
					}
					get_InfoObjectAddress(&asdu_info_obj_addr, tvb, &offset, trSignal, parms->ioa_len);
				} else {
					/* -------- following Information object address depending on SQ */
					if (asduh.SQ) /* <=> SQ=1, info obj addr = startaddr++ */
					{
						proto_item *ti;
						asdu_info_obj_addr++;
						ti = proto_tree_add_uint(trSignal, hf_ioa, tvb, 0, 0, asdu_info_obj_addr);
						proto_item_set_generated(ti);
					} else { /* SQ=0, info obj addr given */
						/* --------  Information object address */
						/* check length */
						if(Len < (guint)(offset + 3)) {
							expert_add_info(pinfo, itSignal, &ei_iec104_short_asdu);
							return offset;
						}
						get_InfoObjectAddress(&asdu_info_obj_addr, tvb, &offset, trSignal, parms->ioa_len);
					}
				}

				proto_item_set_text(itSignal, "IOA: %d", asdu_info_obj_addr);

				/* check length */
				if(Len < (guint)(offset + asduh.DataLength)) {
					expert_add_info(pinfo, itSignal, &ei_iec104_short_asdu);
					return offset;
				}

				switch (asduh.TypeId) {
				case M_SP_NA_1: /* 1	Single-point information */
					get_SIQ(tvb, &offset, trSignal);
					break;
				case M_SP_TA_1: /* 2	Single-point information with time tag */
					get_SIQ(tvb, &offset, trSignal);
					get_CP24Time(tvb, &offset, trSignal);
					break;
				case M_DP_NA_1: /* 3	Double-point information */
					get_DIQ(tvb, &offset, trSignal);
					break;
				case M_DP_TA_1: /* 4	Double-point information with time tag */
					get_DIQ(tvb, &offset, trSignal);
					get_CP24Time(tvb, &offset, trSignal);
					break;
				case M_ST_NA_1: /* 5	Step position information */
					get_VTI(tvb, &offset, trSignal);
					get_QDS(tvb, &offset, trSignal);
					break;
				case M_ST_TA_1: /* 6	Step position information with time tag */
					get_VTI(tvb, &offset, trSignal);
					get_QDS(tvb, &offset, trSignal);
					get_CP24Time(tvb, &offset, trSignal);
					break;
				case M_BO_NA_1: /* 7	Bitstring of 32 bits */
					get_BSI(tvb, &offset, trSignal);
					get_QDS(tvb, &offset, trSignal);
					break;
				case M_BO_TA_1: /* 8	Bitstring of 32 bits with time tag */
					get_BSI(tvb, &offset, trSignal);
					get_QDS(tvb, &offset, trSignal);
					get_CP24Time(tvb, &offset, trSignal);
					break;
				case M_ME_NA_1: /* 9	Measured value, normalized value */
					get_NVA(tvb, &offset, trSignal);
					get_QDS(tvb, &offset, trSignal);
					break;
				case M_ME_TA_1: /* 10	Measured value, normalized value with time tag */
					get_NVA(tvb, &offset, trSignal);
					get_QDS(tvb, &offset, trSignal);
					get_CP24Time(tvb, &offset, trSignal);
					break;
				case M_ME_NB_1: /* 11     Measured value, scaled value */
					get_SVA(tvb, &offset, trSignal);
					get_QDS(tvb, &offset, trSignal);
					break;
				case M_ME_TB_1: /* 12     Measured value, scaled value with time tag */
					get_SVA(tvb, &offset, trSignal);
					get_QDS(tvb, &offset, trSignal);
					get_CP24Time(tvb, &offset, trSignal);
					break;
				case M_ME_NC_1: /* 13	Measured value, short floating point value */
					get_FLT(tvb, &offset, trSignal);
					get_QDS(tvb, &offset, trSignal);
					break;
				case M_ME_TC_1: /* 14	Measured value, short floating point value with time tag */
					get_FLT(tvb, &offset, trSignal);
					get_QDS(tvb, &offset, trSignal);
					get_CP24Time(tvb, &offset, trSignal);
					break;
				case M_IT_NA_1: /* 15	Integrated totals */
					get_BCR(tvb, &offset, trSignal);
					break;
				case M_IT_TA_1: /* 16	Integrated totals with time tag */
					get_BCR(tvb, &offset, trSignal);
					get_CP24Time(tvb, &offset, trSignal);
					break;
				case M_ME_ND_1: /* 21    Measured value, normalized value without quality descriptor */
					get_NVA(tvb, &offset, trSignal);
					break;
				case M_SP_TB_1: /* 30	Single-point information with time tag CP56Time2a */
					get_SIQ(tvb, &offset, trSignal);
					get_CP56Time(tvb, &offset, trSignal);
					break;
				case M_DP_TB_1: /* 31	Double-point information with time tag CP56Time2a */
					get_DIQ(tvb, &offset, trSignal);
					get_CP56Time(tvb, &offset, trSignal);
					break;
				case M_ST_TB_1: /* 32	Step position information with time tag CP56Time2a */
					get_VTI(tvb, &offset, trSignal);
					get_QDS(tvb, &offset, trSignal);
					get_CP56Time(tvb, &offset, trSignal);
					break;
				case M_BO_TB_1: /* 33	Bitstring of 32 bit with time tag CP56Time2a */
					get_BSI(tvb, &offset, trSignal);
					get_QDS(tvb, &offset, trSignal);
					get_CP56Time(tvb, &offset, trSignal);
					break;
				case M_ME_TD_1: /* 34    Measured value, normalized value with time tag CP56Time2a */
					get_NVA(tvb, &offset, trSignal);
					get_QDS(tvb, &offset, trSignal);
					get_CP56Time(tvb, &offset, trSignal);
					break;
				case M_ME_TE_1: /* 35    Measured value, scaled value with time tag CP56Time2a */
					get_SVA(tvb, &offset, trSignal);
					get_QDS(tvb, &offset, trSignal);
					get_CP56Time(tvb, &offset, trSignal);
					break;
				case M_ME_TF_1: /* 36    Measured value, short floating point value with time tag CP56Time2a */
					get_FLT(tvb, &offset, trSignal);
					get_QDS(tvb, &offset, trSignal);
					get_CP56Time(tvb, &offset, trSignal);
					break;
				case M_IT_TB_1: /* 37	Integrated totals with time tag CP56Time2a */
					get_BCR(tvb, &offset, trSignal);
					get_CP56Time(tvb, &offset, trSignal);
					break;
				case C_SC_NA_1: /* 45	Single command */
					get_SCO(tvb, &offset, trSignal);
					break;
				case C_DC_NA_1: /* 46	Double command */
					get_DCO(tvb, &offset, trSignal);
					break;
				case C_RC_NA_1: /* 47	Regulating step command */
					get_RCO(tvb, &offset, trSignal);
					break;
				case C_SE_NA_1: /* 48    Set point command, normalized value */
					get_NVAspt(tvb, &offset, trSignal);
					get_QOS(tvb, &offset, trSignal);
					break;
				case C_SE_NB_1: /* 49    Set point command, scaled value */
					get_SVAspt(tvb, &offset, trSignal);
					get_QOS(tvb, &offset, trSignal);
					break;
				case C_SE_NC_1: /* 50    Set point command, short floating point value */
					get_FLTspt(tvb, &offset, trSignal);
					get_QOS(tvb, &offset, trSignal);
					break;
				case C_BO_NA_1: /* 51    Bitstring of 32 bits */
					get_BSIspt(tvb, &offset, trSignal);
					break;
				case C_SC_TA_1: /* 58    Single command with time tag CP56Time2a */
					get_SCO(tvb, &offset, trSignal);
					get_CP56Time(tvb, &offset, trSignal);
					break;
				case C_DC_TA_1: /* 59    Double command with time tag CP56Time2a */
					get_DCO(tvb, &offset, trSignal);
					get_CP56Time(tvb, &offset, trSignal);
					break;
				case C_RC_TA_1: /* 60    Regulating step command with time tag CP56Time2a */
					get_RCO(tvb, &offset, trSignal);
					get_CP56Time(tvb, &offset, trSignal);
					break;
				case C_SE_TA_1: /* 61    Set point command, normalized value with time tag CP56Time2a */
					get_NVAspt(tvb, &offset, trSignal);
					get_QOS(tvb, &offset, trSignal);
					get_CP56Time(tvb, &offset, trSignal);
					break;
				case C_SE_TB_1: /* 62    Set point command, scaled value with time tag CP56Time2a */
					get_SVAspt(tvb, &offset, trSignal);
					get_QOS(tvb, &offset, trSignal);
					get_CP56Time(tvb, &offset, trSignal);
					break;
				case C_SE_TC_1: /* 63    Set point command, short floating point value with time tag CP56Time2a */
					get_FLTspt(tvb, &offset, trSignal);
					get_QOS(tvb, &offset, trSignal);
					get_CP56Time(tvb, &offset, trSignal);
					break;
				case C_BO_TA_1: /* 64    Bitstring of 32 bits with time tag CP56Time2a */
					get_BSIspt(tvb, &offset, trSignal);
					get_CP56Time(tvb, &offset, trSignal);
					break;
				case M_EI_NA_1: /* 70    End of initialization  */
					get_COI(tvb, &offset, trSignal);
					break;
				case C_IC_NA_1: /* 100   Interrogation command  */
					get_QOI(tvb, &offset, trSignal);
					break;
				case C_CS_NA_1: /* 103   Clock synchronization command  */
					get_CP56Time(tvb, &offset, trSignal);
					break;
				case C_RP_NA_1: /* 105   reset process command  */
					get_QRP(tvb, &offset, trSignal);
					break;
				case P_ME_NA_1: /* 110   Parameter of measured value, normalized value */
					get_NVA(tvb, &offset, trSignal);
					get_QPM(tvb, &offset, trSignal);
					break;
				case P_ME_NB_1: /* 111   Parameter of measured value, scaled value */
					get_SVA(tvb, &offset, trSignal);
					get_QPM(tvb, &offset, trSignal);
					break;
				case P_ME_NC_1: /* 112   Parameter of measured value, short floating-point number */
					get_FLT(tvb, &offset, trSignal);
					get_QPM(tvb, &offset, trSignal);
					break;
				default:
					break;
				} /* end 'switch (asduh.TypeId)' */
			} /* end 'for(i = 0; i < dui.asdu_vsq_no_of_obj; i++)' */
			break;
		default:
			proto_tree_add_item(it104tree, hf_ioa, tvb, offset, 3, ENC_LITTLE_ENDIAN);
			offset += 3;

			if (Len - offset > 0)
				proto_tree_add_item(it104tree, hf_asdu_raw_data, tvb, offset, Len - offset, ENC_NA);
			offset = Len;

			break;
	} /* end 'switch (asdu_typeid)' */

	/* check correct apdu length */
	if (Len != offset) {
		expert_add_info(pinfo, it104tree, &ei_iec104_apdu_invalid_len);
		return offset;
	}

	return tvb_captured_length(tvb);
}



/* Is is called twice: For 'Packet List' and for 'Packet Details' */
static int dissect_iec60870_104(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
	guint TcpLen = tvb_reported_length(tvb);
	guint8 Start, len, type, temp8;
	guint16 apci_txid, apci_rxid;
	guint Off;
	proto_item *it104, *ti;
	proto_tree *it104tree;
	wmem_strbuf_t * res;
	struct asdu_parms parms;

	col_set_str(pinfo->cinfo, COL_PROTOCOL, "IEC 60870-5-104");

	it104 = proto_tree_add_item(tree, proto_iec60870_104, tvb, 0, -1, ENC_NA);
	it104tree = proto_item_add_subtree(it104, ett_apci);

	res = wmem_strbuf_new_label(wmem_packet_scope());

	Start = 0;
	for (Off = 0; Off <= TcpLen - 2; Off++) {
		Start = tvb_get_guint8(tvb, Off);

		if (Start == APCI_START) {
			if (Off > 0)
			{
				proto_tree_add_item(it104tree, hf_apcidata, tvb, 0, Off, ENC_NA);
				wmem_strbuf_append_printf(res, "<ERR prefix %u bytes> ", Off);
			}

			proto_item_set_len(it104, Off + APCI_LEN);

			proto_tree_add_uint_format(it104tree, hf_start, tvb, Off, 1, Start, "START");
			ti = proto_tree_add_item(it104tree, hf_apdulen, tvb, Off + 1, 1, ENC_LITTLE_ENDIAN);

			len = tvb_get_guint8(tvb, Off + 1);
			if (len < APDU_MIN_LEN) {
				expert_add_info_format(pinfo, ti, &ei_iec104_apdu_min_len, "APDU less than %d bytes", APDU_MIN_LEN);
				wmem_strbuf_append_printf(res, "<ERR ApduLen=%u bytes> ", len);
				return tvb_captured_length(tvb);
			}

			temp8 = tvb_get_guint8(tvb, Off + 2);
			if ((temp8 & 0x01) == 0)
				type = 0;
			else
				type = temp8 & 0x03;

			if (type == I_TYPE)
				proto_tree_add_bits_item(it104tree, hf_apcitype, tvb, (Off + 2) * 8 + 7, 1, ENC_LITTLE_ENDIAN);
			else
				proto_tree_add_bits_item(it104tree, hf_apcitype, tvb, (Off + 2) * 8 + 6, 2, ENC_LITTLE_ENDIAN);

			if (len <= APDU_MAX_LEN) {
				wmem_strbuf_append_printf(res, "%s %s ",
					(pinfo->srcport == pinfo->match_uint ? "->" : "<-"),
					val_to_str_const(type, apci_types, "<ERR>"));
			}
			else {
				wmem_strbuf_append_printf(res, "<ERR ApduLen=%u bytes> ", len);
			}

			switch(type) {
			case I_TYPE:
				apci_txid = tvb_get_letohs(tvb, Off + 2) >> 1;
				apci_rxid = tvb_get_letohs(tvb, Off + 4) >> 1;
				wmem_strbuf_append_printf(res, "(%d,%d) ", apci_txid, apci_rxid);
				proto_tree_add_uint(it104tree, hf_apcitx, tvb, Off+2, 2, apci_txid);
				proto_tree_add_uint(it104tree, hf_apcirx, tvb, Off+4, 2, apci_rxid);
				break;
			case S_TYPE:
				apci_rxid = tvb_get_letohs(tvb, Off + 4) >> 1;
				wmem_strbuf_append_printf(res, "(%d) ", apci_rxid);
				proto_tree_add_uint(it104tree, hf_apcirx, tvb, Off+4, 2, apci_rxid);
				break;
			case U_TYPE:
				wmem_strbuf_append_printf(res, "(%s) ", val_to_str_const((temp8 >> 2) & 0x3F, u_types, "<ERR>"));
				proto_tree_add_item(it104tree, hf_apciutype, tvb, Off + 2, 1, ENC_LITTLE_ENDIAN);
				break;
			}

			col_clear(pinfo->cinfo, COL_INFO);
			col_append_sep_str(pinfo->cinfo, COL_INFO, " | ", wmem_strbuf_get_str(res));
			col_set_fence(pinfo->cinfo, COL_INFO);

			proto_item_append_text(it104, ": %s", wmem_strbuf_get_str(res));

			if (type == I_TYPE) {
				/* Set the field lengths to the '104 fixed values before calling the ASDU dissection */
				parms.cot_len = 2;
				parms.asdu_addr_len = 2;
				parms.ioa_len = 3;

				call_dissector_with_data(iec60870_asdu_handle, tvb_new_subset_length_caplen(tvb, Off + APCI_LEN, -1, len - APCI_DATA_LEN), pinfo, tree, &parms);
			}
			/* Don't search more the APCI_START */
			break;
		}
	}

	if (Start != APCI_START) {
		/* Everything is bad (no APCI found) */
		proto_tree_add_item(it104tree, hf_apcidata, tvb, 0, Off, ENC_NA);
	}

	return tvb_captured_length(tvb);
}

/******************************************************************************************************/
/* Code to dissect IEC 101 Protocol packets */
/******************************************************************************************************/
static int
dissect_iec60870_101(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
/* Set up structures needed to add the protocol subtree and manage it */
	proto_item	*iec101_item, *ctrlfield_item;
	proto_tree	*iec101_tree, *ctrlfield_tree;
	guint8		frametype, ctrlfield_prm, linkaddr, data_len;
	int		    offset = 0;
	struct      asdu_parms parms;

	/* Make entries in Protocol column on summary display */
	col_set_str(pinfo->cinfo, COL_PROTOCOL, "IEC 60870-5-101");
	col_clear(pinfo->cinfo, COL_INFO);

	iec101_item = proto_tree_add_item(tree, proto_iec60870_101, tvb, 0, -1, ENC_NA);
	iec101_tree = proto_item_add_subtree(iec101_item, ett_iec60870_101);

	/* Add Frame Format to Protocol Tree */
	proto_tree_add_item(iec101_tree, hf_iec60870_101_frame, tvb, offset, 1, ENC_LITTLE_ENDIAN);
	frametype = tvb_get_guint8(tvb, 0);
	offset += 1;

	/* If this is a single character frame, there is nothing left to do... */
	if (frametype == IEC101_SINGLE_CHAR) {
		return offset;
	}

	if (frametype == IEC101_VAR_LEN) {
		proto_tree_add_item(iec101_tree, hf_iec60870_101_length, tvb, offset, 1, ENC_LITTLE_ENDIAN);
		proto_tree_add_item(iec101_tree, hf_iec60870_101_num_user_octets, tvb, offset+1, 1, ENC_LITTLE_ENDIAN);

		/* data_len - 2 is used as we are not including the ctrl field and link address bytes in the length passed to the asdu dissector */
		data_len = tvb_get_guint8(tvb, offset+1) - 2;
		proto_tree_add_item(iec101_tree, hf_iec60870_101_frame, tvb, offset+2, 1, ENC_LITTLE_ENDIAN);
		offset += 3;
	}

	/* Fields common to both variable and fixed length frames */
	ctrlfield_item = proto_tree_add_item(iec101_tree, hf_iec60870_101_ctrlfield, tvb, offset, 1, ENC_LITTLE_ENDIAN);
	ctrlfield_tree = proto_item_add_subtree(ctrlfield_item, ett_iec60870_101_ctrlfield);

	ctrlfield_prm = tvb_get_guint8(tvb, offset) & 0x40;
	if (ctrlfield_prm) {
		col_append_sep_str(pinfo->cinfo, COL_INFO, ", ", "Pri->Sec");
		proto_tree_add_item(ctrlfield_tree, hf_iec60870_101_ctrl_prm, tvb, offset, 1, ENC_LITTLE_ENDIAN);
		proto_tree_add_item(ctrlfield_tree, hf_iec60870_101_ctrl_fcb, tvb, offset, 1, ENC_LITTLE_ENDIAN);
		proto_tree_add_item(ctrlfield_tree, hf_iec60870_101_ctrl_fcv, tvb, offset, 1, ENC_LITTLE_ENDIAN);
		proto_tree_add_item(ctrlfield_tree, hf_iec60870_101_ctrl_func_pri_to_sec, tvb, offset, 1, ENC_LITTLE_ENDIAN);
	}
	else {
		col_append_sep_str(pinfo->cinfo, COL_INFO, ", ", "Sec->Pri");
		proto_tree_add_item(ctrlfield_tree, hf_iec60870_101_ctrl_prm, tvb, offset, 1, ENC_LITTLE_ENDIAN);
		proto_tree_add_item(ctrlfield_tree, hf_iec60870_101_ctrl_dfc, tvb, offset, 1, ENC_LITTLE_ENDIAN);
		proto_tree_add_item(ctrlfield_tree, hf_iec60870_101_ctrl_func_sec_to_pri, tvb, offset, 1, ENC_LITTLE_ENDIAN);
	}
	offset += 1;

	proto_tree_add_item(iec101_tree, hf_iec60870_101_linkaddr, tvb, offset, 1, ENC_LITTLE_ENDIAN);
	linkaddr = tvb_get_guint8(tvb, offset);
	col_append_sep_fstr(pinfo->cinfo, COL_INFO, NULL, "Link Address: %d ", linkaddr);
	offset += 1;

	/* If this is a variable length frame, we need to call the ASDU dissector */
	if (frametype == IEC101_VAR_LEN) {

		/* Retrieve the user preferences */
		parms.cot_len = global_iec60870_cot_len;
		parms.asdu_addr_len = global_iec60870_asdu_addr_len;
		parms.ioa_len = global_iec60870_ioa_len;

		call_dissector_with_data(iec60870_asdu_handle, tvb_new_subset_length_caplen(tvb, offset, -1, data_len), pinfo, tree, &parms);
		offset += data_len;
	}

	proto_tree_add_item(iec101_tree, hf_iec60870_101_checksum, tvb, offset, 1, ENC_LITTLE_ENDIAN);
	proto_tree_add_item(iec101_tree, hf_iec60870_101_stopchar, tvb, offset+1, 1, ENC_LITTLE_ENDIAN);
	offset += 2;

	return offset;

}

static int dissect_iec60870_104_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data)
{
	/* 5th parameter = 6 = minimum bytes received to calculate the length.
	 * (Not 2 in order to find more APCIs in case of 'noisy' bytes between the APCIs)
	 */
	tcp_dissect_pdus(tvb, pinfo, tree, TRUE, APCI_LEN,
			get_iec104apdu_len, dissect_iec60870_104, data);
	return tvb_captured_length(tvb);
}

/* The protocol has two subprotocols: Register APCI */
void
proto_register_iec60870_104(void)
{
	static hf_register_info hf_ap[] = {

		{ &hf_apdulen,
		  { "ApduLen", "iec60870_104.apdulen", FT_UINT8, BASE_DEC, NULL, 0x0,
		    "APDU Len", HFILL }},

		{ &hf_apcitype,
		  { "Type", "iec60870_104.type", FT_UINT8, BASE_HEX, VALS(apci_types), 0x00,
		    "APCI type", HFILL }},

		{ &hf_apciutype,
		  { "UType", "iec60870_104.utype", FT_UINT8, BASE_HEX, VALS(u_types), 0xFC,
		    "Apci U type", HFILL }},

		{ &hf_apcitx,
		  { "Tx", "iec60870_104.tx", FT_UINT16, BASE_DEC, NULL, 0,
		    NULL, HFILL }},

		{ &hf_apcirx,
		  { "Rx", "iec60870_104.rx", FT_UINT16, BASE_DEC, NULL, 0,
		    NULL, HFILL }},

		{ &hf_apcidata,
		  { "Data", "iec60870_104.data", FT_BYTES, BASE_NONE, NULL, 0,
		    NULL, HFILL }},
	};

	static gint *ett_ap[] = {
		&ett_apci,
	};

	proto_iec60870_104 = proto_register_protocol("IEC 60870-5-104", "IEC 60870-5-104", "iec60870_104");

	/* Provide an alias to the previous name of this dissector */
	proto_register_alias(proto_iec60870_104, "104apci");

	proto_register_field_array(proto_iec60870_104, hf_ap, array_length(hf_ap));
	proto_register_subtree_array(ett_ap, array_length(ett_ap));

	prefs_register_protocol(proto_iec60870_104, NULL);
}

/* Register ASDU dissection, shared by the '101 and '104 dissectors */
void
proto_register_iec60870_asdu(void)
{
	static hf_register_info hf_as[] = {

		{ &hf_addr,
		  { "Addr", "iec60870_asdu.addr", FT_UINT16, BASE_DEC, NULL, 0x0,
		    "Common Address of Asdu", HFILL }},

		{ &hf_oa,
		  { "OA", "iec60870_asdu.oa", FT_UINT8, BASE_DEC, NULL, 0x0,
		    "Originator Address", HFILL }},

		{ &hf_typeid,
		  { "TypeId", "iec60870_asdu.typeid", FT_UINT8, BASE_DEC, VALS(asdu_types), 0x0,
		    "Asdu Type Id", HFILL }},

		{ &hf_causetx,
		  { "CauseTx", "iec60870_asdu.causetx", FT_UINT8, BASE_DEC, VALS(causetx_types), F_CAUSE,
		    "Cause of Transmission", HFILL }},

		{ &hf_nega,
		  { "Negative", "iec60870_asdu.nega", FT_BOOLEAN, 8, NULL, F_NEGA,
		    NULL, HFILL }},

		{ &hf_test,
		  { "Test", "iec60870_asdu.test", FT_BOOLEAN, 8, NULL, F_TEST,
		    NULL, HFILL }},

		{ &hf_ioa,
		  { "IOA", "iec60870_asdu.ioa", FT_UINT24, BASE_DEC, NULL, 0x0,
		    "Information Object Address", HFILL }},

		{ &hf_numix,
		  { "NumIx", "iec60870_asdu.numix", FT_UINT8, BASE_DEC, NULL, 0x7F,
		    "Number of Information Objects/Elements", HFILL }},

		{ &hf_sq,
		  { "SQ", "iec60870_asdu.sq", FT_BOOLEAN, 8, NULL, F_SQ,
		    "Sequence", HFILL }},

		{ &hf_cp24time,
		  { "CP24Time", "iec60870_asdu.cp24time", FT_RELATIVE_TIME, BASE_NONE, NULL, 0,
		    NULL, HFILL }},

		{ &hf_cp24time_ms,
		  { "MS", "iec60870_asdu.cp24time.ms", FT_UINT16, BASE_DEC, NULL, 0,
		    "CP24Time milliseconds", HFILL }},

		{ &hf_cp24time_min,
		  { "Min", "iec60870_asdu.cp24time.min", FT_UINT8, BASE_DEC, NULL, 0x3F,
		    "CP24Time minutes", HFILL }},

		{ &hf_cp24time_iv,
		  { "IV", "iec60870_asdu.cp24time.iv", FT_BOOLEAN, 8, TFS(&tfs_invalid_valid), 0x80,
		    "CP24Time invalid", HFILL }},

		{ &hf_cp56time,
		  { "CP56Time", "iec60870_asdu.cp56time", FT_ABSOLUTE_TIME, ABSOLUTE_TIME_LOCAL, NULL, 0,
		    NULL, HFILL }},

		{ &hf_cp56time_ms,
		  { "MS", "iec60870_asdu.cp56time.ms", FT_UINT16, BASE_DEC, NULL, 0,
		    "CP56Time milliseconds", HFILL }},

		{ &hf_cp56time_min,
		  { "Min", "iec60870_asdu.cp56time.min", FT_UINT8, BASE_DEC, NULL, 0x3F,
		    "CP56Time minutes", HFILL }},

		{ &hf_cp56time_iv,
		  { "IV", "iec60870_asdu.cp56time.iv", FT_BOOLEAN, 8, TFS(&tfs_invalid_valid), 0x80,
		    "CP56Time invalid", HFILL }},

		{ &hf_cp56time_hour,
		  { "Hour", "iec60870_asdu.cp56time.hour", FT_UINT8, BASE_DEC, NULL, 0x1F,
		    "CP56Time hours", HFILL }},

		{ &hf_cp56time_su,
		  { "SU", "iec60870_asdu.cp56time.su", FT_BOOLEAN, 8, TFS(&tfs_local_dst), 0x80,
		    "CP56Time summer time", HFILL }},

		{ &hf_cp56time_day,
		  { "Day", "iec60870_asdu.cp56time.day", FT_UINT8, BASE_DEC, NULL, 0x1F,
		    "CP56Time day", HFILL }},

		{ &hf_cp56time_dow,
		  { "DOW", "iec60870_asdu.cp56time.dow", FT_UINT8, BASE_DEC, NULL, 0xE0,
		    "CP56Time day of week", HFILL }},

		{ &hf_cp56time_month,
		  { "Month", "iec60870_asdu.cp56time.month", FT_UINT8, BASE_DEC, NULL, 0x0F,
		    "CP56Time month", HFILL }},

		{ &hf_cp56time_year,
		  { "Year", "iec60870_asdu.cp56time.year", FT_UINT8, BASE_DEC, NULL, 0x7F,
		    "CP56Time year", HFILL }},

		{ &hf_siq,
		  { "SIQ", "iec60870_asdu.siq", FT_UINT8, BASE_HEX, NULL, 0,
		    NULL, HFILL }},

		{ &hf_siq_spi,
		  { "SPI", "iec60870_asdu.siq.spi", FT_BOOLEAN, 8, TFS(&tfs_on_off), 0x01,
		    "SIQ SPI", HFILL }},

		{ &hf_siq_bl,
		  { "BL", "iec60870_asdu.siq.bl", FT_BOOLEAN, 8, TFS(&tfs_blocked_not_blocked), 0x10,
		    "SIQ BL", HFILL }},

		{ &hf_siq_sb,
		  { "SB", "iec60870_asdu.siq.sb", FT_BOOLEAN, 8, TFS(&tfs_substituted_not_substituted), 0x20,
		    "SIQ SB", HFILL }},

		{ &hf_siq_nt,
		  { "NT", "iec60870_asdu.siq.nt", FT_BOOLEAN, 8, TFS(&tfs_not_topical_topical), 0x40,
		    "SIQ NT", HFILL }},

		{ &hf_siq_iv,
		  { "IV", "iec60870_asdu.siq.iv", FT_BOOLEAN, 8, TFS(&tfs_invalid_valid), 0x80,
		    "SIQ IV", HFILL }},

		{ &hf_diq,
		  { "DIQ", "iec60870_asdu.diq", FT_UINT8, BASE_HEX, NULL, 0,
		    NULL, HFILL }},

		{ &hf_diq_dpi,
		  { "DPI", "iec60870_asdu.diq.dpi", FT_UINT8, BASE_DEC, VALS(diq_types), 0x03,
		    "DIQ DPI", HFILL }},

		{ &hf_diq_bl,
		  { "BL", "iec60870_asdu.diq.bl", FT_BOOLEAN, 8, TFS(&tfs_blocked_not_blocked), 0x10,
		    "DIQ BL", HFILL }},

		{ &hf_diq_sb,
		  { "SB", "iec60870_asdu.diq.sb", FT_BOOLEAN, 8, TFS(&tfs_substituted_not_substituted), 0x20,
		    "DIQ SB", HFILL }},

		{ &hf_diq_nt,
		  { "NT", "iec60870_asdu.diq.nt", FT_BOOLEAN, 8, TFS(&tfs_not_topical_topical), 0x40,
		    "DIQ NT", HFILL }},

		{ &hf_diq_iv,
		  { "IV", "iec60870_asdu.diq.iv", FT_BOOLEAN, 8, TFS(&tfs_invalid_valid), 0x80,
		    "DIQ IV", HFILL }},

		{ &hf_qds,
		  { "QDS", "iec60870_asdu.qds", FT_UINT8, BASE_HEX, NULL, 0,
		    NULL, HFILL }},

		{ &hf_qds_ov,
		  { "OV", "iec60870_asdu.qds.ov", FT_BOOLEAN, 8, TFS(&tfs_overflow_no_overflow), 0x01,
		    "QDS OV", HFILL }},

		{ &hf_qds_bl,
		  { "BL", "iec60870_asdu.qds.bl", FT_BOOLEAN, 8, TFS(&tfs_blocked_not_blocked), 0x10,
		    "QDS BL", HFILL }},

		{ &hf_qds_sb,
		  { "SB", "iec60870_asdu.qds.sb", FT_BOOLEAN, 8, TFS(&tfs_substituted_not_substituted), 0x20,
		    "QDS SB", HFILL }},

		{ &hf_qds_nt,
		  { "NT", "iec60870_asdu.qds.nt", FT_BOOLEAN, 8, TFS(&tfs_not_topical_topical), 0x40,
		    "QDS NT", HFILL }},

		{ &hf_qds_iv,
		  { "IV", "iec60870_asdu.qds.iv", FT_BOOLEAN, 8, TFS(&tfs_invalid_valid), 0x80,
		    "QDS IV", HFILL }},

		{ &hf_vti,
		  { "VTI", "iec60870_asdu.vti", FT_UINT8, BASE_HEX, NULL, 0,
		    NULL, HFILL }},

		{ &hf_vti_v,
		  { "Value", "iec60870_asdu.vti.v", FT_INT8, BASE_DEC, NULL, 0x7F,
		    "VTI Value", HFILL }},

		{ &hf_vti_t,
		  { "T", "iec60870_asdu.vti.t", FT_BOOLEAN, 8, TFS(&tfs_transient_not_transient), 0x80,
		    "VTI T", HFILL }},

		{ &hf_qos,
		  { "QOS", "iec60870_asdu.qos", FT_UINT8, BASE_HEX, NULL, 0,
		    NULL, HFILL }},

		{ &hf_qos_ql,
		  { "QL", "iec60870_asdu.qos.ql", FT_UINT8, BASE_DEC, NULL, 0x7F,
		    "QOS QL", HFILL }},

		{ &hf_qos_se,
		  { "S/E", "iec60870_asdu.qos.se", FT_BOOLEAN, 8, TFS(&tfs_select_execute), 0x80,
		    "QOS S/E", HFILL }},

		{ &hf_sco,
		  { "SCO", "iec60870_asdu.sco", FT_UINT8, BASE_HEX, NULL, 0,
		    NULL, HFILL }},

		{ &hf_sco_on,
		  { "ON/OFF", "iec60870_asdu.sco.on", FT_BOOLEAN, 8, TFS(&tfs_on_off), 0x01,
		    "SCO SCS", HFILL }},

		{ &hf_sco_qu,
		  { "QU", "iec60870_asdu.sco.qu", FT_UINT8, BASE_DEC, VALS(qos_qu_types), 0x7C,
		    "SCO QU", HFILL }},

		{ &hf_sco_se,
		  { "S/E", "iec60870_asdu.sco.se", FT_BOOLEAN, 8, TFS(&tfs_select_execute), 0x80,
		    "SCO S/E", HFILL }},

		{ &hf_dco,
		  { "DCO", "iec60870_asdu.dco", FT_UINT8, BASE_HEX, NULL, 0,
		    NULL, HFILL }},

		{ &hf_dco_on,
		  { "ON/OFF", "iec60870_asdu.dco.on", FT_UINT8, BASE_DEC, VALS(dco_on_types), 0x03,
		    "DCO DCS", HFILL }},

		{ &hf_dco_qu,
		  { "QU", "iec60870_asdu.dco.qu", FT_UINT8, BASE_DEC, VALS(qos_qu_types), 0x7C,
		    "DCO QU", HFILL }},

		{ &hf_dco_se,
		  { "S/E", "iec60870_asdu.dco.se", FT_BOOLEAN, 8, TFS(&tfs_select_execute), 0x80,
		    "DCO S/E", HFILL }},

		{ &hf_rco,
		  { "RCO", "iec60870_asdu.rco", FT_UINT8, BASE_HEX, NULL, 0,
		    NULL, HFILL }},

		{ &hf_rco_up,
		  { "UP/DOWN", "iec60870_asdu.rco.up", FT_UINT8, BASE_DEC, VALS(rco_up_types), 0x03,
		    "RCO RCS", HFILL }},

		{ &hf_rco_qu,
		  { "QU", "iec60870_asdu.rco.qu", FT_UINT8, BASE_DEC, VALS(qos_qu_types), 0x7C,
		    "RCO QU", HFILL }},

		{ &hf_rco_se,
		  { "S/E", "iec60870_asdu.rco.se", FT_BOOLEAN, 8, TFS(&tfs_select_execute), 0x80,
		    "RCO S/E", HFILL }},

		{ &hf_qpm,
		  { "QPM", "iec60870_asdu.qpm", FT_UINT8, BASE_HEX, NULL, 0,
		    NULL, HFILL } },

		{ &hf_qpm_kpa,
		  { "KPA", "iec60870_asdu.qpm.kpa", FT_UINT8, BASE_DEC, VALS(qpm_kpa_types), 0x3F,
		    "QPM KPA", HFILL } },

		{ &hf_qpm_lpc,
		  { "LPC", "iec60870_asdu.qpm.lpc", FT_UINT8, BASE_DEC, VALS(qpm_lpc_types), 0x40,
		    "QPM LPC", HFILL } },

		{ &hf_qpm_pop,
		  { "POP", "iec60870_asdu.qpm.pop", FT_UINT8, BASE_DEC, VALS(qpm_pop_types), 0x80,
		    "QPM POP", HFILL } },

		{ &hf_coi,
		  { "COI", "iec60870_asdu.coi", FT_UINT8, BASE_HEX, NULL, 0,
		    NULL, HFILL }},

		{ &hf_coi_r,
		  { "R", "iec60870_asdu.coi_r", FT_UINT8, BASE_DEC, VALS(coi_r_types), 0x7F,
		    "COI R", HFILL }},

		{ &hf_coi_i,
		  { "I", "iec60870_asdu.coi_i", FT_BOOLEAN, 8, TFS(&tfs_coi_i), 0x80,
		    "COI I", HFILL }},

		{ &hf_qoi,
		  { "QOI", "iec60870_asdu.qoi", FT_UINT8, BASE_DEC, VALS(qoi_r_types), 0,
		    NULL, HFILL }},

		{ &hf_qrp,
		  { "QRP", "iec60870_asdu.qrp", FT_UINT8, BASE_DEC, VALS(qrp_r_types), 0,
		    NULL, HFILL }},

		{ &hf_bcr_count,
		  { "Binary Counter", "iec60870_asdu.bcr.count", FT_INT32, BASE_DEC, NULL, 0x0,
		    NULL, HFILL }},

		{ &hf_bcr_sq,
		  { "SQ", "iec60870_asdu.bcr.sq", FT_UINT8, BASE_DEC, NULL, 0x1F,
		    "Sequence Number", HFILL }},

		{ &hf_bcr_cy,
		  { "CY", "iec60870_asdu.bcr.cy", FT_BOOLEAN, 8, TFS(&tfs_overflow_no_overflow), 0x20,
		    "Counter Overflow", HFILL }},

		{ &hf_bcr_ca,
		  { "CA", "iec60870_asdu.bcr.ca", FT_BOOLEAN, 8, TFS(&tfs_adjusted_not_adjusted), 0x40,
		    "Counter Adjusted", HFILL }},

		{ &hf_bcr_iv,
		  { "IV", "iec60870_asdu.bcr.iv", FT_BOOLEAN, 8, TFS(&tfs_invalid_valid), 0x80,
		    "Counter Validity", HFILL }},

		{ &hf_start,
		  { "START", "iec60870_asdu.start", FT_UINT8, BASE_HEX, NULL, 0x0,
		    NULL, HFILL }},

		{ &hf_asdu_bitstring,
		  { "Value", "iec60870_asdu.bitstring", FT_UINT32, BASE_HEX, NULL, 0x0,
		    "BSI value", HFILL }},

		{ &hf_asdu_float,
		  { "Value", "iec60870_asdu.float", FT_FLOAT, BASE_NONE, NULL, 0x0,
		    "Float value", HFILL }},

		{ &hf_asdu_normval,
		  { "Value", "iec60870_asdu.normval", FT_FLOAT, BASE_NONE, NULL, 0x0,
		    "Normalised value", HFILL }},

		{ &hf_asdu_scalval,
		  { "Value", "iec60870_asdu.scalval", FT_INT16, BASE_DEC, NULL, 0x0,
		    "Scaled value", HFILL }},

		{ &hf_asdu_raw_data,
		  { "Raw Data", "iec60870_asdu.rawdata", FT_BYTES, BASE_NONE, NULL, 0x0,
		    "Information object raw data", HFILL }},
	};

	static gint *ett_as[] = {
		&ett_asdu,
		&ett_asdu_objects,
		&ett_siq,
		&ett_diq,
		&ett_qds,
		&ett_qos,
		&ett_vti,
		&ett_sco,
		&ett_dco,
		&ett_rco,
		&ett_qpm,
		&ett_coi,
		&ett_cp24time,
		&ett_cp56time
	};

	static ei_register_info ei[] = {
		{ &ei_iec104_short_asdu, { "iec104.short_asdu", PI_MALFORMED, PI_ERROR, "<ERR Short Asdu>", EXPFILL }},
		{ &ei_iec104_apdu_min_len, { "iec104.apdu_min_len", PI_MALFORMED, PI_ERROR, "APDU less than bytes", EXPFILL }},
		{ &ei_iec104_apdu_invalid_len, { "iec104.apdu_invalid_len", PI_MALFORMED, PI_ERROR, "Invalid ApduLen", EXPFILL }},
	};

	expert_module_t* expert_iec60870;

	proto_iec60870_asdu = proto_register_protocol("IEC 60870-5-101/104 ASDU", "IEC 60870-5-101/104 ASDU", "iec60870_asdu");
	iec60870_asdu_handle = create_dissector_handle(dissect_iec60870_asdu, proto_iec60870_asdu);

	/* Provide an alias to the previous name of this dissector */
	proto_register_alias(proto_iec60870_asdu, "104asdu");

	proto_register_field_array(proto_iec60870_asdu, hf_as, array_length(hf_as));
	proto_register_subtree_array(ett_as, array_length(ett_as));
	expert_iec60870 = expert_register_protocol(proto_iec60870_asdu);
	expert_register_field_array(expert_iec60870, ei, array_length(ei));

}

/* The registration hand-off routine */
void
proto_reg_handoff_iec60870_104(void)
{
	dissector_handle_t iec60870_104_handle;

	iec60870_104_handle = create_dissector_handle(dissect_iec60870_104_tcp, proto_iec60870_104);

	dissector_add_uint_with_preference("tcp.port", IEC104_PORT, iec60870_104_handle);
}

static void
apply_iec60870_101_prefs(void)
{
  /* IEC101 uses user customizable preferences for the configurable field lengths */
  global_iec60870_cot_len       = prefs_get_uint_value("iec60870_101", "cot_len");
  global_iec60870_asdu_addr_len = prefs_get_uint_value("iec60870_101", "asdu_addr_len");
  global_iec60870_ioa_len       = prefs_get_uint_value("iec60870_101", "asdu_ioa_len");
}


/******************************************************************************************************/
/* Return length of IEC 101 Protocol over TCP message (used for re-assembly)						 */
/******************************************************************************************************/
static guint
get_iec101_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset _U_, void *data _U_)
{

	guint len=0, type;
	type = tvb_get_guint8(tvb, offset);

	switch (type) {
		case IEC101_SINGLE_CHAR:
			len = 1;
			break;
		case IEC101_FIXED_LEN:
			len = 5;
			break;
		case IEC101_VAR_LEN:
			len = tvb_get_guint8(tvb, offset+1) + 6;
			break;
	}

	return len;
}

/******************************************************************************************************/
/* Dissect (and possibly Re-assemble) IEC 101 protocol payload data */
/******************************************************************************************************/
static int
dissect_iec60870_101_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{

	guint type = tvb_get_guint8(tvb, 0);

	/* Check that this is actually a IEC 60870-5-101 packet. */
	switch (type) {
		case IEC101_SINGLE_CHAR:
		case IEC101_FIXED_LEN:
		case IEC101_VAR_LEN:
			tcp_dissect_pdus(tvb, pinfo, tree, TRUE, 1, get_iec101_len, dissect_iec60870_101, data);
			break;
		default:
			return 0;
	}

	return tvb_captured_length(tvb);
}

/* The registration hand-off routine */
void
proto_register_iec60870_101(void)
{
	/* IEC 101 Protocol header fields */
	static hf_register_info iec60870_101_hf[] = {
		{ &hf_iec60870_101_frame,
		{ "Frame Format", "iec60870_101.header", FT_UINT8, BASE_HEX, VALS(iec60870_101_frame_vals), 0x0, NULL, HFILL }},
		{ &hf_iec60870_101_length,
		{ "Length", "iec60870_101.length", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL }},
		{ &hf_iec60870_101_num_user_octets,
		{ "Number of User Octets", "iec60870_101.num_user_octets", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL }},
		{ &hf_iec60870_101_ctrlfield,
		{ "Control Field", "iec60870_101.ctrlfield", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }},
		{ &hf_iec60870_101_ctrl_prm,
		{ "PRM", "iec60870_101.ctrl_prm", FT_UINT8, BASE_DEC, VALS(iec60870_101_ctrl_prm_values), 0x40, "Primary Message", HFILL }},
		{ &hf_iec60870_101_ctrl_fcb,
		{ "FCB", "iec60870_101.ctrl_fcb", FT_UINT8, BASE_DEC, NULL, 0x20, "Frame Count Bit", HFILL }},
		{ &hf_iec60870_101_ctrl_fcv,
		{ "FCV", "iec60870_101.ctrl_fcv", FT_UINT8, BASE_DEC, NULL, 0x10, "Frame Count Bit Valid", HFILL }},
		{ &hf_iec60870_101_ctrl_dfc,
		{ "DFC", "iec60870_101.ctrl_dfc", FT_UINT8, BASE_DEC, NULL, 0x10, "Data Flow Control", HFILL }},
		{ &hf_iec60870_101_ctrl_func_pri_to_sec,
		{ "CF Func Code", "iec60870_101.ctrl_func_pri_to_sec", FT_UINT8, BASE_DEC, VALS(iec60870_101_ctrl_func_pri_to_sec_values), 0x0F, "Control Field Function Code, Pri to Sec", HFILL }},
		{ &hf_iec60870_101_ctrl_func_sec_to_pri,
		{ "CF Func Code", "iec60870_101.ctrl_func_sec_to_pri", FT_UINT8, BASE_DEC, VALS(iec60870_101_ctrl_func_sec_to_pri_values), 0x0F, "Control Field Function Code, Sec to Pri", HFILL }},
		{ &hf_iec60870_101_linkaddr,
		{ "Data Link Address", "iec60870_101.linkaddr", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL }},
		{ &hf_iec60870_101_checksum,
		{ "Checksum", "iec60870_101.checksum", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }},
		{ &hf_iec60870_101_stopchar,
		{ "Stop Character", "iec60870_101.stopchar", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }},

	};

	/* Setup protocol subtree array */
	static gint *ett_serial[] = {
		&ett_iec60870_101,
		&ett_iec60870_101_ctrlfield,
	};

	module_t *iec60870_101_module;

	/* Register the protocol name and description */
	proto_iec60870_101 = proto_register_protocol("IEC 60870-5-101", "IEC 60870-5-101", "iec60870_101");

	/* Required function calls to register the header fields and subtrees used */
	proto_register_field_array(proto_iec60870_101, iec60870_101_hf, array_length(iec60870_101_hf));
	proto_register_subtree_array(ett_serial, array_length(ett_serial));

	/* Register required preferences for IEC 101 configurable field lengths */
	iec60870_101_module = prefs_register_protocol(proto_iec60870_101, NULL);

	static const enum_val_t cot_len[] = {
		{"1", "1 octet", 1},
		{"2", "2 octet", 2},
		{NULL, NULL, -1}
	};

	static const enum_val_t asdu_addr_len[] = {
		{"1", "1 octet", 1},
		{"2", "2 octet", 2},
		{NULL, NULL, -1}
	};

	static const enum_val_t asdu_ioa_len[] = {
		{"2", "2 octet", 2},
		{"3", "3 octet", 3},
		{NULL, NULL, -1}
	};

	prefs_register_enum_preference(iec60870_101_module, "cot_len",
		"Length of the Cause of Transmission Field",
		"Length of the Cause of Transmission Field, configurable in '101 and fixed at 2 octets with '104",
		&global_iec60870_cot_len, cot_len, FALSE);

	prefs_register_enum_preference(iec60870_101_module, "asdu_addr_len",
		"Length of the Common ASDU Address Field",
		"Length of the Common ASDU Address Field, configurable in '101 and fixed at 2 octets with '104",
		&global_iec60870_asdu_addr_len, asdu_addr_len, FALSE);

	prefs_register_enum_preference(iec60870_101_module, "asdu_ioa_len",
		"Length of the Information Object Address Field",
		"Length of the Information Object Address Field, configurable in '101 and fixed at 3 octets with '104",
		&global_iec60870_ioa_len, asdu_ioa_len, FALSE);

}

void
proto_reg_handoff_iec60870_101(void)
{
	dissector_handle_t iec60870_101_handle;

	iec60870_101_handle = create_dissector_handle(dissect_iec60870_101_tcp, proto_iec60870_101);

	/* Add decode-as connection to determine user-customized TCP port */
	dissector_add_for_decode_as_with_preference("tcp.port", iec60870_101_handle);
	/* Add dissection for serial pcap files generated by the RTAC */
	dissector_add_for_decode_as("rtacser.data", iec60870_101_handle);

	apply_iec60870_101_prefs();
}

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