From Kovarththanan Rajaratnam via bug 3548:
[obnox/wireshark/wip.git] / epan / dissectors / packet-synphasor.c
1 /* packet-synphasor.c
2  * Dissector for IEEE C37.118 synchrophasor frames.
3  *
4  * Copyright 2008, Jens Steinhauser <jens.steinhauser@omicron.at>
5  *
6  * $Id$ 
7  *
8  * Wireshark - Network traffic analyzer
9  * By Gerald Combs <gerald@wireshark.org>
10  * Copyright 1998 Gerald Combs
11  *
12  * This program is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU General Public License
14  * as published by the Free Software Foundation; either version 2
15  * of the License, or (at your option) any later version.
16  * 
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  * 
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25  */
26
27 #ifdef HAVE_CONFIG_H
28 #       include "config.h"
29 #endif
30
31 #include <glib.h>
32 #include <epan/conversation.h>
33 #include <epan/crc16.h>
34 #include <epan/dissectors/packet-tcp.h>
35 #include <epan/packet.h>
36 #include <epan/prefs.h>
37
38 #include <math.h>
39
40 #define PROTOCOL_NAME       "IEEE C37.118 Synchrophasor Protocol"
41 #define PROTOCOL_SHORT_NAME "SYNCHROPHASOR" 
42 #define PROTOCOL_ABBREV     "synphasor"
43
44 /* forward references */
45 void proto_reg_handoff_synphasor(void);
46
47 /* global variables */
48
49 static int     proto_synphasor   = -1;
50 static GSList *config_frame_list = NULL;
51
52 #if ! GLIB_CHECK_VERSION(2,10,0)
53 static GMemChunk *frame_chunks   = NULL;
54 #endif
55
56 /* user preferences */
57 static guint global_pref_tcp_port = 4712;
58 static guint global_pref_udp_port = 4713;
59
60 /* the ett... variables hold the state (open/close) of the treeview in the GUI */
61 static gint ett_synphasor          = -1; /* root element for this protocol */
62   /* used in the common header */
63   static gint ett_frtype           = -1;
64   static gint ett_timequal         = -1;
65   /* used for config frames */
66   static gint ett_conf             = -1;
67     static gint ett_conf_station   = -1;
68       static gint ett_conf_format  = -1;
69       static gint ett_conf_phnam   = -1;
70       static gint ett_conf_annam   = -1;
71       static gint ett_conf_dgnam   = -1;
72       static gint ett_conf_phconv  = -1;
73       static gint ett_conf_anconv  = -1;
74       static gint ett_conf_dgmask  = -1;
75   /* used for data frames */
76   static gint ett_data             = -1;
77     static gint ett_data_block     = -1;
78       static gint ett_data_stat    = -1;
79       static gint ett_data_phasors = -1;
80       static gint ett_data_analog  = -1;
81       static gint ett_data_digital = -1;
82   /* used for command frames */
83   static gint ett_command          = -1;
84
85 /* handles to the header fields hf[] in proto_register_synphasor() */
86 static int hf_sync                  = -1;
87 static int hf_sync_frtype           = -1;
88 static int hf_sync_version          = -1;
89 static int hf_idcode                = -1;
90 static int hf_frsize                = -1;
91 static int hf_soc                   = -1;
92 static int hf_timeqal_lsdir         = -1;
93 static int hf_timeqal_lsocc         = -1;
94 static int hf_timeqal_lspend        = -1;
95 static int hf_timeqal_timequalindic = -1;
96 static int hf_fracsec               = -1;
97 static int hf_conf_timebase         = -1;
98 static int hf_conf_numpmu           = -1;
99 static int hf_conf_formatb3         = -1;
100 static int hf_conf_formatb2         = -1;
101 static int hf_conf_formatb1         = -1;
102 static int hf_conf_formatb0         = -1;
103 static int hf_conf_fnom             = -1;
104 static int hf_conf_cfgcnt           = -1;
105 static int hf_data_statb15          = -1;
106 static int hf_data_statb14          = -1;
107 static int hf_data_statb13          = -1;
108 static int hf_data_statb12          = -1;
109 static int hf_data_statb11          = -1;
110 static int hf_data_statb10          = -1;
111 static int hf_data_statb05to04      = -1;
112 static int hf_data_statb03to00      = -1;
113 static int hf_command               = -1;
114
115 /* the five different frame types for this protocol */
116 enum FrameType {
117         DATA = 0,
118         HEADER,
119         CFG1,
120         CFG2,
121         CMD
122 };
123
124 /* the channel names in the protocol are all 16 bytes
125  * long (and don't have to be NULL terminated) */
126 #define CHNAM_LEN 16
127
128 /* Structures to save CFG frame content. */
129
130 /* type to indicate the format for (D)FREQ/PHASORS/ANALOG in data frame  */
131 typedef enum { integer,         /* 16 bit signed integer */
132                floating_point   /* single precision floating point */
133 } data_format; 
134
135 typedef enum { rect, polar } phasor_notation;
136
137 /* holds the information required to dissect a single phasor */
138 typedef struct {
139         char          name[CHNAM_LEN + 1];
140         enum { V, A } unit;
141         guint32       conv; /* conversation factor in 10^-5 scale */
142 } phasor_info;
143
144 /* holds the information for an analog value */
145 typedef struct {
146         char    name[CHNAM_LEN + 1];
147         guint32 conv; /* conversation factor, user defined scaling (so its pretty useless) */
148 } analog_info;
149
150 /* holds information required to dissect a single PMU block in a data frame */
151 typedef struct {
152         guint16         id;                  /* identifies source of block     */
153         char            name[CHNAM_LEN + 1]; /* holds STN                      */
154         data_format     format_fr;           /* data format of FREQ and DFREQ  */
155         data_format     format_ph;           /* data format of PHASORS         */
156         data_format     format_an;           /* data format of ANALOG          */
157         phasor_notation phasor_notation;     /* format of the phasors          */
158         guint           fnom;                /* nominal line frequency         */
159         guint           num_dg;              /* number of digital status words */
160         GArray         *phasors;             /* array of phasor_infos          */
161         GArray         *analogs;             /* array of analog_infos          */
162 } config_block;
163
164 /* holds the id the configuration comes from an and
165  * an array of config_block members */
166 typedef struct {
167         guint32  fnum;          /* frame number */
168
169         guint16  id;
170         GArray  *config_blocks; /* Contains a config_block struct for
171                                  * every PMU included in the config frame */
172 } config_frame;
173
174 /* strings for type bits in SYNC */
175 static const value_string typenames[] = { 
176         { 0, "Data Frame"            },
177         { 1, "Header Frame"          },
178         { 2, "Configuration Frame 1" },
179         { 3, "Configuration Frame 2" },
180         { 4, "Command Frame"         },
181         { 0, NULL                    }
182 };
183
184 /* strings for version bits in SYNC */
185 static const value_string versionnames[] = { 
186         { 1, "IEEE C37.118-2005 initial publication" },
187         { 0, NULL                                    }
188 };
189
190 /* strings for the time quality flags in FRACSEC */
191 static const value_string timequalcodes[] = { 
192         { 0xF, "Clock failure, time not reliable"    },
193         { 0xB, "Clock unlocked, time within 10 s"    },
194         { 0xA, "Clock unlocked, time within 1 s"     },
195         { 0x9, "Clock unlocked, time within 10^-1 s" },
196         { 0x8, "Clock unlocked, time within 10^-2 s" },
197         { 0x7, "Clock unlocked, time within 10^-3 s" },
198         { 0x6, "Clock unlocked, time within 10^-4 s" },
199         { 0x5, "Clock unlocked, time within 10^-5 s" },
200         { 0x4, "Clock unlocked, time within 10^-6 s" },
201         { 0x3, "Clock unlocked, time within 10^-7 s" },
202         { 0x2, "Clock unlocked, time within 10^-8 s" },
203         { 0x1, "Clock unlocked, time within 10^-9 s" },
204         { 0x0, "Normal operation, clock locked"      },
205         {  0 , NULL                                  }
206 };
207
208 /* strings for flags in the FORMAT word of a configuration frame */
209 static const true_false_string conf_formatb123names = {
210         "floating point",
211         "16-bit integer"
212 };
213 static const true_false_string conf_formatb0names = {
214         "polar",
215         "rectangular"
216 };
217
218 /* strings to decode ANUNIT in configuration frame */
219 static const range_string conf_anconvnames[] = {
220         {  0,   0, "single point-on-wave" },
221         {  1,   1, "rms of analog input"  },
222         {  2,   2, "peak of input"        },
223         {  3,   4, "undefined"            },
224         {  5,  64, "reserved"             },
225         { 65, 255, "user defined"         },
226         {  0,   0, NULL                   }
227 };
228
229 /* strings for the FNOM field */
230 static const true_false_string conf_fnomnames = {
231         "50Hz",
232         "60Hz"
233 };
234
235 /* strings for flags in the STAT word of a data frame */
236 static const true_false_string data_statb15names = {
237         "Data is invalid",
238         "Data is valid"
239 };
240 static const true_false_string data_statb14names = {
241         "Error",
242         "No error"
243 };
244 static const true_false_string data_statb13names = {
245         "Synchronization lost",
246         "Clock is synchronized"
247 };
248 static const true_false_string data_statb12names = {
249         "By arrival",
250         "By timestamp"
251 };
252 static const true_false_string data_statb11names = {
253         "Trigger detected",
254         "No trigger"
255 };
256 static const true_false_string data_statb10names = {
257         "Within 1 minute",
258         "No"
259 };
260 static const value_string      data_statb05to04names[] = {
261         { 0, "Time locked, best quality" },
262         { 1, "Unlocked for 10s"          },
263         { 2, "Unlocked for 100s"         },
264         { 3, "Unlocked for over 1000s"   },
265         { 0, NULL                        }
266 };
267 static const value_string      data_statb03to00names[] = {
268         { 0x0, "Manual"             },
269         { 0x1, "Magnitude low"      },
270         { 0x2, "Magnitude high"     },
271         { 0x3, "Phase-angel diff"   },
272         { 0x4, "Frequency high/low" },
273         { 0x5, "df/dt high"         },
274         { 0x6, "Reserved"           },
275         { 0x7, "Digital"            },
276         { 0x8, "User defined"       },
277         { 0x9, "User defined"       },
278         { 0xA, "User defined"       },
279         { 0xB, "User defined"       },
280         { 0xC, "User defined"       },
281         { 0xD, "User defined"       },
282         { 0xE, "User defined"       },
283         { 0xF, "User defined"       },
284         {  0 , NULL                 }
285 };
286
287 /* strings to decode the commands */
288 static const value_string command_names[] = {
289         {  0, "unknown command"       },
290         {  1, "data transmission off" },
291         {  2, "data transmission on"  },
292         {  3, "send HDR frame"        },
293         {  4, "send CFG-1 frame"      },
294         {  5, "send CFG-2 frame"      },
295         {  6, "unknown command"       },
296         {  7, "unknown command"       },
297         {  8, "extended frame"        },
298         {  9, "unknown command"       },
299         { 10, "unknown command"       },
300         { 11, "unknown command"       },
301         { 12, "unknown command"       },
302         { 13, "unknown command"       },
303         { 14, "unknown command"       },
304         { 15, "unknown command"       },
305         {  0, NULL                    }
306 };
307
308 /* Dissects a configuration frame (only the most important stuff, tries
309  * to be fast, does no GUI stuff) and returns a pointer to a config_frame
310  * struct that contains all the information from the frame needed to
311  * dissect a DATA frame.
312  *
313  * use 'config_frame_free()' to free the config_frame again
314  */
315 static config_frame* config_frame_fast(tvbuff_t *tvb)
316 {
317         guint16       idcode, num_pmu;
318         gint          offset;
319         config_frame *frame;
320
321         /* get a new frame and initialize it */
322 #if GLIB_CHECK_VERSION(2,10,0)
323         frame = g_slice_new(config_frame);
324 #else
325         frame = g_mem_chunk_alloc(frame_chunks);
326 #endif
327
328         frame->config_blocks = g_array_new(FALSE, TRUE, sizeof(config_block));
329
330         idcode = tvb_get_ntohs(tvb, 4);
331         frame->id       = idcode;
332
333         num_pmu = tvb_get_ntohs(tvb, 18);
334         offset = 20; /* start of repeating blocks */
335
336         while (num_pmu) {
337                 guint16      format_flags;
338                 gint         num_ph,
339                              num_an,
340                              num_dg;
341                 gint         i,
342                              phunit,
343                              anunit,
344                              fnom;
345                 config_block block;
346
347                 /* initialize the block */
348                 block.phasors = g_array_new(FALSE, TRUE, sizeof(phasor_info));
349                 block.analogs = g_array_new(FALSE, TRUE, sizeof(analog_info));
350                 /* copy the station name from the tvb to block, and add NULL byte */
351                 tvb_memcpy(tvb, block.name, offset, CHNAM_LEN); offset += CHNAM_LEN;
352                 block.name[CHNAM_LEN] = '\0';
353
354                 block.id = tvb_get_ntohs(tvb, offset); offset += 2;
355
356                 format_flags          = tvb_get_ntohs(tvb, offset); offset += 2;
357                 block.format_fr       = (format_flags & 0x0008) ? floating_point : integer;
358                 block.format_an       = (format_flags & 0x0004) ? floating_point : integer;
359                 block.format_ph       = (format_flags & 0x0002) ? floating_point : integer;
360                 block.phasor_notation = (format_flags & 0x0001) ? polar          : rect;
361
362                 num_ph = tvb_get_ntohs(tvb, offset); offset += 2;
363                 num_an = tvb_get_ntohs(tvb, offset); offset += 2;
364                 num_dg = tvb_get_ntohs(tvb, offset); offset += 2;
365                 block.num_dg = num_dg;
366
367                 /* the offset of the PHUNIT, ANUNIT, and FNOM blocks */
368                 phunit = offset + (num_ph + num_an + num_dg * CHNAM_LEN) * CHNAM_LEN;
369                 anunit = phunit + num_ph * 4;
370                 fnom   = anunit + num_an * 4 + num_dg * 4;
371
372                 /* read num_ph phasor names and conversation factors */
373                 for (i = 0; i != num_ph; i++) {
374                         phasor_info  pi;
375                         guint32      conv;
376                         
377                         /* copy the phasor name from the tvb, and add NULL byte */
378                         tvb_memcpy(tvb, pi.name, offset, CHNAM_LEN); offset += CHNAM_LEN;
379                         pi.name[CHNAM_LEN] = '\0';
380                         
381                         conv = tvb_get_ntohl(tvb, phunit + 4 * i);
382                         pi.unit = conv & 0xFF000000 ? A : V;
383                         pi.conv = conv & 0x00FFFFFF;
384
385                         g_array_append_val(block.phasors, pi);
386                 }
387
388                 /* read num_an analog value names and conversation factors */
389                 for (i = 0; i != num_an; i++) {
390                         analog_info ai;
391                         guint32     conv;
392
393                         /* copy the phasor name from the tvb, and add NULL byte */
394                         tvb_memcpy(tvb, ai.name, offset, CHNAM_LEN); offset += CHNAM_LEN;
395                         ai.name[CHNAM_LEN] = '\0';
396
397                         conv = tvb_get_ntohl(tvb, anunit + 4 * i);
398                         ai.conv = conv;
399
400                         g_array_append_val(block.analogs, ai);
401                 }
402
403                 /* the names for the bits in the digital status words aren't saved,
404                    there is no space to display them in the GUI anyway */
405
406                 /* save FNOM */
407                 block.fnom = tvb_get_ntohs(tvb, fnom) & 0x0001 ? 50 : 60;
408                 offset = fnom + 2;
409
410                 /* skip CFGCNT */
411                 offset += 2;
412
413                 g_array_append_val(frame->config_blocks, block);
414                 num_pmu--;
415         }
416
417         return frame;
418 }
419
420 /* Frees the memory pointed to by 'frame' and all the contained
421  * config_blocks and the data in their GArrays.
422  */
423 static void config_frame_free(config_frame *frame)
424 {
425         int i = frame->config_blocks->len;
426
427         /* free all the config_blocks this frame contains */
428         while (i--) {
429                 config_block *block;
430
431                 block = &g_array_index(frame->config_blocks, config_block, i);
432                 g_array_free(block->phasors, TRUE);
433                 g_array_free(block->analogs, TRUE);
434         }
435
436         /* free the array of config blocks itself */
437         g_array_free(frame->config_blocks, TRUE);
438
439         /* and the config_frame */
440 #if GLIB_CHECK_VERSION(2,10,0)
441         g_slice_free1(sizeof(config_frame), frame);
442 #else
443         g_mem_chunk_free(frame_chunks, frame);
444 #endif
445 }
446
447 /* called every time the user loads a capture file or starts to capture */
448 static void synphasor_init(void)
449 {
450         /* free stuff in the list from a previous run */
451         if (config_frame_list) {
452                 g_slist_foreach(config_frame_list, (GFunc) config_frame_free, NULL);
453
454                 g_slist_free(config_frame_list);
455
456                 config_frame_list = NULL;
457         }
458
459 #if ! GLIB_CHECK_VERSION(2,10,0)
460         if (frame_chunks != NULL) {
461             g_mem_chunk_destroy(frame_chunks);
462         }
463         frame_chunks = g_mem_chunk_new("Frame_Chunks", 
464                                        sizeof(config_frame),
465                                        10*(sizeof(config_frame)),
466                                        G_ALLOC_AND_FREE);
467 #endif
468 }
469
470 /* the main dissection routine */
471 static void dissect_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree);
472
473 /* called for synchrophasors over UDP */
474 static void dissect_udp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
475 {
476         dissect_common(tvb, pinfo, tree);
477 }
478
479 /* callback for 'tcp_dissect_pdus()' to give it the length of the frame */
480 static guint get_pdu_length(packet_info *pinfo _U_, tvbuff_t *tvb, int offset)
481 {
482         return tvb_get_ntohs(tvb, offset + 2);
483 }
484
485 static void dissect_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
486 {
487         tcp_dissect_pdus(tvb, pinfo, tree, TRUE, 4, get_pdu_length, dissect_common);
488 }
489
490 /* Checks the CRC of a synchrophasor frame, 'tvb' has to include the whole
491  * frame, including CRC, the calculated CRC is returned in '*computedcrc'.
492  */
493 static gboolean check_crc(tvbuff_t *tvb, guint16 *computedcrc)
494 {
495         guint16 crc;
496         guint   len = tvb_get_ntohs(tvb, 2);
497
498         crc = tvb_get_ntohs(tvb, len - 2); 
499         *computedcrc = crc16_x25_ccitt_tvb(tvb, len - 2);
500
501         if (crc == *computedcrc)
502                 return TRUE;
503
504         return FALSE;
505 }
506
507 /* forward declarations of the subdissectors for the data
508  * in the frame that is not common to all types of frames
509  */
510 static int dissect_config_frame (tvbuff_t *, proto_item *);
511 static int dissect_data_frame   (tvbuff_t *, proto_item *, packet_info *);
512 static int dissect_command_frame(tvbuff_t *, proto_item *, packet_info *);
513 /* to keep 'dissect_common()' shorter */
514 static gint dissect_header(tvbuff_t *, proto_tree *);
515
516 /* Dissects the header (common to all types of frames) and then calls
517  * one of the subdissectors (declared above) for the rest of the frame.
518  */
519 static void dissect_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
520 {
521         guint8  frame_type;
522         guint16 crc;
523         guint   tvbsize = tvb_length(tvb);
524
525         /* some heuristics */
526         if (tvbsize < 17                    /* 17 bytes = header frame with only a
527                                                NULL character, useless but valid */
528          || tvb_get_guint8(tvb, 0) != 0xAA) /* every synchrophasor frame starts with 0xAA */
529                 return;
530
531         /* write the protocol name to the info column */
532         if (check_col(pinfo->cinfo, COL_PROTOCOL))
533                 col_set_str(pinfo->cinfo, COL_PROTOCOL, PROTOCOL_SHORT_NAME);
534
535         frame_type = tvb_get_guint8(tvb, 1) >> 4;
536
537         if (check_col(pinfo->cinfo, COL_INFO)) {
538                 col_clear(pinfo->cinfo, COL_INFO); /* clear out stuff in the info column */
539                 col_add_fstr(pinfo->cinfo, COL_INFO, "%s", val_to_str(frame_type, typenames, "invalid packet type"));
540         }
541
542         /* CFG-2 and DATA frames need special treatment during the first run:
543          * For CFG-2 frames, a 'config_frame' struct is created to hold the
544          * information necessary to decode DATA frames. A pointer to this
545          * struct is saved in the conversation and is copied to the
546          * per-packet information if a DATA frame is dissected.
547          */
548         if (!pinfo->fd->flags.visited) {
549                 if (CFG2 == frame_type &&
550                     check_crc(tvb, &crc)) {
551                         conversation_t *conversation;
552
553                         /* fill the config_frame */
554                         config_frame *frame = config_frame_fast(tvb);
555                         frame->fnum = pinfo->fd->num;
556                         /* so we can cleanup later */
557                         config_frame_list = g_slist_append(config_frame_list, frame);
558
559                         /* find a conversation, create a new if no one exists */
560                         conversation = find_conversation(pinfo->fd->num,
561                                                          &pinfo->src, &pinfo->dst,
562                                                          pinfo->ptype,
563                                                          pinfo->srcport, pinfo->destport,
564                                                          0);
565
566                         if (!conversation)
567                                 conversation = conversation_new(pinfo->fd->num,
568                                                                 &pinfo->src, &pinfo->dst,
569                                                                 pinfo->ptype,
570                                                                 pinfo->srcport, pinfo->destport,
571                                                                 0);
572
573                         /* remove data from a previous CFG-2 frame, only
574                          * the most recent configuration frame is relevant */
575                         if (conversation_get_proto_data(conversation, proto_synphasor))
576                                 conversation_delete_proto_data(conversation, proto_synphasor);
577
578                         conversation_add_proto_data(conversation, proto_synphasor, frame);
579                 }
580                 else if (DATA == frame_type) {
581                         conversation_t *conversation = find_conversation(pinfo->fd->num,
582                                                                          &pinfo->src, &pinfo->dst,
583                                                                          pinfo->ptype,
584                                                                          pinfo->srcport, pinfo->destport,
585                                                                          0);
586
587                         if (conversation) {
588                                 config_frame *conf = conversation_get_proto_data(conversation, proto_synphasor);
589                                 /* no problem if 'conf' is NULL, the DATA frame dissector checks this again */
590                                 p_add_proto_data(pinfo->fd, proto_synphasor, conf);
591                         }
592                 }
593         } /* if (!visited) */
594
595         if (tree) { /* we are being asked for details */
596                 proto_tree *synphasor_tree = NULL;
597                 proto_item *temp_item      = NULL;
598                 proto_item *sub_item       = NULL;
599
600                 gint     offset;
601                 guint16  framesize;
602                 tvbuff_t *sub_tvb;
603
604                 temp_item = proto_tree_add_item(tree, proto_synphasor, tvb, 0, -1, FALSE);
605                 proto_item_append_text(temp_item, ", %s", val_to_str(frame_type, typenames,
606                                                                      ", invalid packet type"));
607
608                 /* synphasor_tree is where from now on all new elements for this protocol get added */
609                 synphasor_tree = proto_item_add_subtree(temp_item, ett_synphasor);
610
611                 framesize = dissect_header(tvb, synphasor_tree);
612                 offset = 14; /* header is 14 bytes long */
613
614                 /* check CRC, call appropriate subdissector for the rest of the frame if CRC is correct*/
615                 sub_item  = proto_tree_add_text(synphasor_tree, tvb, offset     , tvbsize - 16, "Data"     );
616                 temp_item = proto_tree_add_text(synphasor_tree, tvb, tvbsize - 2, 2           , "Checksum:");
617                 if (!check_crc(tvb, &crc)) {
618                         proto_item_append_text(sub_item,  ", not dissected because of wrong checksum");
619                         proto_item_append_text(temp_item, " 0x%04x [incorrect]", crc);
620                 }
621                 else {
622                         /* create a new tvb to pass to the subdissector
623                            '-16': length of header + 2 CRC bytes */
624                         sub_tvb = tvb_new_subset(tvb, offset, tvbsize - 16, framesize - 16);
625
626                         /* call subdissector */
627                         switch (frame_type) {
628                                 case DATA:
629                                         offset += dissect_data_frame(sub_tvb, sub_item, pinfo);
630                                         break;
631                                 case HEADER: /* no further dissection is done/needed */
632                                         proto_item_append_text(sub_item, "Header Frame");
633                                         offset += tvb_length(sub_tvb);
634                                         break;
635                                 case CFG1:
636                                 case CFG2:
637                                         offset += dissect_config_frame(sub_tvb, sub_item);
638                                         break;
639                                 case CMD:
640                                         offset += dissect_command_frame(sub_tvb, sub_item, pinfo);
641                                         break;
642
643                                 default:
644                                         proto_item_append_text(sub_item, " of unknown type");
645                         }
646                         proto_item_append_text(temp_item, " 0x%04x [correct]", crc);
647                 }
648
649                 offset += 2; /* CRC */
650         } /* if (tree) */
651 } /* dissect_synphasor() */
652
653 /* Dissects the common header of frames.
654  *
655  * Returns the framesize, in contrast to most
656  * other helper functions that return the offset.
657  */
658 static gint dissect_header(tvbuff_t *tvb, proto_tree *tree)
659 {
660         proto_tree *temp_tree;
661         proto_item *temp_item;
662
663         gint    offset = 0;
664         guint16 framesize, idcode;
665
666         /* SYNC and flags */
667         temp_item = proto_tree_add_item(tree, hf_sync, tvb, offset, 2, FALSE);
668         temp_tree = proto_item_add_subtree(temp_item, ett_frtype);
669                 proto_tree_add_item(temp_tree, hf_sync_frtype,  tvb, offset, 2, FALSE);
670                 proto_tree_add_item(temp_tree, hf_sync_version, tvb, offset, 2, FALSE);
671         offset += 2;
672
673         /* FRAMESIZE */
674         proto_tree_add_item(tree, hf_frsize, tvb, offset, 2, FALSE);
675         framesize = tvb_get_ntohs(tvb, offset); offset += 2;
676
677         /* IDCODE */
678         proto_tree_add_item(tree, hf_idcode, tvb, offset, 2, FALSE);
679         idcode = tvb_get_ntohs(tvb, offset); offset += 2;
680
681         /* SOC */
682         {
683                 /* can't use 'proto_tree_add_time()' because we need UTC */
684                 char   buf[20];
685                 struct tm* t;
686                 time_t soc = tvb_get_ntohl(tvb, offset);
687                 t = gmtime(&soc);
688                 strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", t);
689                 proto_tree_add_string(tree, hf_soc, tvb, offset, 4, buf);
690                 offset += 4;
691         }
692
693         /* FRACSEC */
694         /* time quality flags */
695         temp_item = proto_tree_add_text(tree, tvb, offset, 1, "Time quality flags");
696         temp_tree = proto_item_add_subtree(temp_item, ett_timequal);
697                 proto_tree_add_item(temp_tree, hf_timeqal_lsdir,         tvb, offset, 1, FALSE);
698                 proto_tree_add_item(temp_tree, hf_timeqal_lsocc,         tvb, offset, 1, FALSE);
699                 proto_tree_add_item(temp_tree, hf_timeqal_lspend,        tvb, offset, 1, FALSE);
700                 proto_tree_add_item(temp_tree, hf_timeqal_timequalindic, tvb, offset, 1, FALSE);
701         offset += 1;
702
703         proto_tree_add_item(tree, hf_fracsec,  tvb, offset, 3, FALSE); offset += 3;
704
705         return framesize;
706 }
707
708 /* forward declarations of helper functions for 'dissect_config_frame()' */
709 static gint dissect_CHNAM  (tvbuff_t *tvb, proto_tree *tree, gint offset, gint cnt, char *prefix);
710 static gint dissect_PHUNIT (tvbuff_t *tvb, proto_tree *tree, gint offset, gint cnt);
711 static gint dissect_ANUNIT (tvbuff_t *tvb, proto_tree *tree, gint offset, gint cnt);
712 static gint dissect_DIGUNIT(tvbuff_t *tvb, proto_tree *tree, gint offset, gint cnt);
713
714 /* dissects a configuration frame (type 1 and 2) and adds fields to 'config_item' */
715 static int dissect_config_frame(tvbuff_t *tvb, proto_item *config_item)
716 {
717         proto_tree *config_tree = NULL;
718         proto_item *temp_item   = NULL;
719         proto_tree *temp_tree   = NULL;
720         gint     offset = 0, j;
721         guint16  num_pmu;
722
723         proto_item_set_text   (config_item, "Configuration data");
724         config_tree = proto_item_add_subtree(config_item, ett_conf);
725
726         /* TIME_BASE and NUM_PMU */
727         offset += 1; /* skip the reserved byte */
728         proto_tree_add_item(config_tree, hf_conf_timebase, tvb, offset, 3, FALSE); offset += 3;
729         proto_tree_add_item(config_tree, hf_conf_numpmu,   tvb, offset, 2, FALSE); 
730         /* add number of included PMUs to the text in the list view  */
731         num_pmu = tvb_get_ntohs(tvb, offset); offset += 2;
732         proto_item_append_text(config_item, ", %"G_GUINT16_FORMAT" PMU(s) included", num_pmu);
733
734         /* dissect the repeating PMU blocks */
735         for (j = 0; j < num_pmu; j++) {
736                 guint16  num_ph, num_an, num_dg;
737                 proto_item *station_item = NULL;
738                 proto_tree *station_tree = NULL;
739                 char *str;
740
741                 gint oldoffset = offset; /* to calculate the length of the whole PMU block later */
742
743                 /* STN with new tree to add the rest of the PMU block */
744                 str = tvb_get_ephemeral_string(tvb, offset, CHNAM_LEN);
745                 station_item = proto_tree_add_text(config_tree, tvb, offset, CHNAM_LEN, "Station #%i: \"%s\"", j + 1, str);
746                 station_tree = proto_item_add_subtree(station_item, ett_conf_station);
747                 offset += CHNAM_LEN;
748
749                 /* IDCODE */
750                 proto_tree_add_item(station_tree, hf_idcode, tvb, offset, 2, FALSE); offset += 2;
751
752                 /* FORMAT */
753                 temp_item = proto_tree_add_text(station_tree, tvb, offset, 2, "Data format in data frame");
754                 temp_tree = proto_item_add_subtree(temp_item, ett_conf_format);
755                         proto_tree_add_item(temp_tree, hf_conf_formatb3, tvb, offset, 2, FALSE);
756                         proto_tree_add_item(temp_tree, hf_conf_formatb2, tvb, offset, 2, FALSE);
757                         proto_tree_add_item(temp_tree, hf_conf_formatb1, tvb, offset, 2, FALSE);
758                         proto_tree_add_item(temp_tree, hf_conf_formatb0, tvb, offset, 2, FALSE);
759                 offset += 2;
760
761                 /* PHNMR, ANNMR, DGNMR */
762                 num_ph = tvb_get_ntohs(tvb, offset    );
763                 num_an = tvb_get_ntohs(tvb, offset + 2);
764                 num_dg = tvb_get_ntohs(tvb, offset + 4);
765                 proto_tree_add_text(station_tree, tvb, offset    , 2, "Number of phasors: %u",              num_ph);
766                 proto_tree_add_text(station_tree, tvb, offset + 2, 2, "Number of analog values: %u",        num_an);
767                 proto_tree_add_text(station_tree, tvb, offset + 4, 2, "Number of digital status words: %u", num_dg);
768                 offset += 6;
769
770                 /* CHNAM, the channel names */
771                 offset = dissect_CHNAM(tvb, station_tree, offset, num_ph     , "Phasor name"         );
772                 offset = dissect_CHNAM(tvb, station_tree, offset, num_an     , "Analog value"        );
773                 offset = dissect_CHNAM(tvb, station_tree, offset, num_dg * 16, "Digital status label");
774
775                 /* PHUNIT, ANUINT and DIGUNIT */
776                 offset = dissect_PHUNIT (tvb, station_tree, offset, num_ph);
777                 offset = dissect_ANUNIT (tvb, station_tree, offset, num_an);
778                 offset = dissect_DIGUNIT(tvb, station_tree, offset, num_dg);
779
780                 /* FNOM and CFGCNT */
781                 proto_tree_add_item(station_tree, hf_conf_fnom,   tvb, offset, 2, FALSE); offset += 2;
782                 proto_tree_add_item(station_tree, hf_conf_cfgcnt, tvb, offset, 2, FALSE); offset += 2;
783
784                 /* set the correct length for the "Station :" item */
785                 proto_item_set_len(station_item, offset - oldoffset);
786         } /* for() PMU blocks */
787
788         /* DATA_RATE */
789         {
790                 gint16 tmp = tvb_get_ntohs(tvb, offset);
791                 temp_item  = proto_tree_add_text(config_tree, tvb, offset, 2, "Rate of transmission: "); offset += 2;
792                 if (tmp > 0)
793                         proto_item_append_text(temp_item, "%"G_GUINT16_FORMAT" frame(s) per second", tmp);
794                 else
795                         proto_item_append_text(temp_item, "1 frame per %"G_GUINT16_FORMAT" second(s)", -tmp);
796         }
797
798         return offset;
799 } /* dissect_config_frame() */
800
801 /* forward declarations of helper functions for 'dissect_data_frame()' */
802 static gint dissect_PHASORS(tvbuff_t *tvb, proto_tree *tree, config_block *block, gint offset);
803 static gint dissect_DFREQ  (tvbuff_t *tvb, proto_tree *tree, config_block *block, gint offset);
804 static gint dissect_ANALOG (tvbuff_t *tvb, proto_tree *tree, config_block *block, gint offset);
805 static gint dissect_DIGITAL(tvbuff_t *tvb, proto_tree *tree, config_block *block, gint offset);
806 /* calculates the size (in bytes) of a data frame that the config_block describes */
807 #define BLOCKSIZE(x) (2                                                    /* STAT    */ \
808                    + (x).phasors->len * (integer == (x).format_ph ? 4 : 8) /* PHASORS */ \
809                    +                    (integer == (x).format_fr ? 4 : 8) /* (D)FREQ */ \
810                    + (x).analogs->len * (integer == (x).format_an ? 2 : 4) /* ANALOG  */ \
811                    + (x).num_dg * 2)                                       /* DIGITAL */
812
813 /* Dissects a data frame */
814 static int dissect_data_frame(tvbuff_t    *tvb,
815                               proto_item  *data_item, /* all items are placed beneath this item   */
816                               packet_info *pinfo)     /* used to find the data from a CFG-2 frame */
817 {
818         proto_tree   *data_tree  = NULL;
819         gint          offset     = 0;
820         guint         i;
821         config_frame *conf;
822
823         proto_item_set_text(data_item, "Measurement data");
824         data_tree = proto_item_add_subtree(data_item, ett_data);
825
826         /* search for configuration information to dissect the frame */
827         {
828                 gboolean config_found = FALSE;
829                 conf = p_get_proto_data(pinfo->fd, proto_synphasor);
830
831                 if (conf) {
832                         /* check if the size of the current frame is the
833                            size of the frame the config_frame describes */
834                         size_t reported_size = 0;
835                         for (i = 0; i < conf->config_blocks->len; i++) {
836                                 config_block *block = &g_array_index(conf->config_blocks, config_block, i);
837                                 reported_size += BLOCKSIZE(*block);
838                         }
839
840                         if (tvb_length(tvb) == reported_size) {
841                                 proto_item_append_text(data_item, ", using frame number %"G_GUINT32_FORMAT" as configuration frame",
842                                                        conf->fnum);
843                                 config_found = TRUE;
844                         }
845                 }
846
847                 if (!config_found) {
848                         proto_item_append_text(data_item, ", no configuration frame found");
849                         return 0;
850                 }
851         }
852
853         /* dissect a PMU block for every config_block in the frame */
854         for (i = 0; i < conf->config_blocks->len; i++) {
855                 config_block *block = &g_array_index(conf->config_blocks, config_block, i);
856
857                 proto_item *block_item = proto_tree_add_text(data_tree, tvb, offset, BLOCKSIZE(*block),
858                                                  "Station: \"%s\"", block->name);
859                 proto_tree *block_tree = proto_item_add_subtree(block_item, ett_data_block);
860
861                 /* STAT */
862                 proto_item *temp_item = proto_tree_add_text(block_tree, tvb, offset, 2, "Flags");
863                 proto_tree *temp_tree = proto_item_add_subtree(temp_item, ett_data_stat);
864                         proto_tree_add_item(temp_tree, hf_data_statb15,     tvb, offset, 2, FALSE);
865                         proto_tree_add_item(temp_tree, hf_data_statb14,     tvb, offset, 2, FALSE);
866                         proto_tree_add_item(temp_tree, hf_data_statb13,     tvb, offset, 2, FALSE);
867                         proto_tree_add_item(temp_tree, hf_data_statb12,     tvb, offset, 2, FALSE);
868                         proto_tree_add_item(temp_tree, hf_data_statb11,     tvb, offset, 2, FALSE);
869                         proto_tree_add_item(temp_tree, hf_data_statb10,     tvb, offset, 2, FALSE);
870                         proto_tree_add_item(temp_tree, hf_data_statb05to04, tvb, offset, 2, FALSE);
871                         proto_tree_add_item(temp_tree, hf_data_statb03to00, tvb, offset, 2, FALSE);
872                 offset += 2;
873
874                 /* PHASORS, (D)FREQ, ANALOG, and DIGITAL */
875                 offset = dissect_PHASORS(tvb, block_item, block, offset);
876                 offset = dissect_DFREQ  (tvb, block_item, block, offset);
877                 offset = dissect_ANALOG (tvb, block_item, block, offset);
878                 offset = dissect_DIGITAL(tvb, block_item, block, offset);
879         }
880         return offset;
881 } /* dissect_data_frame() */
882
883 /* Dissects a command frame and adds fields to config_item.
884  *
885  * 'pinfo' is used to add the type of command
886  * to the INFO column in the packet list.
887  */
888 static int dissect_command_frame(tvbuff_t    *tvb,
889                                  proto_item  *command_item,
890                                  packet_info *pinfo)
891 {
892         proto_tree *command_tree  = NULL;
893         guint       tvbsize       = tvb_length(tvb);
894
895         proto_item_set_text(command_item, "Command data");
896         command_tree = proto_item_add_subtree(command_item, ett_command);
897
898         /* CMD */
899         proto_tree_add_item(command_tree, hf_command, tvb, 0, 2, FALSE);
900         if (check_col(pinfo->cinfo, COL_INFO)) {
901                 const char *s = val_to_str(tvb_get_ntohs(tvb, 0), command_names, "invalid command");
902                 col_append_str(pinfo->cinfo, COL_INFO, ", ");
903                 col_append_str(pinfo->cinfo, COL_INFO, s);
904         }
905
906         if (tvbsize > 2) {
907                 if (tvb_get_ntohs(tvb, 0) == 0x0008) {
908                         /* Command: Extended Frame, the extra data is ok */
909                                 proto_item* i = proto_tree_add_text(command_tree, tvb, 2, tvbsize - 2, "Extended frame data");
910                                 if (tvbsize % 2)
911                                         proto_item_append_text(i, ", but size not multiple of 16-bit word");
912                 }
913                 else
914                         proto_tree_add_text(command_tree, tvb, 2, tvbsize - 2, "Unknown data");
915         }
916
917         return tvbsize;
918 } /* dissect_command_frame() */
919
920 /****************************************************************/
921 /* after this line: helper functions for 'dissect_data_frame()' */
922 /****************************************************************/
923
924 /* Dissects a single phasor for 'dissect_PHASORS()' */
925 static int dissect_single_phasor(tvbuff_t *tvb, int offset,
926                                         double* mag, double* phase, /* returns the resulting values here */
927                                         data_format     format,     /* information needed to... */
928                                         phasor_notation notation)   /*   ...dissect the phasor  */
929 {
930         if (floating_point == format) {
931                 if (polar == notation) {
932                         /* float, polar */
933                         *mag   = tvb_get_ntohieee_float(tvb, offset    );
934                         *phase = tvb_get_ntohieee_float(tvb, offset + 4);
935                 }
936                 else {
937                         /* float, rect */
938                         gfloat real, imag;
939                         real = tvb_get_ntohieee_float(tvb, offset    );
940                         imag = tvb_get_ntohieee_float(tvb, offset + 4);
941
942                         *mag   = sqrt(pow(real, 2) + pow(imag, 2));
943                         *phase = atan2(imag, real);
944                 }
945         }
946         else {
947                 if (polar == notation) {
948                         /* int, polar */
949                         *mag    = (guint16)tvb_get_ntohs(tvb, offset    );
950                         *phase  = (gint16) tvb_get_ntohs(tvb, offset + 2);
951                         *phase /= 10000.0; /* angle is in radians*10^4 */
952                 }
953                 else {
954                         /* int, rect */
955                         gint16 real, imag;
956                         real = tvb_get_ntohs(tvb, offset    );
957                         imag = tvb_get_ntohs(tvb, offset + 2);
958
959                         *mag   = sqrt(pow(real, 2) + pow(imag, 2));
960                         *phase = atan2(imag, real);
961                 }
962         }
963
964         return floating_point == format ? 8 : 4;
965 }
966
967 /* used by 'dissect_data_frame()' to dissect the PHASORS field */
968 static gint dissect_PHASORS(tvbuff_t *tvb, proto_tree *tree, config_block *block, gint offset)
969 {
970         proto_item *temp_item   = NULL;
971         proto_tree *phasor_tree = NULL;
972         guint       length;
973         gint        j,
974                     cnt = block->phasors->len; /* number of phasors to dissect */
975
976         if (0 == cnt)
977                 return offset;
978
979         length      = block->phasors->len * (floating_point == block->format_ph ? 8 : 4);
980         temp_item   = proto_tree_add_text(tree, tvb, offset, length, "Phasors (%u)", cnt);
981         phasor_tree = proto_item_add_subtree(temp_item, ett_data_phasors);
982
983         /* dissect a phasor for every phasor_info saved in the config_block */
984         for (j = 0; j < cnt; j++) {
985                 double       mag, phase;
986                 phasor_info *pi;
987
988                 pi = &g_array_index(block->phasors, phasor_info, j);
989                 temp_item = proto_tree_add_text(phasor_tree, tvb, offset,
990                                                 floating_point == block->format_ph ? 8 : 4,
991                                                 "Phasor #%u: \"%s\"", j + 1, pi->name);
992
993                 offset += dissect_single_phasor(tvb, offset,
994                                                 &mag, &phase, 
995                                                 block->format_ph,
996                                                 block->phasor_notation);
997
998                 /* for values in integer format, apply conversation factor */
999                 if (integer == block->format_ph)
1000                         mag = (mag * pi->conv) * 0.00001;
1001
1002                 #define ANGLE  "/_"
1003                 #define DEGREE "\xC2\xB0" /* DEGREE signs in UTF-8 */
1004
1005                 proto_item_append_text(temp_item, ", %10.2f%c" ANGLE "%7.2f" DEGREE,
1006                                                   mag,
1007                                                   V == pi->unit ? 'V' : 'A',
1008                                                   phase *180.0/G_PI);
1009                 #undef ANGLE
1010                 #undef DEGREE
1011         }
1012         return offset;
1013 }
1014
1015 /* used by 'dissect_data_frame()' to dissect the FREQ and DFREQ fields */
1016 static gint dissect_DFREQ(tvbuff_t *tvb, proto_tree *tree, config_block *block, gint offset)
1017 {
1018         if (floating_point == block->format_fr) {
1019                 gfloat tmp;
1020
1021                 tmp = tvb_get_ntohieee_float(tvb, offset);
1022                 proto_tree_add_text(tree, tvb, offset, 4, "Actual frequency value: %fHz", tmp); offset += 4;
1023
1024                 /* The standard doesn't clearly say how to interpret this value, but
1025                  * http://www.pes-psrc.org/h/C37_118_H11_FAQ_Jan2008.pdf provides further information.
1026                  * --> no scaling factor is applied to DFREQ
1027                  */
1028                 tmp = tvb_get_ntohieee_float(tvb, offset);
1029                 proto_tree_add_text(tree, tvb, offset, 4, "Rate of change of frequency: %fHz/s", tmp); offset += 4;
1030         }
1031         else {
1032                 gint16 tmp;
1033
1034                 tmp = tvb_get_ntohs(tvb, offset);
1035                 proto_tree_add_text(tree, tvb, offset, 2,
1036                                     "Frequency deviation from nominal: %" G_GINT16_FORMAT "mHz (actual frequency: %.3fHz)",
1037                                     tmp, block->fnom + (tmp / 1000.0));
1038                 offset += 2;
1039
1040                 tmp = tvb_get_ntohs(tvb, offset);
1041                 proto_tree_add_text(tree, tvb, offset, 2, "Rate of change of frequency: %.3fHz/s", tmp / 100.0); offset += 2;
1042         }
1043         return offset;
1044 }
1045
1046 /* used by 'dissect_data_frame()' to dissect the ANALOG field */
1047 static gint dissect_ANALOG(tvbuff_t *tvb, proto_tree *tree, config_block *block, gint offset)
1048 {
1049         proto_tree *analog_tree = NULL;
1050         proto_item *temp_item   = NULL;
1051         guint       length;
1052         gint        j,
1053                     cnt = block->analogs->len; /* number of analog values to dissect */
1054
1055         if (0 == cnt)
1056                 return offset;
1057
1058         length      = block->analogs->len * (floating_point == block->format_an ? 4 : 2);
1059         temp_item   = proto_tree_add_text(tree, tvb, offset, length, "Analog values (%u)", cnt);
1060
1061         analog_tree = proto_item_add_subtree(temp_item, ett_data_analog);
1062
1063         for (j = 0; j < cnt; j++) {
1064                 analog_info *ai = &g_array_index(block->analogs, analog_info, j);
1065
1066                 temp_item = proto_tree_add_text(analog_tree, tvb, offset,
1067                                                 floating_point == block->format_an ? 4 : 2,
1068                                                 "Analog value #%u: \"%s\"", j + 1, ai->name);
1069
1070                 if (floating_point == block->format_an) {
1071                         gfloat tmp = tvb_get_ntohieee_float(tvb, offset); offset += 4;
1072                         proto_item_append_text(temp_item, ", %.3f", tmp);
1073                 }
1074                 else {
1075                         /* the "standard" doesn't say if this is signed or unsigned,
1076                          * so I just use gint16, the scaling of the conversation factor
1077                          * is also "user defined", so I just write it after the analog value */
1078                         gint16 tmp = tvb_get_ntohs(tvb, offset); offset += 2;
1079                         proto_item_append_text(temp_item, ", %" G_GINT16_FORMAT " (conversation factor: %#06x)",
1080                                                tmp, ai->conv);
1081                 }
1082         }
1083         return offset;
1084 }
1085
1086 /* used by 'dissect_data_frame()' to dissect the DIGITAL field */
1087 static gint dissect_DIGITAL(tvbuff_t *tvb, proto_tree *tree, config_block *block, gint offset)
1088 {
1089         proto_item *digital_item = NULL;
1090         gint        j,
1091                     cnt = block->num_dg; /* number of digital status words to dissect */
1092
1093         if (0 == cnt)
1094                 return offset;
1095
1096         digital_item = proto_tree_add_text(tree, tvb, offset, cnt * 2, "Digital status words (%u)", cnt);
1097         tree         = proto_item_add_subtree(digital_item, ett_data_digital);
1098
1099         for (j = 0; j < cnt; j++) {
1100                 guint16 tmp = tvb_get_ntohs(tvb, offset); 
1101                 proto_tree_add_text(tree, tvb, offset, 2, "Digital status word #%u: 0x%04x", j + 1, tmp);
1102                 offset += 2;
1103         }
1104         return offset;
1105 }
1106
1107 /*******************************************************************/
1108 /* after this line:  helper functions for 'dissect_config_frame()' */
1109 /*******************************************************************/
1110
1111 /* used by 'dissect_config_frame()' to dissect the PHUNIT field */
1112 static gint dissect_PHUNIT(tvbuff_t *tvb, proto_tree *tree, gint offset, gint cnt)
1113 {
1114         proto_item *temp_item = NULL;
1115         proto_tree *temp_tree = NULL;
1116         int i;
1117
1118         if (0 == cnt)
1119                 return offset;
1120
1121         temp_item = proto_tree_add_text(tree, tvb, offset, 4 * cnt, "Phasor conversation factors (%u)", cnt);
1122         temp_tree = proto_item_add_subtree(temp_item, ett_conf_phconv);
1123
1124         /* Conversion factor for phasor channels. Four bytes for each phasor.
1125          * MSB:           0 = voltage, 1 = current
1126          * Lower 3 Bytes: unsigned 24-bit word in 10^-5 V or A per bit to scale the phasor value
1127          */
1128         for (i = 0; i < cnt; i++) {
1129                 guint32 tmp = tvb_get_ntohl(tvb, offset);
1130                 proto_tree_add_text(temp_tree, tvb, offset, 4,
1131                                     "#%u factor: %u * 10^-5, unit: %s",
1132                                     i + 1,
1133                                     tmp & 0x00FFFFFF,
1134                                     tmp & 0xFF000000 ? "Ampere" : "Volt");
1135                 offset += 4;
1136         }
1137
1138         return offset;
1139 }
1140
1141 /* used by 'dissect_config_frame()' to dissect the ANUNIT field */
1142 static gint dissect_ANUNIT(tvbuff_t *tvb, proto_tree *tree, gint offset, gint cnt)
1143 {
1144         proto_item *temp_item = NULL;
1145         proto_tree *temp_tree = NULL;
1146         int i;
1147
1148         if (0 == cnt)
1149                 return offset;
1150
1151         temp_item = proto_tree_add_text(tree, tvb, offset, 4 * cnt, "Analog values conversation factors (%u)", cnt);
1152         temp_tree = proto_item_add_subtree(temp_item, ett_conf_anconv);
1153
1154         /* Conversation factor for analog channels. Four bytes for each analog value.
1155          * MSB: see 'synphasor_conf_anconvnames' in 'synphasor_strings.c'
1156          * Lower 3 Bytes: signed 24-bit word, user-defined scaling
1157          */
1158         for (i = 0; i < cnt; i++) {
1159                 gint32 tmp = tvb_get_ntohl(tvb, offset);
1160                 temp_item = proto_tree_add_text(temp_tree, tvb, offset, 4,
1161                                                 "Factor for analog value #%i: %s",
1162                                                 i + 1,
1163                                                 match_strrval((tmp >> 24) & 0x000000FF, conf_anconvnames));
1164
1165                         tmp &= 0x00FFFFFF;
1166                 if (    tmp &  0x00800000) /* sign bit set */
1167                         tmp |= 0xFF000000;
1168
1169                 proto_item_append_text(temp_item, ", value: %" G_GINT32_FORMAT, tmp);
1170
1171                 offset += 4;
1172         }
1173
1174         return offset;
1175 }
1176
1177 /* used by 'dissect_config_frame()' to dissect the DIGUNIT field */
1178 static gint dissect_DIGUNIT(tvbuff_t *tvb, proto_tree *tree, gint offset, gint cnt)
1179 {
1180         proto_item *temp_item = NULL;
1181         proto_tree *temp_tree = NULL;
1182         int i;
1183
1184         if (0 == cnt)
1185                 return offset;
1186
1187         temp_item = proto_tree_add_text(tree, tvb, offset, 4 * cnt, "Masks for digital status words (%u)", cnt);
1188         temp_tree = proto_item_add_subtree(temp_item, ett_conf_dgmask);
1189
1190         /* Mask words for digital status words. Two 16-bit words for each digital word. The first
1191          * inidcates the normal status of the inputs, the second indicated the valid bits in
1192          * the status word
1193          */
1194         for (i = 0; i < cnt; i++) {
1195                 guint32 tmp = tvb_get_ntohl(tvb, offset);
1196
1197                 temp_item = proto_tree_add_text(temp_tree, tvb, offset, 4, "Mask for status word #%u: ", i + 1);
1198                 proto_item_append_text(temp_item, "normal state: 0x%04"G_GINT16_MODIFIER"x", (guint16)((tmp & 0xFFFF0000) >> 16));
1199                 proto_item_append_text(temp_item, ", valid bits: 0x%04"G_GINT16_MODIFIER"x", (guint16)( tmp & 0x0000FFFF));
1200
1201                 offset += 4;
1202         }
1203
1204         return offset;
1205 }
1206
1207 /* used by 'dissect_config_frame()' to dissect the "channel name"-fields */
1208 static gint dissect_CHNAM(tvbuff_t *tvb, proto_tree *tree, gint offset, gint cnt, char *prefix)
1209 {
1210         proto_item *temp_item = NULL;
1211         proto_tree *temp_tree = NULL;
1212         int i;
1213
1214         if (0 == cnt)
1215                 return offset;
1216
1217         temp_item = proto_tree_add_text(tree, tvb, offset, CHNAM_LEN * cnt, "%ss (%u)", prefix, cnt);
1218         temp_tree = proto_item_add_subtree(temp_item, ett_conf_phnam);
1219
1220         /* dissect the 'cnt' channel names */
1221         for (i = 0; i < cnt; i++) {
1222                 char *str;
1223                 str = tvb_get_ephemeral_string(tvb, offset, CHNAM_LEN);
1224                 proto_tree_add_text(temp_tree, tvb, offset, CHNAM_LEN,
1225                                     "%s #%i: \"%s\"", prefix, i+1, str);
1226                 offset += CHNAM_LEN;
1227         }
1228
1229         return offset;
1230 }
1231
1232 void proto_register_synphasor(void)
1233 {
1234         static hf_register_info hf[] = {
1235                 /* Sync word */
1236                 { &hf_sync,
1237                 { "Synchronization word", PROTOCOL_ABBREV ".sync", FT_UINT16, BASE_HEX,
1238                   NULL, 0x0, NULL, HFILL }},
1239                         /* Flags in the Sync word */
1240                         { &hf_sync_frtype,
1241                         { "Frame Type", PROTOCOL_ABBREV ".frtype", FT_UINT16, BASE_HEX,
1242                           VALS(typenames), 0x0070, NULL, HFILL }},
1243
1244                         { &hf_sync_version,
1245                         { "Version",    PROTOCOL_ABBREV ".version", FT_UINT16, BASE_DEC,
1246                           VALS(versionnames), 0x000F, NULL, HFILL }},
1247
1248                 { &hf_frsize,
1249                 { "Framesize", PROTOCOL_ABBREV ".frsize", FT_UINT16, BASE_DEC,
1250                   NULL, 0x0, NULL, HFILL }},
1251
1252                 { &hf_idcode,
1253                 { "PMU/DC ID number", PROTOCOL_ABBREV ".idcode", FT_UINT16, BASE_DEC,
1254                   NULL, 0x0, NULL, HFILL }},
1255
1256                 { &hf_soc,
1257                 { "SOC time stamp (UTC)", PROTOCOL_ABBREV ".soc", FT_STRINGZ, BASE_NONE,
1258                   NULL, 0x0, NULL, HFILL }},
1259
1260                 /* Time quality flags in fracsec */
1261                 { &hf_timeqal_lsdir,
1262                 { "Leap second direction", PROTOCOL_ABBREV ".timeqal.lsdir", FT_BOOLEAN, 8,
1263                   NULL, 0x40, NULL, HFILL }},
1264
1265                 { &hf_timeqal_lsocc,
1266                 { "Leap second occurred", PROTOCOL_ABBREV ".timeqal.lsocc", FT_BOOLEAN, 8,
1267                   NULL, 0x20, NULL, HFILL }},
1268
1269                 { &hf_timeqal_lspend,
1270                 { "Leap second pending", PROTOCOL_ABBREV ".timeqal.lspend", FT_BOOLEAN, 8,
1271                   NULL, 0x10, NULL, HFILL }},
1272
1273                 { &hf_timeqal_timequalindic,
1274                 { "Time Quality indicator code", PROTOCOL_ABBREV ".timeqal.timequalindic", FT_UINT8, BASE_HEX,
1275                   VALS(timequalcodes), 0x0F, NULL, HFILL }},
1276
1277                 /* Fraction of second */
1278                 { &hf_fracsec,
1279                 { "Fraction of second (raw)", PROTOCOL_ABBREV ".fracsec", FT_UINT24, BASE_DEC, 
1280                   NULL, 0x0, NULL, HFILL }},
1281
1282         /* Data types for configuration frames */
1283                 { &hf_conf_timebase,
1284                 { "Resolution of fractional second time stamp", PROTOCOL_ABBREV ".conf.timebase", FT_UINT24, BASE_DEC,
1285                   NULL, 0x0, NULL, HFILL }},
1286
1287                 { &hf_conf_numpmu,
1288                 { "Number of PMU blocks included in the frame", PROTOCOL_ABBREV ".conf.numpmu", FT_UINT16, BASE_DEC,
1289                   NULL, 0x0, NULL, HFILL }},
1290
1291                 /* Bits in the FORMAT word */
1292                 { &hf_conf_formatb3,
1293                 { "FREQ/DFREQ format", PROTOCOL_ABBREV ".conf.dfreq_format", FT_BOOLEAN, 16,
1294                   TFS(&conf_formatb123names), 0x8, NULL, HFILL }},
1295                 
1296                 { &hf_conf_formatb2,
1297                 { "Analog values format", PROTOCOL_ABBREV ".conf.analog_format", FT_BOOLEAN, 16,
1298                   TFS(&conf_formatb123names), 0x4, NULL, HFILL }},
1299
1300                 { &hf_conf_formatb1,
1301                 { "Phasor format", PROTOCOL_ABBREV ".conf.phasor_format", FT_BOOLEAN, 16,
1302                   TFS(&conf_formatb123names), 0x2, NULL, HFILL }},
1303
1304                 { &hf_conf_formatb0,
1305                 { "Phasor notation", PROTOCOL_ABBREV ".conf.phasor_notation", FT_BOOLEAN, 16,
1306                   TFS(&conf_formatb0names), 0x1, NULL, HFILL }},
1307
1308                 { &hf_conf_fnom,
1309                 { "Nominal line freqency", PROTOCOL_ABBREV ".conf.fnom", FT_BOOLEAN, 16,
1310                   TFS(&conf_fnomnames), 0x0001, NULL, HFILL }},
1311
1312                 { &hf_conf_cfgcnt,
1313                 { "Configuration change count", PROTOCOL_ABBREV ".conf.cfgcnt", FT_UINT16, BASE_DEC,
1314                   NULL, 0, NULL, HFILL }},
1315
1316         /* Data types for data frames */
1317                 /* Flags in the STAT word */
1318                 { &hf_data_statb15,
1319                 { "Data valid", PROTOCOL_ABBREV ".data.valid", FT_BOOLEAN, 16,
1320                   TFS(&data_statb15names), 0x8000, NULL, HFILL }},
1321
1322                 { &hf_data_statb14,
1323                 { "PMU error", PROTOCOL_ABBREV ".data.PMUerror", FT_BOOLEAN, 16,
1324                   TFS(&data_statb14names), 0x4000, NULL, HFILL }},
1325
1326                 { &hf_data_statb13,
1327                 { "Time syncronized", PROTOCOL_ABBREV ".data.sync", FT_BOOLEAN, 16,
1328                   TFS(&data_statb13names), 0x2000, NULL, HFILL }},
1329
1330                 { &hf_data_statb12,
1331                 { "Data sorting", PROTOCOL_ABBREV ".data.sorting", FT_BOOLEAN, 16,
1332                   TFS(&data_statb12names), 0x1000, NULL, HFILL }},
1333
1334                 { &hf_data_statb11,
1335                 { "Trigger detected", PROTOCOL_ABBREV ".data.trigger", FT_BOOLEAN, 16,
1336                   TFS(&data_statb11names), 0x0800, NULL, HFILL }},
1337
1338                 { &hf_data_statb10,
1339                 { "Configuration changed", PROTOCOL_ABBREV ".data.CFGchange", FT_BOOLEAN, 16,
1340                   TFS(&data_statb10names), 0x0400, NULL, HFILL }},
1341
1342                 { &hf_data_statb05to04,
1343                 { "Unlocked time", PROTOCOL_ABBREV  ".data.t_unlock", FT_UINT16, BASE_HEX,
1344                   VALS(&data_statb05to04names), 0x0030, NULL, HFILL }},
1345
1346                 { &hf_data_statb03to00,
1347                 { "Trigger reason", PROTOCOL_ABBREV  ".data.trigger_reason", FT_UINT16, BASE_HEX,
1348                   VALS(&data_statb03to00names), 0x000F, NULL, HFILL }},
1349
1350         /* Data type for command frame */
1351                 { &hf_command,
1352                 { "Command", PROTOCOL_ABBREV ".command", FT_UINT16, BASE_HEX,
1353                   VALS(&command_names), 0x000F, NULL, HFILL }}
1354         };
1355
1356         /* protocol subtree array */
1357         static gint *ett[] = {
1358                 &ett_synphasor,
1359                 &ett_frtype,
1360                 &ett_timequal,
1361                 &ett_conf,
1362                 &ett_conf_station,
1363                 &ett_conf_format,
1364                 &ett_conf_phnam,
1365                 &ett_conf_annam,
1366                 &ett_conf_dgnam,
1367                 &ett_conf_phconv,
1368                 &ett_conf_anconv,
1369                 &ett_conf_dgmask,
1370                 &ett_data,
1371                 &ett_data_block,
1372                 &ett_data_stat,
1373                 &ett_data_phasors,
1374                 &ett_data_analog,
1375                 &ett_data_digital,
1376                 &ett_command
1377         };
1378
1379         module_t *synphasor_module;
1380
1381         /* register protocol */
1382         proto_synphasor = proto_register_protocol(PROTOCOL_NAME,
1383                                                   PROTOCOL_SHORT_NAME,
1384                                                   PROTOCOL_ABBREV);
1385
1386         proto_register_field_array(proto_synphasor, hf, array_length(hf));
1387         proto_register_subtree_array(ett, array_length(ett));
1388
1389         /* register preferences */
1390         synphasor_module = prefs_register_protocol(proto_synphasor, proto_reg_handoff_synphasor);
1391
1392         /* the port numbers of the lower level protocols */
1393         prefs_register_uint_preference(synphasor_module, "udp_port", "Synchrophasor UDP port",
1394                                        "Set the port number for synchrophasor frames over UDP" \
1395                                        "(if other than the default of 4713)",
1396                                        10, &global_pref_udp_port);
1397         prefs_register_uint_preference(synphasor_module, "tcp_port", "Synchrophasor TCP port",
1398                                        "Set the port number for synchrophasor frames over TCP" \
1399                                        "(if other than the default of 4712)",
1400                                        10, &global_pref_tcp_port);
1401
1402         /* register the initalization routine */
1403         register_init_routine(&synphasor_init);
1404 } /* proto_register_synphasor() */
1405
1406 /* called at startup and when the preferences change */
1407 void proto_reg_handoff_synphasor(void)
1408 {
1409         static gboolean           initialized = FALSE;
1410         static dissector_handle_t synphasor_udp_handle;
1411         static dissector_handle_t synphasor_tcp_handle;
1412         static guint              current_udp_port;
1413         static guint              current_tcp_port;
1414
1415         if (!initialized) {
1416                 synphasor_udp_handle = create_dissector_handle(dissect_udp, proto_synphasor);
1417                 synphasor_tcp_handle = create_dissector_handle(dissect_tcp, proto_synphasor);
1418
1419                 initialized = TRUE;
1420         }
1421         else {
1422                 /* update preferences */
1423                 dissector_delete("udp.port", current_udp_port, synphasor_udp_handle);
1424                 dissector_delete("tcp.port", current_tcp_port, synphasor_tcp_handle);
1425         }
1426
1427         current_udp_port = global_pref_udp_port;
1428         current_tcp_port = global_pref_tcp_port;
1429
1430         dissector_add("udp.port", current_udp_port, synphasor_udp_handle);
1431         dissector_add("tcp.port", current_tcp_port, synphasor_tcp_handle);
1432 } /* proto_reg_handoff_synphasor() */
1433