From Didier Gautheron:
[obnox/wireshark/wip.git] / epan / dissectors / packet-iec104.c
1 /* packet-iec104.c
2  * Routines for IEC-60870-5-104 (iec104) Protocol disassembly
3  *
4  *
5  * $Id$
6  *
7  * Copyright (c) 2008 by Joan Ramio <joan@ramio.cat>
8  * Joan is a masculine catalan name. Search the Internet for Joan Pujol (alias Garbo).
9  *
10  * Copyright (c) 2009 by Kjell Hultman <kjell.hultman@gmail.com> 
11  * Added dissection of signal (ASDU) information.
12  * Kjell is also a masculine name, but a Scandinavian one.
13  *
14  * Wireshark - Network traffic analyzer
15  * By Gerald Combs <gerald@wireshark.org>
16  * Copyright 1999 Gerald Combs
17  *
18  * This program is free software; you can redistribute it and/or
19  * modify it under the terms of the GNU General Public License
20  * as published by the Free Software Foundation; either version 2
21  * of the License, or (at your option) any later version.
22  *
23  * This program is distributed in the hope that it will be useful,
24  * but WITHOUT ANY WARRANTY; without even the implied warranty of
25  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26  * GNU General Public License for more details.
27  *
28  * You should have received a copy of the GNU General Public License
29  * along with this program; if not, write to the Free Software
30  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
31  */
32
33 #ifdef HAVE_CONFIG_H
34 #include "config.h"
35 #endif
36
37 #include <stdio.h>
38 #include <string.h>
39 #include <glib.h>
40 #include <math.h> /* floor */
41
42 #include <epan/packet.h>
43 #include <epan/dissectors/packet-tcp.h>
44 #include <epan/emem.h>
45
46 /* IEC-104 comment: Fields are little endian. */
47
48 #define MAXS 256
49
50 static dissector_handle_t iec104asdu_handle;
51
52 /* the asdu header structure */
53 struct asduheader {
54         guint8 AddrLow;
55         guint8 AddrHigh;
56         guint8 OA;
57         guint8 TypeId;
58         guint8 TNCause;
59         guint32 IOA;
60         guint8 NumIx;
61         guint8 SQ;
62 };
63
64 /* the apci header structure */
65 struct apciheader {
66         guint8 ApduLen;
67         guint8 Type;
68         guint8 UType;
69         guint16 Tx;
70         guint16 Rx;
71 };
72
73 /* asdu value time stamp structure */
74 typedef struct
75 {
76   guint8 cp56t_ms;
77   guint8 cp56t_s;
78   guint8 cp56t_min;
79   guint8 cp56t_h;
80   guint8 cp56t_dom;    /* day of month */
81   guint8 cp56t_dow;    /* day of week */
82   guint8 cp56t_month;
83   guint8 cp56t_year;
84   
85   gboolean IV;  /* Invalid (1) */
86
87
88 } td_CP56Time;
89
90 /* asdu value/status structure */
91 typedef struct {
92         union {
93                 guint8  VTI; /* Value w transient state indication, */ 
94                                          /* CP8: value I7[1..7]<-64..+63>,  */
95                                          /* Transient BS1[8]<0..1>0: eq. not in transient state, 1: in trans ... */
96                 gint16 NVA;  /* Normalized value F16[1..16]<-1..+1-2^-15> */
97                 gint16 SVA;  /* Scaled value I16[1..16]<-2^15..+2^15-1> */
98                 gfloat  FLT; /* IEEE 754 float value  R32.23{Fraction,Exponent,Sign} */
99                 /* ToDo -- BCR Binary counter reading */
100         } MV;   /* Measured Value */
101
102         /* together with VTI */
103         gboolean TRANSIENT; /* equipment is in transient state */
104
105         /* boolean values */
106         gboolean IPOS0; /* double-point: indeterminate or intermediate state  */
107         gboolean OFF;
108         gboolean ON;
109         gboolean IPOS3; /* double-point: indeterminate state  (fault?) */
110         
111         /* quality descriptor-bits  */
112         gboolean BL;  /* Blocked (1) */
113         gboolean SB;  /* Substituted (1) */
114         gboolean NT;  /* Topical (0) / Not topical (1) [Topical <=> if most recent update was succesful] */
115         gboolean IV;  /* Invalid (1) */
116         /* from separat quality descriptor  */
117         gboolean OV;  /* Overflow (1) */
118         /* from separate quality descriptor  */
119         gboolean EI;  /* Elapsed time valid (0) / Elapsed time invalid (1) */
120         
121 } td_ValueInfo;
122
123 /* asdu command value/status structure */
124 typedef struct {
125         gboolean OFF;
126         gboolean ON;
127         
128         /* QOC qualifier-bits */
129         guint16  QU;      /* qualifier-value */
130         gboolean ZeroP;   /* No pulse */
131         gboolean ShortP;  /* Short Pulse */
132         gboolean LongP;   /* Long Pulse */
133         gboolean Persist; /* Persistent output */
134         gboolean SE;      /* Select (1) / Execute (0) */
135
136
137 } td_CmdInfo;
138
139 /* asdu setpoint value/status structure */
140 typedef struct {
141         union {                                                                  
142                 gint16 NVA; /* Normalized value F16[1..16]<-1..+1-2^-15>            */
143                 gint16 SVA; /* Scaled value I16[1..16]<-2^15..+2^15-1>              */
144                 gfloat  FLT; /* IEEE 754 float value  R32.23{Fraction,Exponent,Sign} */
145         } SP;   /* Measured Value */
146         
147         /* QOS qualifier-bits  */
148         guint8 QL;    /* UI7[1..7]<0..127>; 0-default, 1..63-reserved for strd def.,     */
149                                   /*                            64..127-reserved for special use (private range) */ 
150         gboolean SE;  /* Select (1) / Execute (0)                                        */
151
152 } td_SpInfo;
153
154
155
156 static guint iec104port = 2404;
157
158 /* Define the iec104 proto */
159 static int proto_iec104apci = -1;
160 static int proto_iec104asdu = -1;
161
162 /* Protocol constants */
163 #define APCI_START      0x68
164 #define APCI_LEN        6
165 #define APCI_START_LEN  2
166 #define APCI_DATA_LEN   (APCI_LEN- APCI_START_LEN)
167 #define APDU_MIN_LEN    4
168 #define APDU_MAX_LEN    253
169
170 /* ASDU_HEAD_LEN: Includes Asdu head and first IOA */
171 #define ASDU_HEAD_LEN   9
172 #define F_TEST  0x80
173 #define F_NEGA  0x40
174 #define F_CAUSE 0x3F
175 #define F_SQ    0x80
176
177 /* APCI types */
178 #define I_TYPE          0
179 #define S_TYPE          1
180 #define U_TYPE          3
181 #define APCI_TYPE_UNKNOWN 4
182 static const value_string apci_types [] = {
183         { I_TYPE,               "I" },
184         { S_TYPE,               "S" },
185         { U_TYPE,               "U" },
186         { 0, NULL }
187 };
188
189
190 /* Constants relative to the filed, independent of the field position in the byte */
191 /* U (Unnombered) constants */
192 #define U_STARTDT_ACT           0x01
193 #define U_STARTDT_CON           0x02
194 #define U_STOPDT_ACT            0x04
195 #define U_STOPDT_CON            0x08
196 #define U_TESTFR_ACT            0x10
197 #define U_TESTFR_CON            0x20
198 static const value_string u_types[] = {
199         { U_STARTDT_ACT,                "STARTDT act" },
200         { U_STARTDT_CON,                "STARTDT con" },
201         { U_STOPDT_ACT,                 "STOPDT act" },
202         { U_STOPDT_CON,                 "STOPDT con" },
203         { U_TESTFR_ACT,                 "TESTFR act" },
204         { U_TESTFR_CON,                 "TESTFR con" },
205         { 0, NULL }
206 };
207
208
209 /* ASDU types (TypeId) */
210 #define M_SP_NA_1  1    /* single-point information                                                             */
211 #define M_DP_NA_1  3    /* double-point information                                                             */
212 #define M_ST_NA_1  5    /* step position information                                                            */
213 #define M_BO_NA_1  7    /* bitstring of 32 bits                                                                 */
214 #define M_ME_NA_1  9    /* measured value, normalized value                                                     */
215 #define M_ME_NB_1  11    /* measured value, scaled value                                                        */
216 #define M_ME_NC_1  13    /* measured value, short floating point number                                         */
217 #define M_IT_NA_1  15    /* integrated totals                                                                   */
218 #define M_PS_NA_1  20    /* packed single-point information with status change detection                        */
219 #define M_ME_ND_1  21    /* measured value, normalized value without quality descriptor                         */
220 #define M_SP_TB_1  30    /* single-point information with time tag CP56Time2a                                   */
221 #define M_DP_TB_1  31    /* double-point information with time tag CP56Time2a                                   */
222 #define M_ST_TB_1  32    /* step position information with time tag CP56Time2a                                  */
223 #define M_BO_TB_1  33    /* bitstring of 32 bit with time tag CP56Time2a                                        */
224 #define M_ME_TD_1  34    /* measured value, normalized value with time tag CP56Time2a                           */
225 #define M_ME_TE_1  35    /* measured value, scaled value with time tag CP56Time2a                               */
226 #define M_ME_TF_1  36    /* measured value, short floating point number with time tag CP56Time2a                */
227 #define M_IT_TB_1  37    /* integrated totals with time tag CP56Time2a                                          */
228 #define M_EP_TD_1  38    /* event of protection equipment with time tag CP56Time2a                              */
229 #define M_EP_TE_1  39    /* packed start events of protection equipment with time tag CP56Time2a                */
230 #define M_EP_TF_1  40    /* packed output circuit information of protection equipment with time tag CP56Time2a  */
231 #define C_SC_NA_1  45    /* single command                                                                      */
232 #define C_DC_NA_1  46    /* double command                                                                      */
233 #define C_RC_NA_1  47    /* regulating step command                                                             */
234 #define C_SE_NA_1  48    /* set point command, normalized value                                                 */
235 #define C_SE_NB_1  49    /* set point command, scaled value                                                     */
236 #define C_SE_NC_1  50    /* set point command, short floating point number                                      */
237 #define C_BO_NA_1  51    /* bitstring of 32 bits                                                                */
238 #define C_SC_TA_1  58    /* single command with time tag CP56Time2a                                             */
239 #define C_DC_TA_1  59    /* double command with time tag CP56Time2a                                             */
240 #define C_RC_TA_1  60    /* regulating step command with time tag CP56Time2a                                    */
241 #define C_SE_TA_1  61    /* set point command, normalized value with time tag CP56Time2a                        */
242 #define C_SE_TB_1  62    /* set point command, scaled value with time tag CP56Time2a                            */
243 #define C_SE_TC_1  63    /* set point command, short floating-point number with time tag CP56Time2a             */
244 #define C_BO_TA_1  64    /* bitstring of 32 bits with time tag CP56Time2a                                       */
245 #define M_EI_NA_1  70    /* end of initialization                                                               */
246 #define C_IC_NA_1  100    /* interrogation command                                                              */
247 #define C_CI_NA_1  101    /* counter interrogation command                                                      */
248 #define C_RD_NA_1  102    /* read command                                                                       */
249 #define C_CS_NA_1  103    /* clock synchronization command                                                      */
250 #define C_RP_NA_1  105    /* reset process command                                                              */
251 #define C_TS_TA_1  107    /* test command with time tag CP56Time2a                                              */
252 #define  P_ME_NA_1  110    /* parameter of measured value, normalized value                                     */
253 #define  P_ME_NB_1  111    /* parameter of measured value, scaled value                                         */
254 #define  P_ME_NC_1  112    /* parameter of measured value, short floating-point number                          */
255 #define  P_AC_NA_1  113    /* parameter activation                                                              */
256 #define  F_FR_NA_1  120    /* file ready                                                                        */
257 #define  F_SR_NA_1  121    /* section ready                                                                     */
258 #define  F_SC_NA_1  122    /* call directory, select file, call file, call section                              */
259 #define  F_LS_NA_1  123    /* last section, last segment                                                        */
260 #define  F_AF_NA_1  124    /* ack file, ack section                                                             */
261 #define  F_SG_NA_1  125    /* segment                                                                           */
262 #define  F_DR_TA_1  126    /* directory                                                                         */
263 #define  F_SC_NB_1  127    /* Query Log - Request archive file                                                  */
264 static const value_string asdu_types [] = {
265         {  M_SP_NA_1,           "M_SP_NA_1" },
266         {  M_DP_NA_1,           "M_DP_NA_1" },
267         {  M_ST_NA_1,           "M_ST_NA_1" },
268         {  M_BO_NA_1,           "M_BO_NA_1" },
269         {  M_ME_NA_1,           "M_ME_NA_1" },
270         {  M_ME_NB_1,           "M_ME_NB_1" },
271         {  M_ME_NC_1,           "M_ME_NC_1" },
272         {  M_IT_NA_1,           "M_IT_NA_1" },
273         {  M_PS_NA_1,           "M_PS_NA_1" },
274         {  M_ME_ND_1,           "M_ME_ND_1" },
275         {  M_SP_TB_1,           "M_SP_TB_1" },
276         {  M_DP_TB_1,           "M_DP_TB_1" },
277         {  M_ST_TB_1,           "M_ST_TB_1" },
278         {  M_BO_TB_1,           "M_BO_TB_1" },
279         {  M_ME_TD_1,           "M_ME_TD_1" },
280         {  M_ME_TE_1,           "M_ME_TE_1" },
281         {  M_ME_TF_1,           "M_ME_TF_1" },
282         {  M_IT_TB_1,           "M_IT_TB_1" },
283         {  M_EP_TD_1,           "M_EP_TD_1" },
284         {  M_EP_TE_1,           "M_EP_TE_1" },
285         {  M_EP_TF_1,           "M_EP_TF_1" },
286         {  C_SC_NA_1,           "C_SC_NA_1" },
287         {  C_DC_NA_1,           "C_DC_NA_1" },
288         {  C_RC_NA_1,           "C_RC_NA_1" },
289         {  C_SE_NA_1,           "C_SE_NA_1" },
290         {  C_SE_NB_1,           "C_SE_NB_1" },
291         {  C_SE_NC_1,           "C_SE_NC_1" },
292         {  C_BO_NA_1,           "C_BO_NA_1" },
293         {  C_SC_TA_1,           "C_SC_TA_1" },
294         {  C_DC_TA_1,           "C_DC_TA_1" },
295         {  C_RC_TA_1,           "C_RC_TA_1" },
296         {  C_SE_TA_1,           "C_SE_TA_1" },
297         {  C_SE_TB_1,           "C_SE_TB_1" },
298         {  C_SE_TC_1,           "C_SE_TC_1" },
299         {  C_BO_TA_1,           "C_BO_TA_1" },
300         {  M_EI_NA_1,           "M_EI_NA_1" },
301         {  C_IC_NA_1,           "C_IC_NA_1" },
302         {  C_CI_NA_1,           "C_CI_NA_1" },
303         {  C_RD_NA_1,           "C_RD_NA_1" },
304         {  C_CS_NA_1,           "C_CS_NA_1" },
305         {  C_RP_NA_1,           "C_RP_NA_1" },
306         {  C_TS_TA_1,           "C_TS_TA_1" },
307         {  P_ME_NA_1,           "P_ME_NA_1" },
308         {  P_ME_NB_1,           "P_ME_NB_1" },
309         {  P_ME_NC_1,           "P_ME_NC_1" },
310         {  P_AC_NA_1,           "P_AC_NA_1" },
311         {  F_FR_NA_1,           "F_FR_NA_1" },
312         {  F_SR_NA_1,           "F_SR_NA_1" },
313         {  F_SC_NA_1,           "F_SC_NA_1" },
314         {  F_LS_NA_1,           "F_LS_NA_1" },
315         {  F_AF_NA_1,           "F_AF_NA_1" },
316         {  F_SG_NA_1,           "F_SG_NA_1" },
317         {  F_DR_TA_1,           "F_DR_TA_1" },
318         {  F_SC_NB_1,           "F_SC_NB_1" },
319         { 0, NULL }
320 };
321 static const value_string asdu_lngtypes [] = {
322         {  M_SP_NA_1,           "single-point information" },
323         {  M_DP_NA_1,           "double-point information" },
324         {  M_ST_NA_1,           "step position information" },
325         {  M_BO_NA_1,           "bitstring of 32 bits" },
326         {  M_ME_NA_1,           "measured value, normalized value" },
327         {  M_ME_NB_1,           "measured value, scaled value" },
328         {  M_ME_NC_1,           "measured value, short floating point number" },
329         {  M_IT_NA_1,           "integrated totals" },
330         {  M_PS_NA_1,           "packed single-point information with status change detection" },
331         {  M_ME_ND_1,           "measured value, normalized value without quality descriptor" },
332         {  M_SP_TB_1,           "single-point information with time tag CP56Time2a" },
333         {  M_DP_TB_1,           "double-point information with time tag CP56Time2a" },
334         {  M_ST_TB_1,           "step position information with time tag CP56Time2a" },
335         {  M_BO_TB_1,           "bitstring of 32 bit with time tag CP56Time2a" },
336         {  M_ME_TD_1,           "measured value, normalized value with time tag CP56Time2a" },
337         {  M_ME_TE_1,           "measured value, scaled value with time tag CP56Time2a" },
338         {  M_ME_TF_1,           "measured value, short floating point number with time tag CP56Time2a" },
339         {  M_IT_TB_1,           "integrated totals with time tag CP56Time2a" },
340         {  M_EP_TD_1,           "event of protection equipment with time tag CP56Time2a" },
341         {  M_EP_TE_1,           "packed start events of protection equipment with time tag CP56Time2a" },
342         {  M_EP_TF_1,           "packed output circuit information of protection equipment with time tag CP56Time2a" },
343         {  C_SC_NA_1,           "single command" },
344         {  C_DC_NA_1,           "double command" },
345         {  C_RC_NA_1,           "regulating step command" },
346         {  C_SE_NA_1,           "set point command, normalized value" },
347         {  C_SE_NB_1,           "set point command, scaled value" },
348         {  C_SE_NC_1,           "set point command, short floating point number" },
349         {  C_BO_NA_1,           "bitstring of 32 bits" },
350         {  C_SC_TA_1,           "single command with time tag CP56Time2a" },
351         {  C_DC_TA_1,           "double command with time tag CP56Time2a" },
352         {  C_RC_TA_1,           "regulating step command with time tag CP56Time2a" },
353         {  C_SE_TA_1,           "set point command, normalized value with time tag CP56Time2a" },
354         {  C_SE_TB_1,           "set point command, scaled value with time tag CP56Time2a" },
355         {  C_SE_TC_1,           "set point command, short floating-point number with time tag CP56Time2a" },
356         {  C_BO_TA_1,           "bitstring of 32 bits with time tag CP56Time2a" },
357         {  M_EI_NA_1,           "end of initialization" },
358         {  C_IC_NA_1,           "interrogation command" },
359         {  C_CI_NA_1,           "counter interrogation command" },
360         {  C_RD_NA_1,           "read command" },
361         {  C_CS_NA_1,           "clock synchronization command" },
362         {  C_RP_NA_1,           "reset process command" },
363         {  C_TS_TA_1,           "test command with time tag CP56Time2a" },
364         {  P_ME_NA_1,           "parameter of measured value, normalized value" },
365         {  P_ME_NB_1,           "parameter of measured value, scaled value" },
366         {  P_ME_NC_1,           "parameter of measured value, short floating-point number" },
367         {  P_AC_NA_1,           "parameter activation" },
368         {  F_FR_NA_1,           "file ready" },
369         {  F_SR_NA_1,           "section ready" },
370         {  F_SC_NA_1,           "call directory, select file, call file, call section" },
371         {  F_LS_NA_1,           "last section, last segment" },
372         {  F_AF_NA_1,           "ack file, ack section" },
373         {  F_SG_NA_1,           "segment" },
374         {  F_DR_TA_1,           "directory" },
375         {  F_SC_NB_1,           "Query Log - Request archive file" },
376         { 0, NULL }
377 };
378
379
380 /* Cause of Transmision (CauseTx) */
381 #define Per_Cyc         1
382 #define Back            2
383 #define Spont           3
384 #define Init            4
385 #define Req             5
386 #define Act             6
387 #define ActCon          7
388 #define Deact           8
389 #define DeactCon        9
390 #define ActTerm         10
391 #define Retrem          11
392 #define Retloc          12
393 #define File            13
394 #define Inrogen         20
395 #define Inro1           21
396 #define Inro2           22
397 #define Inro3           23
398 #define Inro4           24
399 #define Inro5           25
400 #define Inro6           26
401 #define Inro7           27
402 #define Inro8           28
403 #define Inro9           29
404 #define Inro10          30
405 #define Inro11          31
406 #define Inro12          32
407 #define Inro13          33
408 #define Inro14          34
409 #define Inro15          35
410 #define Inro16          36
411 #define Reqcogen        37
412 #define Reqco1          38
413 #define Reqco2          39
414 #define Reqco3          40
415 #define Reqco4          41
416 #define UkTypeId        44
417 #define UkCauseTx       45
418 #define UkComAdrASDU    46
419 #define UkIOA           47
420 static const value_string causetx_types [] = {
421         { Per_Cyc         ,"Per/Cyc" },
422         { Back            ,"Back" },
423         { Spont           ,"Spont" },
424         { Init            ,"Init" },
425         { Req             ,"Req" },
426         { Act             ,"Act" },
427         { ActCon          ,"ActCon" },
428         { Deact           ,"Deact" },
429         { DeactCon        ,"DeactCon" },
430         { ActTerm         ,"ActTerm" },
431         { Retrem          ,"Retrem" },
432         { Retloc          ,"Retloc" },
433         { File            ,"File" },
434         { Inrogen         ,"Inrogen" },
435         { Inro1           ,"Inro1" },
436         { Inro2           ,"Inro2" },
437         { Inro3           ,"Inro3" },
438         { Inro4           ,"Inro4" },
439         { Inro5           ,"Inro5" },
440         { Inro6           ,"Inro6" },
441         { Inro7           ,"Inro7" },
442         { Inro8           ,"Inro8" },
443         { Inro9           ,"Inro9" },
444         { Inro10          ,"Inro10" },
445         { Inro11          ,"Inro11" },
446         { Inro12          ,"Inro12" },
447         { Inro13          ,"Inro13" },
448         { Inro14          ,"Inro14" },
449         { Inro15          ,"Inro15" },
450         { Inro16          ,"Inro16" },
451         { Reqcogen        ,"Reqcogen" },
452         { Reqco1          ,"Reqco1" },
453         { Reqco2          ,"Reqco2" },
454         { Reqco3          ,"Reqco3" },
455         { Reqco4          ,"Reqco4" },
456         { UkTypeId        ,"UkTypeId" },
457         { UkCauseTx       ,"UkCauseTx" },
458         { UkComAdrASDU    ,"UkComAdrASDU" },
459         { UkIOA           ,"UkIOA" },
460         { 0, NULL }
461 };
462
463
464 /* Protocol fields to be filtered */
465 static int hf_apdulen = -1;
466 static int hf_apcitype = -1;
467 static int hf_apciutype    = -1;
468
469 static int hf_addr  = -1;
470 static int hf_oa  = -1;
471 static int hf_typeid   = -1;
472 static int hf_causetx  = -1;
473 static int hf_nega  = -1;
474 static int hf_test  = -1;
475 static int hf_ioa  = -1;
476 static int hf_numix  = -1;
477 static int hf_sq  = -1;
478
479 static gint hf_iec104_asdufloat = -1;
480 static gint hf_iec104_asdunormval = -1;
481
482 static gint ett_apci = -1;
483 static gint ett_asdu = -1;
484
485 /* Misc. functions for dissection of signal values */
486
487 /* ==================================================================== 
488     void get_CP56Time( td_CP56Time *cp56t, tvbuff_t *tvb, guint8 offset)
489
490     Dissects the CP56Time2a time (Seven octet binary time)
491     that starts 'offset' bytes in 'tvb'.
492     The time and date is put in struct 'cp56t'
493    ==================================================================== */  
494 void get_CP56Time( td_CP56Time *cp56t, tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree )
495 {
496   guint16 ms;
497   ms = tvb_get_letohs( tvb , *offset );
498   (*offset) += 2;
499   cp56t->cp56t_s = (int)floor(ms/1000);
500   cp56t->cp56t_ms = (int)(ms-(cp56t->cp56t_s*1000));
501    
502   cp56t->cp56t_min = tvb_get_guint8(tvb, *offset);
503   /* "Invalid" -- Todo: test */
504   cp56t->IV = cp56t->cp56t_min & 0x80;
505
506   cp56t->cp56t_min = cp56t->cp56t_min & 0x3F;
507   (*offset)++;
508   cp56t->cp56t_h = 0x1F & tvb_get_guint8(tvb, *offset);
509   (*offset)++;
510   cp56t->cp56t_dom = tvb_get_guint8(tvb, *offset);
511   cp56t->cp56t_dow = 0xE0 & cp56t->cp56t_dom;
512   cp56t->cp56t_dow >>= 5;
513   cp56t->cp56t_dom = cp56t->cp56t_dom & 0x1F;
514   (*offset)++;
515   cp56t->cp56t_month = 0x0F & tvb_get_guint8(tvb, *offset);
516   (*offset)++;
517   cp56t->cp56t_year = 0x7F & tvb_get_guint8(tvb, *offset);
518   (*offset)++;
519
520
521   if( iec104_header_tree != NULL )
522   {
523     /* ---- format yy-mm-dd (dow) hh:mm:ss.ms  */
524     proto_tree_add_text(iec104_header_tree, tvb, (*offset)-7, 7, 
525           "%.2d-%.2d-%.2d (%d) %.2d:%.2d:%.2d.%.3d (%s)",
526                   cp56t->cp56t_year,cp56t->cp56t_month,cp56t->cp56t_dom,
527                   cp56t->cp56t_dow,cp56t->cp56t_h,cp56t->cp56t_min,
528                   cp56t->cp56t_s,cp56t->cp56t_ms,cp56t->IV?"Invalid":"Valid");
529   }
530
531
532 }
533
534
535 /* ==================================================================== 
536     Information object address (Identifier)
537     ASDU -> Inform Object #1 -> Information object address
538    ==================================================================== */  
539 void get_InfoObjectAddress( guint32 *asdu_info_obj_addr, tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree )
540 {
541   /* --------  Information object address */
542   *asdu_info_obj_addr = tvb_get_letoh24(tvb, *offset);
543   if( iec104_header_tree != NULL )
544   {
545     proto_tree_add_uint(iec104_header_tree, hf_ioa, 
546                 tvb, *offset, 3, *asdu_info_obj_addr);
547                 
548   }
549   (*offset) += 3;          
550 }
551
552
553
554
555 /* ==================================================================== 
556     SIQ: Single-point information (IEV 371-02-07) w quality descriptor
557    ==================================================================== */  
558 void get_SIQ( td_ValueInfo *value, tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree )
559 {
560   guint8 siq;
561   siq = tvb_get_guint8(tvb, *offset);
562   
563   value->ON = siq & 0x01;
564   value->OFF = !(value->ON);
565   value->BL = siq & 0x10;  /* Blocked (1)                                       */
566   value->SB = siq & 0x20;  /* Substituted (1)                                   */
567   value->NT = siq & 0x40;  /* Topical (0) / Not topical (1)                     */
568                            /* [Topical <=> if most recent update was succesful] */
569   value->IV = siq & 0x80;  /* Invalid (1)                                       */
570   if( iec104_header_tree != NULL )
571   {
572     proto_tree_add_text( iec104_header_tree, tvb, *offset, 1, "Value: %s - Status: %s, %s, %s, %s",
573                 value->ON?"ON":"OFF", value->BL?"Blocked":"Not blocked", 
574                 value->SB?"Substituted":"Not Substituted", value->NT?"Not Topical":"Topical",
575                 value->IV?"Invalid":"Valid" );
576   }
577
578   (*offset)++;
579
580 }
581
582
583 /* ==================================================================== 
584     DIQ: Double-point information (IEV 371-02-08) w quality descriptor
585    ==================================================================== */  
586 void get_DIQ( td_ValueInfo *value, tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree )
587 {
588
589   guint8 diq;
590   diq = tvb_get_guint8(tvb, *offset);
591   value->IPOS0 = FALSE;
592   value->OFF = FALSE;
593   value->ON = FALSE;
594   value->IPOS3 = FALSE;
595   switch ( diq & 0x03 )
596   {
597   case 0:
598     value->IPOS0 = TRUE;
599     break;
600   case 1:
601     value->OFF = TRUE;
602     break;
603   case 2:
604     value->ON = TRUE;
605     break;
606   case 3:
607     value->IPOS3 = TRUE;
608     break;
609   default:
610     break;
611   }
612   value->BL = diq & 0x10;  /* Blocked (1)                                       */
613   value->SB = diq & 0x20;  /* Substituted (1)                                   */
614   value->NT = diq & 0x40;  /* Topical (0) / Not topical (1)                     */
615                            /* [Topical <=> if most recent update was succesful] */
616   value->IV = diq & 0x80;  /* Invalid (1)                                       */
617   
618   if( iec104_header_tree != NULL )
619   {
620     proto_tree_add_text( iec104_header_tree, tvb, *offset, 1, "Value: %s%s%s%s - Status: %s, %s, %s, %s",
621                 value->ON?"ON":"", value->OFF?"OFF":"", value->IPOS0?"IPOS0":"", value->IPOS3?"IPOS3":"", 
622                 value->BL?"Blocked":"Not blocked", value->SB?"Substituted":"Not Substituted", 
623                 value->NT?"Not Topical":"Topical", value->IV?"Invalid":"Valid" );
624   }
625
626   (*offset)++;
627
628 }
629
630 /* ==================================================================== 
631     QDS: Quality descriptor (separate octet) 
632    ==================================================================== */  
633 void get_QDS( td_ValueInfo *value, tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree )
634 {
635   guint8 qds;
636   /* --------  QDS quality description */
637   qds = tvb_get_guint8(tvb, *offset);
638
639   value->OV = qds & 0x01;  /* Overflow (1)                                      */
640   value->BL = qds & 0x10;  /* Blocked (1)                                       */
641   value->SB = qds & 0x20;  /* Substituted (1)                                   */
642   value->NT = qds & 0x40;  /* Topical (0) / Not topical (1)                     */
643                            /* [Topical <=> if most recent update was succesful] */
644   value->IV = qds & 0x80;  /* Invalid (1)                                       */
645   if( iec104_header_tree != NULL )
646   {
647     proto_tree_add_text( iec104_header_tree, tvb, *offset, 1, "Status: %s, %s, %s, %s, %s",
648                 value->OV?"Overflow!":"No Overflow", value->BL?"Blocked!":"Not Blocked", 
649                 value->SB?"Substituted!":"Not Substituted", value->NT?"Not Topical!":"Topical",
650                 value->IV?"Invalid!":"Valid" );
651   }
652
653   (*offset)++;
654
655 }
656
657 /* ==================================================================== 
658     QDP: Quality descriptor for events of protection equipment
659         (separate octet)
660    ==================================================================== */  
661 void get_QDP( td_ValueInfo *value _U_, tvbuff_t *tvb _U_, guint8 *offset _U_, proto_tree *iec104_header_tree _U_ )
662 {
663         /* todo */
664
665 }
666
667 /* ==================================================================== 
668     VTI: Value with transient state indication
669    ==================================================================== */  
670 void get_VTI( td_ValueInfo *value _U_, tvbuff_t *tvb _U_, guint8 *offset _U_, proto_tree *iec104_header_tree _U_ )
671 {
672         /* todo */
673
674 }
675
676 /* ==================================================================== 
677     NVA: Normalized value
678    ==================================================================== */  
679 void get_NVA( td_ValueInfo *value, tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree )
680 {
681   /* Normalized value F16[1..16]<-1..+1-2^-15> */
682         value->MV.NVA = tvb_get_letohs(tvb, *offset);   
683
684         if ( iec104_header_tree != NULL )
685         {
686                 proto_tree_add_int(iec104_header_tree, hf_iec104_asdunormval,
687                                                                 tvb, *offset, 2, value->MV.NVA);
688                         /* todo ... presentation as float +/- 1 (val/32767) ... */
689         }
690         (*offset) += 2;
691
692 }
693
694 void get_NVAspt( td_SpInfo *spt, tvbuff_t *tvb, guint8 *offset, 
695             proto_tree *iec104_header_tree )
696 {
697   /* Normalized value F16[1..16]<-1..+1-2^-15> */
698         spt->SP.NVA = tvb_get_letohs(tvb, *offset);     
699
700         if ( iec104_header_tree != NULL )
701         {
702                 proto_tree_add_int(iec104_header_tree, hf_iec104_asdunormval,
703                                                                 tvb, *offset, 2, spt->SP.NVA);
704                         /* todo ... presentation as float +/- 1 */
705         }
706         (*offset) += 2;
707
708 }
709
710 /* ==================================================================== 
711     SVA: Scaled value
712    ==================================================================== */  
713 void get_SVA( td_ValueInfo *value, tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree )
714 {
715   /* Scaled value I16[1..16]<-2^15..+2^15-1> */
716         value->MV.SVA = tvb_get_letohs(tvb, *offset);   
717         if ( iec104_header_tree != NULL )
718         {
719                 proto_tree_add_int(iec104_header_tree, hf_iec104_asdunormval,
720                                                                 tvb, *offset, 2, value->MV.SVA);
721         }
722         (*offset) += 2;
723
724 }
725
726 void get_SVAspt( td_SpInfo *spt, tvbuff_t *tvb, guint8 *offset, 
727             proto_tree *iec104_header_tree )
728 {
729   /* Scaled value I16[1..16]<-2^15..+2^15-1> */
730         spt->SP.SVA = tvb_get_letohs(tvb, *offset);     
731         if ( iec104_header_tree != NULL )
732         {
733                 proto_tree_add_int(iec104_header_tree, hf_iec104_asdunormval,
734                                                                 tvb, *offset, 2, spt->SP.SVA);
735         }
736         (*offset) += 2;
737
738 }
739
740 /* ==================================================================== 
741     "FLT": Short floating point number
742    ==================================================================== */  
743 void get_FLT( td_ValueInfo *value, tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree )
744 {
745         /* --------  IEEE 754 float value */
746         value->MV.FLT = tvb_get_letohieee_float(tvb, *offset);
747
748         if ( iec104_header_tree != NULL )
749         {
750                 proto_tree_add_float(iec104_header_tree, hf_iec104_asdufloat,
751                                 tvb, *offset, 4, value->MV.FLT);
752         }
753         (*offset) += 4;
754
755
756 }
757
758 void get_FLTspt( td_SpInfo *spt, tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree )
759 {
760         /* --------  IEEE 754 float value */
761         spt->SP.FLT = tvb_get_letohieee_float(tvb, *offset);
762
763         if ( iec104_header_tree != NULL )
764         {
765                 proto_tree_add_float(iec104_header_tree, hf_iec104_asdufloat,
766                                 tvb, *offset, 4, spt->SP.FLT);
767         }
768         (*offset) += 4;
769
770
771 }
772
773 /* ==================================================================== 
774     todo  -- BCR: Binary counter reading
775    ==================================================================== */  
776 /* void get_BCR( td_ValueInfo *value, tvbuff_t *tvb, guint8 *offset, 
777            proto_tree *iec104_header_tree );  */
778
779 /* ==================================================================== 
780     todo -- SEP: Single event of protection equipment
781    ==================================================================== */  
782 void get_SEP( td_ValueInfo *value _U_, tvbuff_t *tvb _U_, guint8 *offset _U_, proto_tree *iec104_header_tree _U_ )
783 {
784         /* todo */
785
786 }
787
788 /* ==================================================================== 
789     QOC: Qualifier Of Command
790    ==================================================================== */  
791 void get_QOC( td_CmdInfo *value, guint8 data )
792 {
793         value->ZeroP   = FALSE;  /* No pulse                        */
794         value->ShortP  = FALSE;  /* Short Pulse                     */
795         value->LongP   = FALSE;  /* Long Pulse                      */
796         value->Persist = FALSE;  /* Persistent output               */
797
798         value->QU = data & 0x7c;
799         value->QU >>= 2;
800
801         switch( value->QU )
802         {
803                 case 0x00:
804                         value->ZeroP = TRUE;
805                         break; /* No additional definition */
806                 case 0x01:
807                         value->ShortP = TRUE;  
808                         break;
809                 case 0x02:
810                         value->LongP = TRUE;
811                         break;
812                 case 0x03:
813                         value->Persist = TRUE;
814                         break;
815                 default:
816                 /* case 4..31 --> reserved .. */
817                         ;
818                 break;
819         }
820         value->SE = data & 0x80;  /* Select (1) / Execute (0) */
821
822 }
823
824
825 /* ==================================================================== 
826     QOS: Qualifier Of Set-point command
827    ==================================================================== */  
828 void get_QOS( td_SpInfo *spt, tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree )
829 {
830         guint8 qos;
831   /* --------  QOS quality description */
832   qos = tvb_get_guint8(tvb, *offset);
833
834         spt->QL = qos & 0x7F;  /* UI7[1..7]<0..127>        */
835         spt->SE = qos & 0x80;  /* Select (1) / Execute (0) */
836
837   if( iec104_header_tree != NULL )
838   {
839           proto_tree_add_text( iec104_header_tree, tvb, *offset, 1, "Qualifier - QL: %d, S/E: %s",
840                 spt->QL, spt->SE?"Select":"Execute" );
841   }
842
843   (*offset)++;
844
845 }
846
847
848 /* ==================================================================== 
849     SCO: Single Command (IEV 371-03-02)
850    ==================================================================== */  
851 void get_SCO( td_CmdInfo *value, tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree )
852 {
853   guint8 data;
854   /* On/Off */
855   data = tvb_get_guint8(tvb, *offset);
856   value->ON  = data & 0x01;
857   value->OFF = !(value->ON);
858   
859   /* QOC */
860   get_QOC( value, data );
861
862
863   if( iec104_header_tree != NULL )
864   {
865           if ( value->QU < 4 )
866           {
867                   proto_tree_add_text( iec104_header_tree, tvb, *offset, 1, "Command: %s%s, Qualifier: %s%s%s%s, %s",
868                         value->ON?"ON":"", value->OFF?"OFF":"", 
869                         value->ZeroP?"No pulse defined":"", value->ShortP?"Short Pulse":"", 
870                         value->LongP?"Long Pulse":"", value->Persist?"Persistent Output":"",
871                         value->SE?"Select":"Execute");
872           } else {
873                   proto_tree_add_text( iec104_header_tree, tvb, *offset, 1, "Command: %s%s, Qualifier: QU=%d, %s",
874                         value->ON?"ON":"", value->OFF?"OFF":"", 
875                         value->QU,
876                         value->SE?"Select":"Execute");
877           }               
878   }
879
880   (*offset)++;
881         
882 }
883
884 /* ==================================================================== 
885     DCO: Double Command (IEV 371-03-03)
886    ==================================================================== */  
887 void get_DCO( td_CmdInfo *value, tvbuff_t *tvb, guint8 *offset, proto_tree *iec104_header_tree )
888 {
889   guint8 data;
890   /* On/Off */
891   data = tvb_get_guint8(tvb, *offset);
892   value->OFF = FALSE;
893   value->ON = FALSE;
894   switch ( data & 0x03 )
895   {
896   case 1:
897     value->OFF = TRUE;
898     break;
899   case 2:
900     value->ON = TRUE;
901     break;
902   default:
903     ;
904     break;
905   }
906   
907   /* QOC */
908   get_QOC( value, data );
909
910
911   if( iec104_header_tree != NULL )
912   {
913           if ( value->QU < 4 )
914           {
915                   proto_tree_add_text( iec104_header_tree, tvb, *offset, 1, "Command: %s%s%s, Qualifier: %s%s%s%s, %s",
916                         value->ON?"ON":"", value->OFF?"OFF":"", (value->ON | value->OFF)?"":"Error: On/Off not defined",
917                         value->ZeroP?"No pulse defined":"", value->ShortP?"Short Pulse":"", 
918                         value->LongP?"Long Pulse":"", value->Persist?"Persistent Output":"",
919                         value->SE?"Select":"Execute");
920           } else {
921                   proto_tree_add_text( iec104_header_tree, tvb, *offset, 1, "Command: %s%s%s, Qualifier: QU=%d, %s",
922                         value->ON?"ON":"", value->OFF?"OFF":"", (value->ON | value->OFF)?"":"Error: On/Off not defined",
923                         value->QU,
924                         value->SE?"Select":"Execute");
925           }               
926   }
927
928   (*offset)++;
929
930 }
931 /* .... end Misc. functions for dissection of signal values */
932
933
934 /* Find the APDU 104 (APDU=APCI+ASDU) length.
935 Includes possible tvb_length-1 bytes that don't form an APDU */
936 static guint get_iec104apdu_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset)
937 {
938         guint8 Val;
939         guint32 Off;
940
941         for (Off= 0; Off <= tvb_length(tvb)- 2; Off++)  {
942                 Val = tvb_get_guint8(tvb, offset+ Off);
943                 if (Val == APCI_START)  {
944                         return (guint)(Off+ tvb_get_guint8(tvb, offset+ Off+ 1)+ 2);
945                 }
946         }
947
948         return (guint)(tvb_length(tvb));
949 }
950
951
952 /* Is is called twice: For 'Packet List' and for 'Packet Details' */
953 static void dissect_iec104asdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
954 {
955         guint Len = tvb_reported_length(tvb);
956         guint8 Bytex = 0;
957         const char *cause_str;
958         size_t Ind = 0;
959         struct asduheader * asduh;
960         emem_strbuf_t * res;
961         proto_item * it104 = NULL;
962         proto_tree * trHead;
963         
964         
965         guint8 offset = 0;  /* byte offset, signal dissection */
966         guint8 offset_start_ioa = 0; /* position first ioa */
967         guint8 i;
968         guint32 asdu_info_obj_addr = 0;
969         proto_item * itSignal = NULL;
970         proto_tree * trSignal;
971         td_ValueInfo value;  /* signal value struct */
972         td_CmdInfo cmd;      /* command value struct */
973         td_SpInfo  spt;      /* setpoint value struct */
974         td_CP56Time cp56t;   /* time value struct */
975         
976         if (!(check_col(pinfo->cinfo, COL_INFO) || tree))   return; /* Be sure that the function is only called twice */
977
978         col_set_str(pinfo->cinfo, COL_PROTOCOL, "104asdu");
979         col_clear(pinfo->cinfo, COL_INFO);
980
981         asduh = ep_alloc(sizeof(struct asduheader));
982         res = ep_strbuf_new_label(NULL);
983
984         /*** *** START: Common to 'Packet List' and 'Packet Details' *** ***/
985         if (Len >= ASDU_HEAD_LEN)  {
986                 /* Get fields */
987                 asduh->AddrLow = tvb_get_guint8(tvb, 4);
988                 asduh->AddrHigh = tvb_get_guint8(tvb, 5);
989                 asduh->OA = tvb_get_guint8(tvb, 3);
990                 asduh->TypeId = tvb_get_guint8(tvb, 0);
991                 asduh->TNCause = tvb_get_guint8(tvb, 2);
992                 asduh->IOA = tvb_get_letoh24(tvb, 6);
993                 Bytex = tvb_get_guint8(tvb, 1);
994                 asduh->NumIx = Bytex & 0x7F;
995                 asduh->SQ = Bytex & F_SQ;
996                 /* Build common string for 'Packet List' and 'Packet Details' */
997                 ep_strbuf_printf(res, "%u,%u%s%u ", asduh->AddrLow, asduh->AddrHigh,  pinfo->srcport == iec104port ? "->" : "<-", asduh->OA);
998                 ep_strbuf_append(res, val_to_str(asduh->TypeId, asdu_types, "<TypeId=%u>"));
999                 ep_strbuf_append_c(res, ' ');
1000                 cause_str = val_to_str(asduh->TNCause & F_CAUSE, causetx_types, " <CauseTx=%u>");
1001                 ep_strbuf_append(res, cause_str);
1002                 if (asduh->TNCause & F_NEGA)   ep_strbuf_append(res, "_NEGA");
1003                 if (asduh->TNCause & F_TEST)   ep_strbuf_append(res, "_TEST");
1004                 if (asduh->TNCause & (F_TEST | F_NEGA))  {
1005                         for (Ind=strlen(cause_str); Ind< 7; Ind++)   ep_strbuf_append_c(res, ' ');
1006                 }
1007                 ep_strbuf_append_printf(res, " IOA=%d", asduh->IOA);
1008                 if (asduh->NumIx > 1)   {
1009                         if (asduh->SQ == F_SQ)   ep_strbuf_append_printf(res, "-%d", asduh->IOA + asduh->NumIx - 1);
1010                         else      ep_strbuf_append(res, ",...");
1011                         ep_strbuf_append_printf(res, " (%u)", asduh->NumIx);
1012                 }
1013         }
1014         else   {
1015                 ep_strbuf_printf(res, "<ERR Short Asdu, Len=%u>", Len);
1016         }
1017         ep_strbuf_append_c(res, ' '); /* We add a space to separate possible APCIs/ASDUs in the same packet */
1018         /*** *** END: Common to 'Packet List' and 'Packet Details' *** ***/
1019
1020         /*** *** DISSECT 'Packet List' *** ***/
1021         if (check_col(pinfo->cinfo, COL_INFO))  {
1022                 col_add_str(pinfo->cinfo, COL_INFO, res->str);
1023                 col_set_fence(pinfo->cinfo, COL_INFO);
1024         }
1025
1026         if(!tree)   return;
1027
1028         /*** *** DISSECT 'Packet Details' *** ***/
1029
1030         it104 = proto_tree_add_item(tree, proto_iec104asdu, tvb, 0, -1, FALSE);
1031
1032         /* 'Packet Details': ROOT ITEM */
1033         proto_item_append_text(it104, ": %s'%s'", res->str, Len >= ASDU_HEAD_LEN ? val_to_str(asduh->TypeId, asdu_lngtypes, "<Unknown TypeId>") : "");
1034
1035         /* 'Packet Details': TREE */
1036         if (Len < ASDU_HEAD_LEN)   return;
1037         trHead = proto_item_add_subtree(it104, ett_asdu);
1038
1039         /* Remember:    add_uint, add_boolean, _add_text: value from last parameter.
1040                         add_item: value from tvb. */
1041         proto_tree_add_uint(trHead, hf_typeid, tvb, 0, 1, asduh->TypeId);
1042         proto_tree_add_uint(trHead, hf_numix, tvb, 1, 1, asduh->NumIx);
1043         proto_tree_add_uint(trHead, hf_causetx, tvb, 2, 1, asduh->TNCause & F_CAUSE);
1044         proto_tree_add_boolean(trHead, hf_nega, tvb, 2, 1, asduh->TNCause);
1045         proto_tree_add_boolean(trHead, hf_test, tvb, 2, 1, asduh->TNCause);
1046         proto_tree_add_uint(trHead, hf_oa, tvb, 3, 1, asduh->OA);
1047         proto_tree_add_uint(trHead, hf_addr, tvb, 4, 2, asduh->AddrLow+ 256* asduh->AddrHigh);
1048         proto_tree_add_uint(trHead, hf_ioa, tvb, 6, 3, asduh->IOA);
1049         if (asduh->NumIx > 1)   proto_tree_add_boolean(trHead, hf_sq, tvb, 1, 1, asduh->SQ);
1050         
1051         /* 'Signal Details': TREE */
1052         offset = 6;  /* offset position after DUI, already stored in asduh struct */
1053         /* -------- get signal value and status based on ASDU type id */
1054         
1055         switch (asduh->TypeId) {
1056                 case M_SP_NA_1:
1057                 case M_DP_NA_1:
1058                 case M_SP_TB_1: 
1059                 case M_DP_TB_1: 
1060                 case M_ME_NA_1: 
1061                 case M_ME_NB_1: 
1062                 case M_ME_NC_1:
1063                 case M_ME_ND_1: 
1064                 case M_ME_TD_1: 
1065                 case M_ME_TE_1: 
1066                 case M_ME_TF_1: 
1067                 case C_SC_NA_1:
1068                 case C_DC_NA_1:
1069                 case C_SE_NA_1: 
1070                 case C_SE_NB_1: 
1071                 case C_SE_NC_1: 
1072                 case C_SC_TA_1: 
1073                 case C_DC_TA_1: 
1074                 case C_SE_TA_1: 
1075                 case C_SE_TB_1: 
1076                 case C_SE_TC_1: 
1077                 case C_CS_NA_1: 
1078                         
1079                         /* create subtree for the signal values ... */
1080                         itSignal = proto_tree_add_item( trHead, proto_iec104asdu, tvb, offset, -1, FALSE );
1081                         proto_item_append_text(itSignal, ": Value");
1082                         
1083                         trSignal = proto_item_add_subtree( itSignal, ett_asdu );
1084                         
1085                         /* -- object values */
1086                         for(i = 0; i < asduh->NumIx; i++)
1087                         {
1088                                 /* --------  First Information object address */
1089                                 if (!i)
1090                                 {
1091                                         offset_start_ioa = offset;
1092                                         /* --------  Information object address */
1093                                         asdu_info_obj_addr = asduh->IOA;
1094                                         proto_tree_add_uint(trSignal, hf_ioa, 
1095                                                         tvb, offset_start_ioa, 3, asdu_info_obj_addr);  
1096                                         /* check length */
1097                                         if( Len < (guint)(offset+3) ) {
1098                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1099                                                 return;
1100                                         }
1101                                         offset += 3;  /* step over IOA bytes */
1102                                 } else {
1103                                         /* -------- following Information object address depending on SQ */
1104                                         if (asduh->SQ) /* <=> SQ=1, info obj addr = startaddr++ */
1105                                         {
1106                                                 asdu_info_obj_addr++;
1107                                                 proto_tree_add_uint(trSignal, hf_ioa, 
1108                                                                 tvb, offset_start_ioa, 3, asdu_info_obj_addr);  
1109                                                                         
1110
1111                                         } else { /* SQ=0, info obj addr given */
1112                                                 /* --------  Information object address */
1113                                                 /* check length */
1114                                                 if( Len < (guint)(offset+3) ) {
1115                                                         proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1116                                                         return;
1117                                                 }
1118                                                 get_InfoObjectAddress( &asdu_info_obj_addr, tvb, &offset, 
1119                                                         trSignal);
1120                                                 
1121                                         }
1122                                 }
1123
1124                                 switch (asduh->TypeId) {
1125                                 case M_SP_NA_1: /* 1    Single-point information */
1126                                         /* check length */
1127                                         if( Len < (guint)(offset+1) ) {
1128                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1129                                                 return;
1130                                         }
1131                                         get_SIQ( &value, tvb, &offset, trSignal );
1132                                         break;
1133                                 case M_DP_NA_1: /* 3    Double-point information */
1134                                         /* check length */
1135                                         if( Len < (guint)(offset+1) ) {
1136                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1137                                                 return;
1138                                         }
1139                                         get_DIQ( &value, tvb, &offset, trSignal );
1140                                         break;
1141                                 case M_ME_NA_1: /* 9    Measured value, normalized value */
1142                                         /* check length */
1143                                         if( Len < (guint)(offset+3) ) {
1144                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1145                                                 return;
1146                                         }
1147                                         get_NVA( &value, tvb, &offset, trSignal );
1148                                         get_QDS( &value, tvb, &offset, trSignal );
1149                                         break;
1150                                 case M_ME_NB_1: /* 11     Measured value, scaled value */
1151                                         /* check length */
1152                                         if( Len < (guint)(offset+3) ) {
1153                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1154                                                 return;
1155                                         }
1156                                         get_SVA( &value, tvb, &offset, trSignal );
1157                                         get_QDS( &value, tvb, &offset, trSignal );
1158                                         break;
1159                                 case M_ME_NC_1: /* 13   Measured value, short floating point value */
1160                                         /* check length */
1161                                         if( Len < (guint)(offset+5) ) {
1162                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1163                                                 return;
1164                                         }
1165                                         get_FLT( &value, tvb, &offset, trSignal );
1166                                         get_QDS( &value, tvb, &offset, trSignal );
1167                                         break;
1168                                 case M_ME_ND_1: /* 21    Measured value, normalized value without quality descriptor */
1169                                         /* check length */
1170                                         if( Len < (guint)(offset+2) ) {
1171                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1172                                                 return;
1173                                         }
1174                                         get_NVA( &value, tvb, &offset, trSignal );
1175                                         break;
1176                                 case M_SP_TB_1: /* 30   Single-point information with time tag CP56Time2a */
1177                                         /* check length */
1178                                         if( Len < (guint)(offset+8) ) {
1179                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1180                                                 return;
1181                                         }
1182                                         get_SIQ( &value, tvb, &offset, trSignal );
1183                                         get_CP56Time( &cp56t, tvb, &offset, trSignal );
1184                                         break;
1185                                 case M_DP_TB_1: /* 31   Double-point information with time tag CP56Time2a */
1186                                         /* check length */
1187                                         if( Len < (guint)(offset+8) ) {
1188                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1189                                                 return;
1190                                         }
1191                                         get_DIQ( &value, tvb, &offset, trSignal );
1192                                         get_CP56Time( &cp56t, tvb, &offset, trSignal );
1193                                         break;
1194                                 case M_ME_TD_1: /* 34    Measured value, normalized value with time tag CP56Time2a */
1195                                         /* check length */
1196                                         if( Len < (guint)(offset+10) ) {
1197                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1198                                                 return;
1199                                         }
1200                                         get_NVA( &value, tvb, &offset, trSignal );
1201                                         get_QDS( &value, tvb, &offset, trSignal );
1202                                         get_CP56Time( &cp56t, tvb, &offset, trSignal );
1203                                         break;
1204                                 case M_ME_TE_1: /* 35    Measured value, scaled value with time tag CP56Time2a */
1205                                         /* check length */
1206                                         if( Len < (guint)(offset+10) ) {
1207                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1208                                                 return;
1209                                         }
1210                                         get_SVA( &value, tvb, &offset, trSignal );
1211                                         get_QDS( &value, tvb, &offset, trSignal );
1212                                         get_CP56Time( &cp56t, tvb, &offset, trSignal );
1213                                         break;
1214                                 case M_ME_TF_1: /* 36    Measured value, short floating point value with time tag CP56Time2a */
1215                                         /* check length */
1216                                         if( Len < (guint)(offset+12) ) {
1217                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1218                                                 return;
1219                                         }
1220                                         get_FLT( &value, tvb, &offset, trSignal );
1221                                         get_QDS( &value, tvb, &offset, trSignal );
1222                                         get_CP56Time( &cp56t, tvb, &offset, trSignal );
1223                                         break;
1224                                 case C_SC_NA_1: /* 45   Single command */
1225                                         /* check length */
1226                                         if( Len < (guint)(offset+1) ) {
1227                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1228                                                 return;
1229                                         }
1230                                         get_SCO( &cmd, tvb, &offset, trSignal );
1231                                         break;
1232                                 case C_DC_NA_1: /* 46   Double command */
1233                                         /* check length */
1234                                         if( Len < (guint)(offset+1) ) {
1235                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1236                                                 return;
1237                                         }
1238                                         get_DCO( &cmd, tvb, &offset, trSignal );
1239                                         break;
1240                                 case C_SE_NA_1: /*  48    Set point command, normalized value */
1241                                         /* check length */
1242                                         if( Len < (guint)(offset+3) ) {
1243                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1244                                                 return;
1245                                         }
1246                                         get_NVAspt( &spt, tvb, &offset, trSignal );
1247                                         get_QOS( &spt, tvb, &offset, trSignal );
1248                                         break;
1249                                 case C_SE_NB_1: /* 49    Set point command, scaled value */
1250                                         /* check length */
1251                                         if( Len < (guint)(offset+3) ) {
1252                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1253                                                 return;
1254                                         }
1255                                         get_SVAspt( &spt, tvb, &offset, trSignal );
1256                                         get_QOS( &spt, tvb, &offset, trSignal );
1257                                         break;
1258                                 case C_SE_NC_1: /* 50    Set point command, short floating point value */
1259                                         /* check length */
1260                                         if( Len < (guint)(offset+5) ) {
1261                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1262                                                 return;
1263                                         }
1264                                         get_FLTspt( &spt, tvb, &offset, trSignal );
1265                                         get_QOS( &spt, tvb, &offset, trSignal );
1266                                         break;
1267                                 case C_SC_TA_1: /* 58    Single command with time tag CP56Time2a */
1268                                         /* check length */
1269                                         if( Len < (guint)(offset+8) ) {
1270                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1271                                                 return;
1272                                         }
1273                                         get_SCO( &cmd, tvb, &offset, trSignal );
1274                                         get_CP56Time( &cp56t, tvb, &offset, trSignal );
1275                                         break;
1276                                 case C_DC_TA_1: /* 59    Double command with time tag CP56Time2a */
1277                                         /* check length */
1278                                         if( Len < (guint)(offset+8) ) {
1279                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1280                                                 return;
1281                                         }
1282                                         get_DCO( &cmd, tvb, &offset, trSignal );
1283                                         get_CP56Time( &cp56t, tvb, &offset, trSignal );
1284                                         break;                                  
1285                                 case C_SE_TA_1: /* 61    Set point command, normalized value with time tag CP56Time2a */
1286                                         /* check length */
1287                                         if( Len < (guint)(offset+10) ) {
1288                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1289                                                 return;
1290                                         }
1291                                         get_NVAspt( &spt, tvb, &offset, trSignal );
1292                                         get_QOS( &spt, tvb, &offset, trSignal );
1293                                         get_CP56Time( &cp56t, tvb, &offset, trSignal );
1294                                         break;
1295                                 case C_SE_TB_1: /* 62    Set point command, scaled value with time tag CP56Time2a */
1296                                         /* check length */
1297                                         if( Len < (guint)(offset+10) ) {
1298                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1299                                                 return;
1300                                         }
1301                                         get_SVAspt( &spt, tvb, &offset, trSignal );
1302                                         get_QOS( &spt, tvb, &offset, trSignal );
1303                                         get_CP56Time( &cp56t, tvb, &offset, trSignal );
1304                                         break;
1305                                 case C_SE_TC_1: /* 63    Set point command, short floating point value with time tag CP56Time2a */
1306                                         /* check length */
1307                                         if( Len < (guint)(offset+12) ) {
1308                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1309                                                 return;
1310                                         }
1311                                         get_FLTspt( &spt, tvb, &offset, trSignal );
1312                                         get_QOS( &spt, tvb, &offset, trSignal );
1313                                         get_CP56Time( &cp56t, tvb, &offset, trSignal );
1314                                         break;
1315                                 case C_CS_NA_1: /* 103    clock synchronization command  */
1316                                         /* check length */
1317                                         if( Len < (guint)(offset+7) ) {
1318                                                 proto_tree_add_text( trSignal, tvb, offset, 1, "<ERR Short Asdu>" );
1319                                                 return;
1320                                         }
1321                                         get_CP56Time( &cp56t, tvb, &offset, trSignal );
1322                                         break;
1323
1324                                 default:
1325                                 break;
1326                                 } /* end 'switch (asduh->TypeId)' */
1327                         } /* end 'for(i = 0; i < dui.asdu_vsq_no_of_obj; i++)' */
1328                         break;
1329                 default:
1330                         break;
1331         } /* end 'switch (asdu_typeid)' */
1332
1333 }
1334
1335
1336
1337 /* Is is called twice: For 'Packet List' and for 'Packet Details' */
1338 static void dissect_iec104apci(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
1339 {
1340         guint8 TcpLen = tvb_length(tvb);
1341         guint16 Brossa = 0;
1342         guint8 Start;
1343         guint8 Off;
1344         guint8 Byte1 = 0;
1345         struct apciheader * apcih;
1346         emem_strbuf_t * res;
1347         proto_item * it104 = NULL;
1348         proto_tree * trHead;
1349
1350         if (!(check_col(pinfo->cinfo, COL_INFO) || tree))   return; /* Be sure that the function is only called twice */
1351
1352         col_set_str(pinfo->cinfo, COL_PROTOCOL, "104apci");
1353         col_clear(pinfo->cinfo, COL_INFO);
1354
1355         apcih = ep_alloc(sizeof(struct apciheader));
1356
1357         /*** *** START: Common to 'Packet List' and 'Packet Details' *** ***/
1358         Start = 0;
1359         for (Off= 0; Off <= TcpLen- 2; Off++)  {
1360                 Start = tvb_get_guint8(tvb, Off);
1361                 if (Start == APCI_START)  {
1362                         Brossa = Off;
1363                         apcih->ApduLen = tvb_get_guint8(tvb, Off+ 1);
1364                         if (apcih->ApduLen >= APDU_MIN_LEN)  {
1365                                 Byte1 = tvb_get_guint8(tvb, Off+ 2);
1366                                 apcih->Type = Byte1 & 0x03;
1367                                 /* Type I is only lowest bit set to 0 */
1368                                 if (apcih->Type == 2)   apcih->Type = 0;
1369                                 switch(apcih->Type)  {
1370                                 case I_TYPE:
1371                                         apcih->Tx = tvb_get_letohs(tvb, Off+ 2) >> 1;
1372                                 case S_TYPE:
1373                                         apcih->Rx = tvb_get_letohs(tvb, Off+ 4) >> 1;
1374                                         break;
1375                                 case U_TYPE:
1376                                         apcih->UType = (Byte1 & 0xFC); /* Don't shift */
1377                                         break;
1378                                 }
1379                         }
1380                         else   {
1381                                 /* WireShark can crash if we process packets with length less than expected (6). We consider that everything is bad */
1382                                 Brossa = TcpLen;
1383                         }
1384                         /* Don't search more the APCI_START */
1385                         break;
1386                 }
1387         }
1388         if (Start != APCI_START)  {
1389                 /* Everything is bad (no APCI found) */
1390                 Brossa = TcpLen;
1391         }
1392         /* Construir string comu a List i Details */
1393         res = ep_strbuf_new_label(NULL);
1394         if (Brossa > 0)
1395                 ep_strbuf_append_printf(res, "<ERR %u bytes> ", Brossa);
1396         if (Brossa != TcpLen)  {
1397                 if (apcih->ApduLen <= APDU_MAX_LEN)  {
1398                         /* APCI in 'Paquet List' */
1399                         ep_strbuf_append_printf(res, "%s%s(", pinfo->srcport == iec104port ? "->" : "<-", val_to_str(apcih->Type, apci_types, "<ERR>"));
1400                         switch(apcih->Type) {  /* APCI in 'Packet List' */
1401                         case I_TYPE:
1402                                 ep_strbuf_append_printf(res, "%d,", apcih->Tx);
1403                         case S_TYPE:
1404                                 ep_strbuf_append_printf(res, "%d)", apcih->Rx);
1405                                 /* Align first packets */
1406                                 if (apcih->Tx < 10)
1407                                         ep_strbuf_append_c(res, ' ');
1408                                 if (apcih->Rx < 10)
1409                                         ep_strbuf_append_c(res, ' ');
1410                                 break;
1411                         case U_TYPE:
1412                                 ep_strbuf_append_printf(res, "%s)", val_to_str(apcih->UType >> 2, u_types, "<ERR>"));
1413                                 break;
1414                         }
1415                         if (apcih->Type != I_TYPE  &&  apcih->ApduLen > APDU_MIN_LEN)   ep_strbuf_append_printf(res, "<ERR %u bytes> ", apcih->ApduLen- APDU_MIN_LEN);
1416                 }
1417                 else  {
1418                         ep_strbuf_append_printf(res, "<ERR ApduLen=%u bytes> ", apcih->ApduLen);
1419                 }
1420         }
1421         ep_strbuf_append_c(res, ' '); /* We add a space to separate possible APCIs/ASDUs in the same packet */
1422         /*** *** END: Common to 'Packet List' and 'Packet Details' *** ***/
1423
1424         /*** *** Dissect 'Packet List' *** ***/
1425         if (check_col(pinfo->cinfo, COL_INFO))  {
1426                 col_add_str(pinfo->cinfo, COL_INFO, res->str);
1427                 if(apcih->Type == I_TYPE  &&  Brossa != TcpLen)   {
1428                         call_dissector(iec104asdu_handle, tvb_new_subset(tvb, Off+ APCI_LEN, -1, apcih->ApduLen- APCI_DATA_LEN), pinfo, tree);
1429                 } else {
1430                         col_set_fence(pinfo->cinfo, COL_INFO);
1431                 }
1432         }
1433
1434         if(!tree)   return;
1435
1436         /*** *** DISSECT 'Packet Details' *** ***/
1437
1438         it104 = proto_tree_add_item(tree, proto_iec104apci, tvb, 0, Off+ APCI_LEN, FALSE);
1439
1440         /* 'Packet Details': ROOT ITEM */
1441         proto_item_append_text(it104, ": %s", res->str);
1442
1443         if(Brossa == TcpLen)   return;
1444
1445         /* Don't call ASDU dissector if it was called before */
1446         if(apcih->Type == I_TYPE  &&  (!check_col(pinfo->cinfo, COL_INFO))){
1447                 call_dissector(iec104asdu_handle, tvb_new_subset(tvb, Off+ APCI_LEN, -1, apcih->ApduLen- APCI_DATA_LEN), pinfo, tree);
1448         }
1449
1450         /* 'Packet Details': TREE */
1451         trHead = proto_item_add_subtree(it104, ett_apci);
1452         /* Remember:    add_uint, add_boolean, _add_text: value from last parameter.
1453                         add_item: value from tvb. */
1454         proto_tree_add_uint(trHead, hf_apdulen, tvb, Off+ 1, 1, apcih->ApduLen);
1455         proto_tree_add_uint(trHead, hf_apcitype, tvb, Off+ 2, 1, apcih->Type);
1456         switch(apcih->Type){
1457         case U_TYPE:
1458                 proto_tree_add_uint(trHead, hf_apciutype, tvb, Off+ 2, 1, apcih->UType); /* Don't shift the value */
1459                 break;
1460         }
1461
1462 }
1463
1464
1465
1466
1467 static void dissect_iec104reas(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
1468 {
1469         /* 5th parameter = 6 = minimum bytes received to calculate the length. 
1470          * (Not 2 in order to find more APCIs in case of 'noisy' bytes between the APCIs)
1471          */
1472         tcp_dissect_pdus(tvb, pinfo, tree, TRUE, APCI_LEN,
1473                         get_iec104apdu_len, dissect_iec104apci);
1474 }
1475
1476
1477 /* The protocol has two subprotocols: Register APCI */
1478 void
1479 proto_register_iec104apci(void)
1480 {
1481
1482         static hf_register_info hf_ap[] = {
1483
1484                 { &hf_apdulen,
1485                   { "ApduLen", "104apci.apdulen", FT_UINT8, BASE_DEC, NULL, 0x0,
1486                     "APDU Len", HFILL }},
1487
1488                 { &hf_apcitype,
1489                   { "ApciType", "104apci.type", FT_UINT8, BASE_HEX, VALS(apci_types), 0x03,
1490                     "APCI type", HFILL }},
1491
1492                 { &hf_apciutype,
1493                   { "ApciUType", "104apci.utype", FT_UINT8, BASE_HEX, VALS(u_types), 0xFC,
1494                     "Apci U type", HFILL }},
1495
1496         };
1497
1498         static gint *ett_ap[] = {
1499                 &ett_apci,
1500         };
1501
1502         proto_iec104apci = proto_register_protocol(
1503                 "IEC 60870-5-104-Apci",
1504                 "104apci",
1505                 "104apci"
1506                 );
1507         proto_register_field_array(proto_iec104apci, hf_ap, array_length(hf_ap));
1508         proto_register_subtree_array(ett_ap, array_length(ett_ap));
1509
1510 }
1511
1512
1513 /* The protocol has two subprotocols: Register ASDU */
1514 void
1515 proto_register_iec104asdu(void)
1516 {
1517
1518         static hf_register_info hf_as[] = {
1519
1520                 { &hf_addr,
1521                   { "Addr", "104asdu.addr", FT_UINT16, BASE_DEC, NULL, 0x0,
1522                     "Common Address of Asdu", HFILL }},
1523
1524                 { &hf_oa,
1525                   { "OA", "104asdu.oa", FT_UINT8, BASE_DEC, NULL, 0x0,
1526                     "Originator Address", HFILL }},
1527
1528                 { &hf_typeid,
1529                   { "TypeId", "104asdu.typeid", FT_UINT8, BASE_DEC, VALS(asdu_types), 0x0,
1530                     "Asdu Type Id", HFILL }},
1531
1532                 { &hf_causetx,
1533                   { "CauseTx", "104asdu.causetx", FT_UINT8, BASE_DEC, VALS(causetx_types), 0x3F,
1534                     "Cause of Transmision", HFILL }},
1535
1536                 { &hf_nega,
1537                   { "Negative", "104asdu.nega", FT_BOOLEAN, 8, NULL, F_NEGA,
1538                     NULL, HFILL }},
1539
1540                 { &hf_test,
1541                   { "Test", "104asdu.test", FT_BOOLEAN, 8, NULL, F_TEST,
1542                     NULL, HFILL }},
1543
1544
1545                 { &hf_ioa,
1546                   { "IOA", "104asdu.ioa", FT_UINT24, BASE_DEC, NULL, 0x0,
1547                     "Information Object Address", HFILL }},
1548
1549                 { &hf_numix,
1550                   { "NumIx", "104asdu.numix", FT_UINT8, BASE_DEC, NULL, 0x7F,
1551                     "Number of Information Objects/Elements", HFILL }},
1552
1553                 { &hf_sq,
1554                   { "SQ", "104asdu.sq", FT_BOOLEAN, 8, NULL, F_SQ,
1555                     "Sequence", HFILL }},
1556
1557                 { &hf_iec104_asdufloat,
1558                 { "Object value", "iec104.asdu_float", FT_FLOAT, BASE_NONE, NULL, 0x0,
1559                  "Object value", HFILL }},
1560
1561                 { &hf_iec104_asdunormval,
1562                 { "Object value", "iec104.asdu_asdunormval", FT_INT16, BASE_DEC, NULL, 0x0,
1563                  "Object value", HFILL }},
1564
1565         };
1566
1567         static gint *ett_as[] = {
1568                 &ett_asdu,
1569         };
1570
1571         proto_iec104asdu = proto_register_protocol(
1572                 "IEC 60870-5-104-Asdu",
1573                 "104asdu",
1574                 "104asdu"
1575                 );
1576         proto_register_field_array(proto_iec104asdu, hf_as, array_length(hf_as));
1577         proto_register_subtree_array(ett_as, array_length(ett_as));
1578
1579 }
1580
1581
1582
1583 /* The registration hand-off routine */
1584 void
1585 proto_reg_handoff_iec104(void)
1586 {
1587         dissector_handle_t iec104apci_handle;
1588
1589         iec104apci_handle = create_dissector_handle(dissect_iec104reas, proto_iec104apci);
1590         iec104asdu_handle = create_dissector_handle(dissect_iec104asdu, proto_iec104asdu);
1591
1592         dissector_add("tcp.port", iec104port, iec104apci_handle);
1593 }
1594