/* packet-iec104.c * Routines for IEC-60870-5-101 & 104 Protocol disassembly * * Copyright (c) 2008 by Joan Ramio * Joan is a masculine catalan name. Search the Internet for Joan Pujol (alias Garbo). * * Copyright (c) 2009 by Kjell Hultman * Added dissection of signal (ASDU) information. * Kjell is also a masculine name, but a Scandinavian one. * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1999 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "config.h" #include /* floor */ #include #include #include #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); /* All fields are little endian. */ #define MAXS 256 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 static guint iec104_port = IEC104_PORT; /* 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_DP_NA_1 3 /* double-point information */ #define M_ST_NA_1 5 /* step position information */ #define M_BO_NA_1 7 /* bitstring of 32 bits */ #define M_ME_NA_1 9 /* measured value, normalized value */ #define M_ME_NB_1 11 /* measured value, scaled value */ #define M_ME_NC_1 13 /* measured value, short floating point number */ #define M_IT_NA_1 15 /* integrated totals */ #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_DP_NA_1, "M_DP_NA_1" }, { M_ST_NA_1, "M_ST_NA_1" }, { M_BO_NA_1, "M_BO_NA_1" }, { M_ME_NA_1, "M_ME_NA_1" }, { M_ME_NB_1, "M_ME_NB_1" }, { M_ME_NC_1, "M_ME_NC_1" }, { M_IT_NA_1, "M_IT_NA_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_DP_NA_1, "double-point information" }, { M_ST_NA_1, "step position information" }, { M_BO_NA_1, "bitstring of 32 bits" }, { M_ME_NA_1, "measured value, normalized value" }, { M_ME_NB_1, "measured value, scaled value" }, { M_ME_NC_1, "measured value, short floating point number" }, { M_IT_NA_1, "integrated totals" }, { 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_DP_NA_1, 1 }, { M_ST_NA_1, 2 }, { M_BO_NA_1, 5 }, { M_ME_NA_1, 3 }, { M_ME_NB_1, 3 }, { M_ME_NC_1, 5 }, { M_IT_NA_1, 5 }, { 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 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 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_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_coi = -1; static int hf_coi_r = -1; static int hf_coi_i = -1; static int hf_qoi = -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_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 */ /* ==================================================================== void get_CP56Time(td_CP56Time *cp56t, tvbuff_t *tvb, guint8 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)++; } /* ==================================================================== 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_rco); 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)++; } /* .... 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, " "); wmem_strbuf_append_printf(res, "ASDU=%u %s %s", asduh.Addr, val_to_str(asduh.TypeId, asdu_types, ""), 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, "") : ""); /* 'Signal Details': TREE */ /* -------- get signal value and status based on ASDU type id */ switch (asduh.TypeId) { case M_SP_NA_1: case M_DP_NA_1: case M_ST_NA_1: case M_BO_NA_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_NB_1: case M_ME_NC_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_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: /* -- 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_DP_NA_1: /* 3 Double-point information */ get_DIQ(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_BO_NA_1: /* 7 Bitstring of 32 bits */ get_BSI(tvb, &offset, trSignal); get_QDS(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_NB_1: /* 11 Measured value, scaled value */ get_SVA(tvb, &offset, trSignal); get_QDS(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_IT_NA_1: /* 15 Integrated totals */ get_BCR(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; 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, " ", 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, " ", 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 == iec104_port ? "->" : "<-"), val_to_str_const(type, apci_types, "")); } else { wmem_strbuf_append_printf(res, " ", 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, "")); 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); } static void apply_iec60870_104_prefs(void) { /* IEC104 uses the port preference to determine direction */ iec104_port = prefs_get_uint_value("iec60870_104", "tcp.port"); } /* 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, apply_iec60870_104_prefs); } /* 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 Transmision", 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_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, 0xFFFF, "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_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_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_cp56time }; static ei_register_info ei[] = { { &ei_iec104_short_asdu, { "iec104.short_asdu", PI_MALFORMED, PI_ERROR, "", 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); apply_iec60870_104_prefs(); } 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: */