Coverage Report

Created: 2026-07-12 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/epan/dissectors/packet-xgt.c
Line
Count
Source
1
/* packet-xgt.c
2
 * Routines for XGT (LS ELECTRIC PLC) protocol packet disassembly
3
 *
4
 * Copyright 2025, Gihyeon Ryu and the SLiMe team (BoB 14th)
5
 *
6
 * XGT is a proprietary protocol used by LS ELECTRIC (formerly LS Industrial Systems)
7
 * for communication with their XGT series PLCs over Ethernet.
8
 *
9
 * Protocol specifications based on:
10
 * "XGT FEnet I/F Module Protocol Specification" (2005.03.30)
11
 *
12
 * Wireshark - Network traffic analyzer
13
 * By Gerald Combs <gerald@wireshark.org>
14
 * Copyright 1998 Gerald Combs
15
 *
16
 * SPDX-License-Identifier: GPL-2.0-or-later
17
 */
18
19
#include "config.h"
20
#include <stdbool.h>
21
#include <epan/packet.h>
22
#include <epan/prefs.h>
23
#include <epan/wmem_scopes.h>
24
#include <epan/tfs.h>
25
#include <epan/expert.h>
26
#include <epan/conversation.h>
27
#include "packet-tcp.h"
28
#include <ws_version.h>
29
30
/* XGT Protocol Ports */
31
15
#define XGT_TCP_PORT 2004
32
15
#define XGT_UDP_PORT 2005
33
34
/* Company ID */
35
1
#define XGT_COMPANY_ID "LSIS-XGT"
36
#define XGT_COMPANY_ID_LEN 8
37
38
/* Header sizes */
39
2
#define XGT_HEADER_LEN 20
40
41
/* Source of Frame values */
42
0
#define XGT_SOURCE_CLIENT 0x33
43
0
#define XGT_SOURCE_SERVER 0x11
44
45
/* CPU Info */
46
#define XGT_CPU_INFO_XGK 0xA0
47
#define XGT_CPU_INFO_XGI 0xA4
48
#define XGT_CPU_INFO_XGR 0xA8
49
50
/* Command codes */
51
0
#define XGT_CMD_READ_REQUEST    0x0054
52
#define XGT_CMD_READ_RESPONSE   0x0055
53
0
#define XGT_CMD_WRITE_REQUEST   0x0058
54
0
#define XGT_CMD_WRITE_RESPONSE  0x0059
55
0
#define XGT_CMD_STATUS_REQUEST  0x00B0
56
0
#define XGT_CMD_STATUS_RESPONSE 0x00B1
57
58
/* Data types */
59
0
#define XGT_DTYPE_BIT    0x00
60
0
#define XGT_DTYPE_BYTE   0x01
61
0
#define XGT_DTYPE_WORD   0x02
62
0
#define XGT_DTYPE_DWORD  0x03
63
0
#define XGT_DTYPE_LWORD  0x04
64
0
#define XGT_DTYPE_CONTINUOUS 0x14
65
66
/* Error status */
67
0
#define XGT_ERROR_NONE   0x0000
68
#define XGT_ERROR_EXISTS 0xFFFF
69
70
/* CPU Types */
71
#define XGT_CPU_TYPE_XGK_CPUH 0x01
72
#define XGT_CPU_TYPE_XGK_CPUS 0x02
73
#define XGT_CPU_TYPE_XGI_CPUU 0x05
74
75
/* System States */
76
#define XGT_SYS_STATE_RUN   0x01
77
#define XGT_SYS_STATE_STOP  0x02
78
#define XGT_SYS_STATE_ERROR 0x04
79
#define XGT_SYS_STATE_DEBUG 0x08
80
81
void proto_register_xgt(void);
82
void proto_reg_handoff_xgt(void);
83
84
/* Protocol handles */
85
static int proto_xgt;
86
87
/* Conversation data structure for request-response matching */
88
typedef struct _xgt_conv_info_t {
89
    wmem_map_t *invoke_id_map;  /* Map: invoke_id -> variable_name */
90
} xgt_conv_info_t;
91
92
/* Header fields */
93
static int hf_xgt_company_id;
94
static int hf_xgt_reserved1;
95
static int hf_xgt_plc_info;
96
static int hf_xgt_plc_info_cpu_type;
97
static int hf_xgt_plc_info_redundancy;
98
static int hf_xgt_plc_info_cpu_error;
99
static int hf_xgt_plc_info_sys_state;
100
static int hf_xgt_cpu_info;
101
static int hf_xgt_source;
102
static int hf_xgt_invoke_id;
103
static int hf_xgt_length;
104
static int hf_xgt_fenet_position;
105
static int hf_xgt_fenet_slot;
106
static int hf_xgt_fenet_base;
107
static int hf_xgt_reserved2;
108
109
/* Instruction fields */
110
static int hf_xgt_command;
111
static int hf_xgt_data_type;
112
static int hf_xgt_reserved_area;
113
static int hf_xgt_error_status;
114
static int hf_xgt_error_code;
115
static int hf_xgt_block_count;
116
static int hf_xgt_variable_count;
117
static int hf_xgt_variable_length;
118
static int hf_xgt_variable_name;
119
static int hf_xgt_data_length;
120
static int hf_xgt_data;
121
static int hf_xgt_data_value_uint8;
122
static int hf_xgt_data_value_uint16;
123
static int hf_xgt_data_value_uint32;
124
static int hf_xgt_data_value_uint64;
125
static int hf_xgt_byte_count;
126
static int hf_xgt_word;
127
128
/* Status fields */
129
static int hf_xgt_status_data;
130
static int hf_xgt_slot_info;
131
static int hf_xgt_cpu_type;
132
static int hf_xgt_ver_num;
133
static int hf_xgt_sys_state;
134
static int hf_xgt_padt_cnf;
135
static int hf_xgt_cnf_er;
136
static int hf_xgt_cnf_war;
137
138
/* Subtree indices */
139
static int ett_xgt;
140
static int ett_xgt_header;
141
static int ett_xgt_instruction;
142
static int ett_xgt_plc_info;
143
static int ett_xgt_fenet_position;
144
static int ett_xgt_block;
145
static int ett_xgt_status;
146
147
/* Expert info fields */
148
static expert_field ei_xgt_invalid_length = EI_INIT;
149
static expert_field ei_xgt_error_response = EI_INIT;
150
static expert_field ei_xgt_truncated_data = EI_INIT;
151
static expert_field ei_xgt_invalid_command = EI_INIT;
152
static expert_field ei_xgt_cpu_error = EI_INIT;
153
static expert_field ei_xgt_suspicious_count = EI_INIT;
154
155
/* Value strings */
156
static const value_string xgt_command_vals[] = {
157
    { XGT_CMD_READ_REQUEST,    "Read Request" },
158
    { XGT_CMD_READ_RESPONSE,   "Read Response" },
159
    { XGT_CMD_WRITE_REQUEST,   "Write Request" },
160
    { XGT_CMD_WRITE_RESPONSE,  "Write Response" },
161
    { XGT_CMD_STATUS_REQUEST,  "Status Request" },
162
    { XGT_CMD_STATUS_RESPONSE, "Status Response" },
163
    { 0, NULL }
164
};
165
166
static const value_string xgt_data_type_vals[] = {
167
    { XGT_DTYPE_BIT,    "BIT" },
168
    { XGT_DTYPE_BYTE,   "BYTE" },
169
    { XGT_DTYPE_WORD,   "WORD" },
170
    { XGT_DTYPE_DWORD,  "DWORD" },
171
    { XGT_DTYPE_LWORD,  "LWORD" },
172
    { XGT_DTYPE_CONTINUOUS, "Continuous Block" },
173
    { 0, NULL }
174
};
175
176
static const value_string xgt_source_vals[] = {
177
    { XGT_SOURCE_CLIENT, "Client (HMI -> PLC)" },
178
    { XGT_SOURCE_SERVER, "Server (PLC -> HMI)" },
179
    { 0, NULL }
180
};
181
182
static const value_string xgt_cpu_info_vals[] = {
183
    { XGT_CPU_INFO_XGK, "XGK CPU" },
184
    { XGT_CPU_INFO_XGI, "XGI CPU" },
185
    { XGT_CPU_INFO_XGR, "XGR CPU" },
186
    { 0, NULL }
187
};
188
189
static const value_string xgt_cpu_type_vals[] = {
190
    { XGT_CPU_TYPE_XGK_CPUH, "XGK-CPUH" },
191
    { XGT_CPU_TYPE_XGK_CPUS, "XGK-CPUS" },
192
    { XGT_CPU_TYPE_XGI_CPUU, "XGI-CPUU" },
193
    { 0, NULL }
194
};
195
196
static const value_string xgt_sys_state_vals[] = {
197
    { XGT_SYS_STATE_RUN,   "RUN" },
198
    { XGT_SYS_STATE_STOP,  "STOP" },
199
    { XGT_SYS_STATE_ERROR, "ERROR" },
200
    { XGT_SYS_STATE_DEBUG, "DEBUG" },
201
    { 0, NULL }
202
};
203
204
static const value_string xgt_error_status_vals[] = {
205
    { XGT_ERROR_NONE,   "Success" },
206
    { XGT_ERROR_EXISTS, "Error" },
207
    { 0, NULL }
208
};
209
210
/* XGT Error codes (common error codes from protocol spec) */
211
static const value_string xgt_error_code_vals[] = {
212
    { 0x0000, "No Error" },
213
    { 0x1101, "There is no XGT instruction" },
214
    { 0x1102, "There is no XGT device" },
215
    { 0x1104, "Invalid data size" },
216
    { 0x1105, "Invalid data range" },
217
    { 0x1106, "Data is protected" },
218
    { 0x1107, "Invalid block number" },
219
    { 0x1108, "Variable name error" },
220
    { 0x1109, "Duplicated variable" },
221
    { 0x110A, "Read not allowed" },
222
    { 0x110B, "Write not allowed" },
223
    { 0x1201, "CPU is in STOP mode" },
224
    { 0x1202, "CPU is in RUN mode" },
225
    { 0x1203, "CPU module error" },
226
    { 0x1301, "Password error" },
227
    { 0x1302, "Mode change not allowed" },
228
    { 0x1303, "Communication timeout" },
229
    { 0, NULL }
230
};
231
232
/* True/False strings */
233
static const true_false_string tfs_slave_master = {
234
    "Slave",
235
    "Master"
236
};
237
238
static const true_false_string tfs_error_normal = {
239
    "Error",
240
    "Normal"
241
};
242
243
/* Dissect XGT Application Header */
244
static int
245
dissect_xgt_header(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, unsigned offset)
246
0
{
247
0
    proto_item *ti, *plc_info_item;
248
0
    proto_tree *header_tree, *plc_info_tree, *fenet_tree;
249
0
    unsigned source;
250
0
    unsigned fenet_pos;
251
0
    unsigned plc_info, data_length;
252
253
    /* Bounds check - ensure we have enough data for the header */
254
0
    if (tvb_reported_length_remaining(tvb, offset) < XGT_HEADER_LEN) {
255
0
        expert_add_info(pinfo, tree, &ei_xgt_truncated_data);
256
0
        return tvb_captured_length(tvb);
257
0
    }
258
259
0
    ti = proto_tree_add_item(tree, proto_xgt, tvb, offset, XGT_HEADER_LEN, ENC_NA);
260
0
    header_tree = proto_item_add_subtree(ti, ett_xgt_header);
261
262
    /* Company ID */
263
0
    proto_tree_add_item(header_tree, hf_xgt_company_id, tvb, offset, 8, ENC_ASCII);
264
0
    offset += 8;
265
266
    /* Reserved */
267
0
    proto_tree_add_item(header_tree, hf_xgt_reserved1, tvb, offset, 2, ENC_LITTLE_ENDIAN);
268
0
    offset += 2;
269
270
    /* PLC Info */
271
0
    plc_info_item = proto_tree_add_item_ret_uint(header_tree, hf_xgt_plc_info, tvb, offset, 2, ENC_LITTLE_ENDIAN, &plc_info);
272
0
    plc_info_tree = proto_item_add_subtree(plc_info_item, ett_xgt_plc_info);
273
274
0
    proto_tree_add_item(plc_info_tree, hf_xgt_plc_info_cpu_type, tvb, offset, 2, ENC_LITTLE_ENDIAN);
275
0
    proto_tree_add_item(plc_info_tree, hf_xgt_plc_info_redundancy, tvb, offset, 2, ENC_LITTLE_ENDIAN);
276
0
    proto_tree_add_item(plc_info_tree, hf_xgt_plc_info_cpu_error, tvb, offset, 2, ENC_LITTLE_ENDIAN);
277
0
    proto_tree_add_item(plc_info_tree, hf_xgt_plc_info_sys_state, tvb, offset, 2, ENC_LITTLE_ENDIAN);
278
279
    /* Check for CPU error and add expert info */
280
0
    if (plc_info & 0x0080) {
281
0
        expert_add_info(pinfo, plc_info_item, &ei_xgt_cpu_error);
282
0
    }
283
0
    offset += 2;
284
285
    /* CPU Info */
286
0
    proto_tree_add_item(header_tree, hf_xgt_cpu_info, tvb, offset, 1, ENC_LITTLE_ENDIAN);
287
0
    offset += 1;
288
289
    /* Source of Frame */
290
0
    proto_tree_add_item_ret_uint(header_tree, hf_xgt_source, tvb, offset, 1, ENC_LITTLE_ENDIAN, &source);
291
0
    offset += 1;
292
293
    /* Update info column based on source */
294
0
    if (source == XGT_SOURCE_CLIENT) {
295
0
        col_set_str(pinfo->cinfo, COL_INFO, "Request: ");
296
0
    } else if (source == XGT_SOURCE_SERVER) {
297
0
        col_set_str(pinfo->cinfo, COL_INFO, "Response: ");
298
0
    }
299
300
    /* Invoke ID */
301
0
    unsigned invoke_id;
302
0
    proto_tree_add_item_ret_uint(header_tree, hf_xgt_invoke_id, tvb, offset, 2, ENC_LITTLE_ENDIAN, &invoke_id);
303
0
    col_append_fstr(pinfo->cinfo, COL_INFO, "[ID:%u] ", invoke_id);
304
0
    offset += 2;
305
306
    /* Length - validate against remaining data */
307
0
    ti = proto_tree_add_item_ret_uint(header_tree, hf_xgt_length, tvb, offset, 2, ENC_LITTLE_ENDIAN, &data_length);
308
0
    if (data_length > (unsigned)tvb_reported_length_remaining(tvb, offset + 4)) {
309
0
        expert_add_info(pinfo, ti, &ei_xgt_invalid_length);
310
0
    }
311
0
    offset += 2;
312
313
    /* FEnet Position */
314
0
    ti = proto_tree_add_item_ret_uint(header_tree, hf_xgt_fenet_position, tvb, offset, 1, ENC_LITTLE_ENDIAN, &fenet_pos);
315
0
    fenet_tree = proto_item_add_subtree(ti, ett_xgt_fenet_position);
316
317
0
    proto_tree_add_uint(fenet_tree, hf_xgt_fenet_slot, tvb, offset, 1, fenet_pos & 0x0F);
318
0
    proto_tree_add_uint(fenet_tree, hf_xgt_fenet_base, tvb, offset, 1, (fenet_pos >> 4) & 0x0F);
319
0
    offset += 1;
320
321
    /* Reserved2 / BCC */
322
0
    proto_tree_add_item(header_tree, hf_xgt_reserved2, tvb, offset, 1, ENC_LITTLE_ENDIAN);
323
0
    offset += 1;
324
325
0
    return offset;
326
0
}
327
328
/* Helper function to translate XGT variable name to word address */
329
static char*
330
translate_xgt_address(wmem_allocator_t *scope, const char *var_name)
331
0
{
332
0
    if (!var_name || var_name[0] != '%') {
333
0
        return wmem_strdup(scope, var_name);
334
0
    }
335
336
    /* Skip % and extract memory type and address */
337
0
    const char *addr_part = var_name + 1;  /* Remove % */
338
339
    /* Find where the number starts */
340
0
    unsigned i = 0;
341
0
    while (addr_part[i] && !g_ascii_isdigit(addr_part[i])) {
342
0
        i++;
343
0
    }
344
345
0
    if (i == 0 || !addr_part[i]) {
346
0
        return wmem_strdup(scope, var_name);
347
0
    }
348
349
    /* Extract memory type (first letter only: D, M, P) */
350
0
    char mem_type = addr_part[0];
351
352
    /* Extract byte address and convert to word address */
353
0
    unsigned byte_addr = (unsigned)g_ascii_strtoull(&addr_part[i], NULL, 10);
354
0
    unsigned word_addr = byte_addr / 2;
355
356
0
    return wmem_strdup_printf(scope, "%c%u", mem_type, word_addr);
357
0
}
358
359
/* Dissect individual read/write block */
360
static int
361
dissect_xgt_block(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, unsigned offset,
362
                  bool is_request, bool is_write, unsigned data_type,
363
                  const char *request_var_name)
364
0
{
365
0
    proto_item *ti, *length_item;
366
0
    proto_tree *block_tree;
367
0
    unsigned var_length, data_length;
368
0
    uint8_t *var_name;
369
0
    unsigned start_offset = offset;
370
371
    /* Bounds check - need at least 2 bytes for length field */
372
0
    if (tvb_reported_length_remaining(tvb, offset) < 2) {
373
0
        expert_add_info(pinfo, tree, &ei_xgt_truncated_data);
374
0
        return tvb_captured_length(tvb);
375
0
    }
376
377
0
    block_tree = proto_tree_add_subtree(tree, tvb, offset, 0, ett_xgt_block, &ti, "Variable Block");
378
379
    /* Variable length and name (only in requests or write response) */
380
0
    if (is_request || is_write) {
381
0
        length_item = proto_tree_add_item_ret_uint(block_tree, hf_xgt_variable_length, tvb, offset, 2, ENC_LITTLE_ENDIAN, &var_length);
382
0
        offset += 2;
383
384
        /* Validate variable length */
385
0
        if (var_length > 16) {
386
0
            expert_add_info(pinfo, length_item, &ei_xgt_invalid_length);
387
0
            proto_item_set_len(ti, offset - start_offset);
388
0
            return offset;
389
0
        }
390
391
0
        if (var_length > 0) {
392
            /* Bounds check for variable name */
393
0
            if (tvb_reported_length_remaining(tvb, offset) < var_length) {
394
0
                expert_add_info(pinfo, length_item, &ei_xgt_truncated_data);
395
0
                proto_item_set_len(ti, offset - start_offset);
396
0
                return tvb_captured_length(tvb);
397
0
            }
398
399
0
            var_name = (uint8_t *)tvb_get_string_enc(pinfo->pool, tvb, offset, var_length, ENC_ASCII);
400
0
            proto_tree_add_item(block_tree, hf_xgt_variable_name, tvb, offset, var_length, ENC_ASCII);
401
0
            proto_item_append_text(ti, ": %s", var_name);
402
0
            col_append_fstr(pinfo->cinfo, COL_INFO, " %s", var_name);
403
0
            offset += var_length;
404
0
        }
405
0
    }
406
407
    /* Data length and data (in write requests and all responses) */
408
0
    if (is_write || !is_request) {
409
        /* Bounds check for data length field */
410
0
        if (tvb_reported_length_remaining(tvb, offset) < 2) {
411
0
            expert_add_info(pinfo, ti, &ei_xgt_truncated_data);
412
0
            proto_item_set_len(ti, offset - start_offset);
413
0
            return tvb_captured_length(tvb);
414
0
        }
415
416
0
        length_item = proto_tree_add_item_ret_uint(block_tree, hf_xgt_data_length, tvb, offset, 2, ENC_LITTLE_ENDIAN, &data_length);
417
0
        offset += 2;
418
419
        /* Validate data length - reasonable upper limit */
420
0
        if (data_length > 8192) {
421
0
            expert_add_info(pinfo, length_item, &ei_xgt_invalid_length);
422
0
        }
423
424
0
        if (data_length > 0) {
425
            /* Bounds check for data */
426
0
            if (tvb_reported_length_remaining(tvb, offset) < data_length) {
427
0
                expert_add_info(pinfo, length_item, &ei_xgt_truncated_data);
428
0
                proto_item_set_len(ti, offset - start_offset);
429
0
                return tvb_captured_length(tvb);
430
0
            }
431
432
            /* For Continuous Block type, parse as individual words (similar to Modbus registers) */
433
0
            if (data_type == XGT_DTYPE_CONTINUOUS && data_length >= 2 && !is_request) {
434
                /* Add byte count field */
435
0
                proto_tree_add_uint(block_tree, hf_xgt_byte_count, tvb, offset, data_length, data_length);
436
437
                /* Parse data as 16-bit words */
438
0
                unsigned num_words = data_length / 2;
439
0
                unsigned i;
440
0
                unsigned word_offset = offset;
441
442
                /* Get translated base address from request variable name */
443
0
                char *base_addr_str = NULL;
444
0
                unsigned base_addr_num = 0;
445
0
                char mem_type = '\0';
446
447
0
                if (request_var_name) {
448
0
                    base_addr_str = translate_xgt_address(pinfo->pool, request_var_name);
449
450
                    /* Extract memory type and base address number */
451
0
                    if (base_addr_str && base_addr_str[0] && g_ascii_isalpha(base_addr_str[0])) {
452
0
                        mem_type = base_addr_str[0];
453
0
                        base_addr_num = (unsigned)g_ascii_strtoull(&base_addr_str[1], NULL, 10);
454
0
                    }
455
0
                }
456
457
0
                for (i = 0; i < num_words; i++) {
458
0
                    if (tvb_reported_length_remaining(tvb, word_offset) >= 2) {
459
0
                        uint16_t word_value = tvb_get_letohs(tvb, word_offset);
460
461
                        /* Display with translated address if available */
462
0
                        if (mem_type) {
463
0
                            char *addr_label = wmem_strdup_printf(pinfo->pool, "%c%u", mem_type, base_addr_num + i);
464
0
                            proto_tree_add_uint_format(block_tree, hf_xgt_word, tvb, word_offset, 2,
465
0
                                                        word_value, "%s (UINT16): %u", addr_label, word_value);
466
0
                        } else {
467
                            /* Fallback to Word N format */
468
0
                            proto_tree_add_uint_format(block_tree, hf_xgt_word, tvb, word_offset, 2,
469
0
                                                        word_value, "Word %u (UINT16): %u", i, word_value);
470
0
                        }
471
0
                        word_offset += 2;
472
0
                    }
473
0
                }
474
475
                /* Add data value to Info column for responses */
476
0
                col_append_fstr(pinfo->cinfo, COL_INFO, "[%u bytes] = %u...", data_length, tvb_get_letohs(tvb, offset));
477
0
            } else if (data_length == 8 && data_type == XGT_DTYPE_LWORD) {
478
                /* For 64-bit values, show as uint64 using data_value field */
479
0
                uint64_t value = tvb_get_letoh64(tvb, offset);
480
0
                proto_tree_add_uint64_format(block_tree, hf_xgt_data_value_uint64, tvb, offset, data_length, value, "Data: %" PRIu64, value);
481
0
            } else if (data_length == 1 && (data_type == XGT_DTYPE_BIT || data_type == XGT_DTYPE_BYTE)) {
482
0
                uint8_t value = tvb_get_uint8(tvb, offset);
483
0
                proto_tree_add_uint_format(block_tree, hf_xgt_data_value_uint8, tvb, offset, data_length, value, "Data: %u", value);
484
0
            } else if (data_length == 2 && data_type == XGT_DTYPE_WORD) {
485
0
                uint16_t value = tvb_get_letohs(tvb, offset);
486
0
                proto_tree_add_uint_format(block_tree, hf_xgt_data_value_uint16, tvb, offset, data_length, value, "Data: %u", value);
487
0
            } else if (data_length == 4 && data_type == XGT_DTYPE_DWORD) {
488
0
                uint32_t value = tvb_get_letohl(tvb, offset);
489
0
                proto_tree_add_uint_format(block_tree, hf_xgt_data_value_uint32, tvb, offset, data_length, value, "Data: %u", value);
490
0
            } else {
491
                /* For multi-value or large data, show all bytes as hex */
492
0
                proto_tree_add_item(block_tree, hf_xgt_data, tvb, offset, data_length, ENC_NA);
493
0
            }
494
495
            /* Add data value to Info column for responses (for non-continuous types) */
496
0
            if (!is_request && data_type != XGT_DTYPE_CONTINUOUS) {
497
0
                if (data_length >= 2) {
498
                    /* For single values, show the value */
499
0
                    uint16_t value = tvb_get_letohs(tvb, offset);
500
0
                    col_append_fstr(pinfo->cinfo, COL_INFO, "= %u", value);
501
0
                } else {
502
                    /* For single byte (data_length==1) */
503
0
                    uint8_t value = tvb_get_uint8(tvb, offset);
504
0
                    col_append_fstr(pinfo->cinfo, COL_INFO, "= %u", value);
505
0
                }
506
0
            }
507
508
0
            offset += data_length;
509
0
        }
510
0
    }
511
512
0
    proto_item_set_len(ti, offset - start_offset);
513
0
    return offset;
514
0
}
515
516
/* Dissect XGT Instruction (Command and Data) */
517
static int
518
dissect_xgt_instruction(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, unsigned offset, unsigned invoke_id)
519
0
{
520
0
    proto_tree *inst_tree, *status_tree;
521
0
    proto_item *ti, *cmd_item, *count_item, *error_item;
522
0
    unsigned command, error_status, error_code, block_count, data_type;
523
0
    unsigned i;
524
0
    bool is_request, is_write, is_status;
525
0
    const char *cmd_str;
526
0
    xgt_conv_info_t *conv_info = NULL;
527
0
    conversation_t *conversation = NULL;
528
529
    /* Bounds check - need at least 8 bytes (command, data type, reserved, error/count) */
530
0
    if (tvb_reported_length_remaining(tvb, offset) < 8) {
531
0
        expert_add_info(pinfo, tree, &ei_xgt_truncated_data);
532
0
        return tvb_captured_length(tvb);
533
0
    }
534
535
0
    ti = proto_tree_add_item(tree, proto_xgt, tvb, offset, -1, ENC_NA);
536
0
    inst_tree = proto_item_add_subtree(ti, ett_xgt_instruction);
537
538
    /* Command */
539
0
    cmd_item = proto_tree_add_item_ret_uint(inst_tree, hf_xgt_command, tvb, offset, 2, ENC_LITTLE_ENDIAN, &command);
540
0
    cmd_str = val_to_str_const(command, xgt_command_vals, "Unknown");
541
0
    col_append_str(pinfo->cinfo, COL_INFO, cmd_str);
542
543
    /* Warn about unknown commands */
544
0
    if (try_val_to_str(command, xgt_command_vals) == NULL) {
545
0
        expert_add_info(pinfo, cmd_item, &ei_xgt_invalid_command);
546
0
    }
547
0
    offset += 2;
548
549
    /* Determine request/response and command type */
550
0
    is_request = (command == XGT_CMD_READ_REQUEST || command == XGT_CMD_WRITE_REQUEST ||
551
0
                  command == XGT_CMD_STATUS_REQUEST);
552
0
    is_write = (command == XGT_CMD_WRITE_REQUEST || command == XGT_CMD_WRITE_RESPONSE);
553
0
    is_status = (command == XGT_CMD_STATUS_REQUEST || command == XGT_CMD_STATUS_RESPONSE);
554
555
    /* Set up conversation tracking for request-response matching */
556
0
    conversation = find_or_create_conversation(pinfo);
557
0
    conv_info = (xgt_conv_info_t *)conversation_get_proto_data(conversation, proto_xgt);
558
559
0
    if (!conv_info) {
560
0
        conv_info = wmem_new(wmem_file_scope(), xgt_conv_info_t);
561
0
        conv_info->invoke_id_map = wmem_map_new(wmem_file_scope(), g_direct_hash, g_direct_equal);
562
0
        conversation_add_proto_data(conversation, proto_xgt, conv_info);
563
0
    }
564
565
    /* Data Type */
566
0
    proto_tree_add_item_ret_uint(inst_tree, hf_xgt_data_type, tvb, offset, 2, ENC_LITTLE_ENDIAN, &data_type);
567
568
    /* Add data type to Info column */
569
0
    const char *dtype_str = val_to_str_const(data_type, xgt_data_type_vals, "Unknown");
570
0
    col_append_fstr(pinfo->cinfo, COL_INFO, "(%s) ", dtype_str);
571
572
0
    offset += 2;
573
574
    /* Reserved Area */
575
0
    proto_tree_add_item(inst_tree, hf_xgt_reserved_area, tvb, offset, 2, ENC_LITTLE_ENDIAN);
576
0
    offset += 2;
577
578
    /* Error Status (responses only) */
579
0
    if (!is_request) {
580
0
        error_item = proto_tree_add_item_ret_uint(inst_tree, hf_xgt_error_status, tvb, offset, 2, ENC_LITTLE_ENDIAN, &error_status);
581
0
        offset += 2;
582
583
        /* Error Code (if error exists) */
584
0
        if (error_status != XGT_ERROR_NONE) {
585
            /* Bounds check for error code */
586
0
            if (tvb_reported_length_remaining(tvb, offset) < 2) {
587
0
                expert_add_info(pinfo, error_item, &ei_xgt_truncated_data);
588
0
                return tvb_captured_length(tvb);
589
0
            }
590
591
0
            proto_tree_add_item_ret_uint(inst_tree, hf_xgt_error_code, tvb, offset, 2, ENC_LITTLE_ENDIAN, &error_code);
592
0
            expert_add_info_format(pinfo, error_item, &ei_xgt_error_response,
593
0
                                   "XGT Error Response: Error Code 0x%04x", error_code);
594
0
            col_append_fstr(pinfo->cinfo, COL_INFO, " (Error: 0x%04x)", error_code);
595
0
            offset += 2;
596
0
            return offset;
597
0
        }
598
0
    }
599
600
    /* Handle Status command separately */
601
0
    if (is_status) {
602
0
        if (!is_request) {
603
            /* Bounds check for data size field */
604
0
            if (tvb_reported_length_remaining(tvb, offset) < 2) {
605
0
                expert_add_info(pinfo, inst_tree, &ei_xgt_truncated_data);
606
0
                return tvb_captured_length(tvb);
607
0
            }
608
609
            /* Status response data */
610
0
            unsigned data_size;
611
0
            ti = proto_tree_add_item_ret_uint(inst_tree, hf_xgt_data_length, tvb, offset, 2, ENC_LITTLE_ENDIAN, &data_size);
612
0
            offset += 2;
613
614
0
            if (data_size >= 24) {
615
                /* Bounds check for status data */
616
0
                if (tvb_reported_length_remaining(tvb, offset) < 24) {
617
0
                    expert_add_info(pinfo, ti, &ei_xgt_truncated_data);
618
0
                    return tvb_captured_length(tvb);
619
0
                }
620
621
0
                ti = proto_tree_add_item(inst_tree, hf_xgt_status_data, tvb, offset, 24, ENC_NA);
622
0
                status_tree = proto_item_add_subtree(ti, ett_xgt_status);
623
624
0
                proto_tree_add_item(status_tree, hf_xgt_slot_info, tvb, offset, 4, ENC_LITTLE_ENDIAN);
625
0
                offset += 4;
626
0
                proto_tree_add_item(status_tree, hf_xgt_cpu_type, tvb, offset, 2, ENC_LITTLE_ENDIAN);
627
0
                offset += 2;
628
0
                proto_tree_add_item(status_tree, hf_xgt_ver_num, tvb, offset, 2, ENC_LITTLE_ENDIAN);
629
0
                offset += 2;
630
0
                proto_tree_add_item(status_tree, hf_xgt_sys_state, tvb, offset, 4, ENC_LITTLE_ENDIAN);
631
0
                offset += 4;
632
0
                proto_tree_add_item(status_tree, hf_xgt_padt_cnf, tvb, offset, 2, ENC_LITTLE_ENDIAN);
633
0
                offset += 2;
634
0
                proto_tree_add_item(status_tree, hf_xgt_cnf_er, tvb, offset, 4, ENC_LITTLE_ENDIAN);
635
0
                offset += 4;
636
0
                proto_tree_add_item(status_tree, hf_xgt_cnf_war, tvb, offset, 4, ENC_LITTLE_ENDIAN);
637
0
                offset += 4;
638
                /* Reserved 2 bytes */
639
0
                if (tvb_reported_length_remaining(tvb, offset) >= 2) {
640
0
                    offset += 2;
641
0
                }
642
0
            }
643
0
        }
644
0
        return offset;
645
0
    }
646
647
    /* Bounds check for block count field */
648
0
    if (tvb_reported_length_remaining(tvb, offset) < 2) {
649
0
        expert_add_info(pinfo, inst_tree, &ei_xgt_truncated_data);
650
0
        return tvb_captured_length(tvb);
651
0
    }
652
653
    /* Block/Variable Count */
654
0
    if (is_request) {
655
0
        count_item = proto_tree_add_item_ret_uint(inst_tree, hf_xgt_variable_count, tvb, offset, 2, ENC_LITTLE_ENDIAN, &block_count);
656
0
    } else {
657
0
        count_item = proto_tree_add_item_ret_uint(inst_tree, hf_xgt_block_count, tvb, offset, 2, ENC_LITTLE_ENDIAN, &block_count);
658
0
    }
659
660
    /* Add block count to Info column if more than 1 */
661
0
    if (block_count > 1) {
662
0
        col_append_fstr(pinfo->cinfo, COL_INFO, "Blocks:%u ", block_count);
663
0
    }
664
665
    /* Warn about suspicious counts */
666
0
    if (block_count > 100) {
667
0
        expert_add_info_format(pinfo, count_item, &ei_xgt_suspicious_count,
668
0
                               "Suspicious block count: %u (possible malformed packet)", block_count);
669
0
    }
670
0
    offset += 2;
671
672
    /* Process blocks/variables with bounds checking */
673
0
    for (i = 0; i < block_count && i < 256; i++) {
674
        /* Check if we have remaining data */
675
0
        if (tvb_reported_length_remaining(tvb, offset) < 2) {
676
0
            expert_add_info(pinfo, count_item, &ei_xgt_truncated_data);
677
0
            break;
678
0
        }
679
680
        /* Get variable name for request-response matching */
681
0
        char *var_name = NULL;
682
683
        /* For requests, extract variable name and save it */
684
0
        if (is_request && conv_info) {
685
            /* Check if already in map */
686
0
            var_name = (char *)wmem_map_lookup(conv_info->invoke_id_map, (void *)(uintptr_t)invoke_id);
687
688
            /* If not found, peek ahead to get variable name and store it */
689
0
            if (!var_name && tvb_reported_length_remaining(tvb, offset) >= 2) {
690
0
                uint16_t var_length = tvb_get_letohs(tvb, offset);
691
0
                if (var_length > 0 && var_length <= 16 &&
692
0
                    tvb_reported_length_remaining(tvb, offset + 2) >= var_length) {
693
0
                    var_name = (char *)tvb_get_string_enc(pinfo->pool, tvb, offset + 2, var_length, ENC_ASCII);
694
                    /* Store in conversation map */
695
0
                    wmem_map_insert(conv_info->invoke_id_map, (void *)(uintptr_t)invoke_id, wmem_strdup(wmem_file_scope(), var_name));
696
0
                }
697
0
            }
698
0
        }
699
700
        /* For responses, retrieve variable name from conversation map */
701
0
        if (!is_request && conv_info) {
702
0
            var_name = (char *)wmem_map_lookup(conv_info->invoke_id_map, (void *)(uintptr_t)invoke_id);
703
0
        }
704
705
0
        offset = dissect_xgt_block(tvb, pinfo, inst_tree, offset, is_request, is_write, data_type, var_name);
706
707
        /* Safety check to prevent infinite loops */
708
0
        if (offset >= tvb_captured_length(tvb)) {
709
0
            break;
710
0
        }
711
0
    }
712
713
0
    return offset;
714
0
}
715
716
/* Return length of XGT PDU */
717
static unsigned
718
get_xgt_pdu_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_)
719
0
{
720
0
    uint16_t plen;
721
722
    /*
723
     * Get the length of the data from the header.
724
     * The length field is at offset 16-17 (little endian).
725
     */
726
0
    plen = tvb_get_letohs(tvb, offset + 16);
727
728
    /*
729
     * That length doesn't include the header itself;
730
     * add that in.
731
     */
732
0
    return plen + XGT_HEADER_LEN;
733
0
}
734
735
/* Dissect one complete XGT PDU */
736
static int
737
dissect_xgt_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
738
0
{
739
0
    proto_item *ti;
740
0
    proto_tree *xgt_tree;
741
0
    unsigned offset = 0;
742
0
    unsigned invoke_id = 0;
743
744
    /* Set protocol column */
745
0
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "XGT");
746
0
    col_clear(pinfo->cinfo, COL_INFO);
747
748
    /* Create protocol tree */
749
0
    ti = proto_tree_add_item(tree, proto_xgt, tvb, 0, -1, ENC_NA);
750
0
    xgt_tree = proto_item_add_subtree(ti, ett_xgt);
751
752
    /* Extract invoke_id from header (offset 16) for conversation tracking */
753
0
    if (tvb_captured_length(tvb) >= 18) {
754
0
        invoke_id = tvb_get_letohs(tvb, 16);
755
0
    }
756
757
    /* Dissect header */
758
0
    offset = dissect_xgt_header(tvb, pinfo, xgt_tree, offset);
759
760
    /* Dissect instruction if present */
761
0
    if (tvb_reported_length_remaining(tvb, offset) > 0) {
762
0
        dissect_xgt_instruction(tvb, pinfo, xgt_tree, offset, invoke_id);
763
0
    }
764
765
0
    return tvb_captured_length(tvb);
766
0
}
767
768
/* Check if packet looks like XGT protocol */
769
static bool
770
is_xgt(tvbuff_t *tvb, packet_info *pinfo)
771
2
{
772
    /* Make sure there's at least enough data to check company ID */
773
2
    if (!tvb_bytes_exist(tvb, 0, XGT_HEADER_LEN))
774
1
        return false;
775
776
    /* Check that it actually looks like XGT */
777
    /* Verify Company ID */
778
1
    if (tvb_strneql(tvb, 0, XGT_COMPANY_ID, 8) != 0)
779
1
        return false;
780
781
    /* Since XGT is registered on specific ports, give it benefit of the doubt */
782
0
    if ((pinfo->srcport != XGT_TCP_PORT) && (pinfo->destport != XGT_TCP_PORT) &&
783
0
        (pinfo->srcport != XGT_UDP_PORT) && (pinfo->destport != XGT_UDP_PORT))
784
0
        return false;
785
786
0
    return true;
787
0
}
788
789
/* Code to dissect XGT messages over TCP */
790
static int
791
dissect_xgt_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
792
1
{
793
1
    if (!is_xgt(tvb, pinfo))
794
1
        return 0;
795
796
    /* Build up protocol tree and iterate over multiple packets */
797
0
    tcp_dissect_pdus(tvb, pinfo, tree, true, XGT_HEADER_LEN,
798
0
                     get_xgt_pdu_len, dissect_xgt_pdu, data);
799
800
0
    return tvb_captured_length(tvb);
801
1
}
802
803
/* Code to dissect XGT messages over UDP */
804
static int
805
dissect_xgt_udp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
806
1
{
807
1
    if (!is_xgt(tvb, pinfo))
808
1
        return 0;
809
810
0
    return dissect_xgt_pdu(tvb, pinfo, tree, data);
811
1
}
812
813
/* Register protocol */
814
void
815
proto_register_xgt(void)
816
15
{
817
15
    static hf_register_info hf[] = {
818
        /* Header fields */
819
15
        { &hf_xgt_company_id,
820
15
          { "Company ID", "xgt.company_id",
821
15
            FT_STRING, BASE_NONE, NULL, 0x0,
822
15
            "Company identification string", HFILL }
823
15
        },
824
15
        { &hf_xgt_reserved1,
825
15
          { "Reserved", "xgt.reserved1",
826
15
            FT_UINT16, BASE_HEX, NULL, 0x0,
827
15
            NULL, HFILL }
828
15
        },
829
15
        { &hf_xgt_plc_info,
830
15
          { "PLC Info", "xgt.plc_info",
831
15
            FT_UINT16, BASE_HEX, NULL, 0x0,
832
15
            "PLC information field", HFILL }
833
15
        },
834
15
        { &hf_xgt_plc_info_cpu_type,
835
15
          { "CPU Type", "xgt.plc_info.cpu_type",
836
15
            FT_UINT16, BASE_DEC, VALS(xgt_cpu_type_vals), 0x003F,
837
15
            NULL, HFILL }
838
15
        },
839
15
        { &hf_xgt_plc_info_redundancy,
840
15
          { "Redundancy", "xgt.plc_info.redundancy",
841
15
            FT_BOOLEAN, 16, TFS(&tfs_slave_master), 0x0040,
842
15
            "Redundancy status", HFILL }
843
15
        },
844
15
        { &hf_xgt_plc_info_cpu_error,
845
15
          { "CPU Error", "xgt.plc_info.cpu_error",
846
15
            FT_BOOLEAN, 16, TFS(&tfs_error_normal), 0x0080,
847
15
            "CPU operation error status", HFILL }
848
15
        },
849
15
        { &hf_xgt_plc_info_sys_state,
850
15
          { "System State", "xgt.plc_info.sys_state",
851
15
            FT_UINT16, BASE_HEX, VALS(xgt_sys_state_vals), 0x1F00,
852
15
            NULL, HFILL }
853
15
        },
854
15
        { &hf_xgt_cpu_info,
855
15
          { "CPU Info", "xgt.cpu_info",
856
15
            FT_UINT8, BASE_HEX, VALS(xgt_cpu_info_vals), 0x0,
857
15
            NULL, HFILL }
858
15
        },
859
15
        { &hf_xgt_source,
860
15
          { "Source", "xgt.source",
861
15
            FT_UINT8, BASE_HEX, VALS(xgt_source_vals), 0x0,
862
15
            "Source of frame", HFILL }
863
15
        },
864
15
        { &hf_xgt_invoke_id,
865
15
          { "Invoke ID", "xgt.invoke_id",
866
15
            FT_UINT16, BASE_DEC, NULL, 0x0,
867
15
            "Frame sequence identifier", HFILL }
868
15
        },
869
15
        { &hf_xgt_length,
870
15
          { "Length", "xgt.length",
871
15
            FT_UINT16, BASE_DEC, NULL, 0x0,
872
15
            "Application instruction length", HFILL }
873
15
        },
874
15
        { &hf_xgt_fenet_position,
875
15
          { "FEnet Position", "xgt.fenet_position",
876
15
            FT_UINT8, BASE_HEX, NULL, 0x0,
877
15
            "FEnet module position", HFILL }
878
15
        },
879
15
        { &hf_xgt_fenet_slot,
880
15
          { "Slot", "xgt.fenet_position.slot",
881
15
            FT_UINT8, BASE_DEC, NULL, 0x0F,
882
15
            "FEnet module slot number", HFILL }
883
15
        },
884
15
        { &hf_xgt_fenet_base,
885
15
          { "Base", "xgt.fenet_position.base",
886
15
            FT_UINT8, BASE_DEC, NULL, 0xF0,
887
15
            "FEnet module base number", HFILL }
888
15
        },
889
15
        { &hf_xgt_reserved2,
890
15
          { "Reserved/BCC", "xgt.reserved2",
891
15
            FT_UINT8, BASE_HEX, NULL, 0x0,
892
15
            NULL, HFILL }
893
15
        },
894
895
        /* Instruction fields */
896
15
        { &hf_xgt_command,
897
15
          { "Command", "xgt.command",
898
15
            FT_UINT16, BASE_HEX, VALS(xgt_command_vals), 0x0,
899
15
            "Command code", HFILL }
900
15
        },
901
15
        { &hf_xgt_data_type,
902
15
          { "Data Type", "xgt.data_type",
903
15
            FT_UINT16, BASE_HEX, VALS(xgt_data_type_vals), 0x0,
904
15
            NULL, HFILL }
905
15
        },
906
15
        { &hf_xgt_reserved_area,
907
15
          { "Reserved", "xgt.reserved_area",
908
15
            FT_UINT16, BASE_HEX, NULL, 0x0,
909
15
            NULL, HFILL }
910
15
        },
911
15
        { &hf_xgt_error_status,
912
15
          { "Error Status", "xgt.error_status",
913
15
            FT_UINT16, BASE_HEX, VALS(xgt_error_status_vals), 0x0,
914
15
            NULL, HFILL }
915
15
        },
916
15
        { &hf_xgt_error_code,
917
15
          { "Error Code", "xgt.error_code",
918
15
            FT_UINT16, BASE_HEX, VALS(xgt_error_code_vals), 0x0,
919
15
            "XGT protocol error code", HFILL }
920
15
        },
921
15
        { &hf_xgt_block_count,
922
15
          { "Block Count", "xgt.block_count",
923
15
            FT_UINT16, BASE_DEC, NULL, 0x0,
924
15
            NULL, HFILL }
925
15
        },
926
15
        { &hf_xgt_variable_count,
927
15
          { "Variable Count", "xgt.variable_count",
928
15
            FT_UINT16, BASE_DEC, NULL, 0x0,
929
15
            NULL, HFILL }
930
15
        },
931
15
        { &hf_xgt_variable_length,
932
15
          { "Variable Length", "xgt.variable_length",
933
15
            FT_UINT16, BASE_DEC, NULL, 0x0,
934
15
            "Length of variable name", HFILL }
935
15
        },
936
15
        { &hf_xgt_variable_name,
937
15
          { "Variable Name", "xgt.variable_name",
938
15
            FT_STRING, BASE_NONE, NULL, 0x0,
939
15
            NULL, HFILL }
940
15
        },
941
15
        { &hf_xgt_data_length,
942
15
          { "Data Length", "xgt.data_length",
943
15
            FT_UINT16, BASE_DEC, NULL, 0x0,
944
15
            "Length of data in bytes", HFILL }
945
15
        },
946
15
        { &hf_xgt_data,
947
15
          { "Data", "xgt.data",
948
15
            FT_BYTES, BASE_NONE, NULL, 0x0,
949
15
            NULL, HFILL }
950
15
        },
951
952
        /* Status fields */
953
15
        { &hf_xgt_status_data,
954
15
          { "Status Data", "xgt.status_data",
955
15
            FT_BYTES, BASE_NONE, NULL, 0x0,
956
15
            NULL, HFILL }
957
15
        },
958
15
        { &hf_xgt_slot_info,
959
15
          { "Slot Info", "xgt.status.slot_info",
960
15
            FT_UINT32, BASE_HEX, NULL, 0x0,
961
15
            NULL, HFILL }
962
15
        },
963
15
        { &hf_xgt_cpu_type,
964
15
          { "CPU Type", "xgt.status.cpu_type",
965
15
            FT_UINT16, BASE_HEX, NULL, 0x0,
966
15
            NULL, HFILL }
967
15
        },
968
15
        { &hf_xgt_ver_num,
969
15
          { "Version Number", "xgt.status.ver_num",
970
15
            FT_UINT16, BASE_HEX, NULL, 0x0,
971
15
            "OS version number", HFILL }
972
15
        },
973
15
        { &hf_xgt_sys_state,
974
15
          { "System State", "xgt.status.sys_state",
975
15
            FT_UINT32, BASE_HEX, NULL, 0x0,
976
15
            "PLC mode and operation state", HFILL }
977
15
        },
978
15
        { &hf_xgt_padt_cnf,
979
15
          { "PADT Connection", "xgt.status.padt_cnf",
980
15
            FT_UINT16, BASE_HEX, NULL, 0x0,
981
15
            "XG5000 connection status", HFILL }
982
15
        },
983
15
        { &hf_xgt_cnf_er,
984
15
          { "Error Flags", "xgt.status.cnf_er",
985
15
            FT_UINT32, BASE_HEX, NULL, 0x0,
986
15
            "System error flags", HFILL }
987
15
        },
988
15
        { &hf_xgt_cnf_war,
989
15
          { "Warning Flags", "xgt.status.cnf_war",
990
15
            FT_UINT32, BASE_HEX, NULL, 0x0,
991
15
            "System warning flags", HFILL }
992
15
        },
993
994
        /* Data value fields */
995
15
        { &hf_xgt_data_value_uint8,
996
15
          { "Value (BYTE)", "xgt.data.value.uint8",
997
15
            FT_UINT8, BASE_DEC, NULL, 0x0,
998
15
            "Data value as 8-bit unsigned integer", HFILL }
999
15
        },
1000
15
        { &hf_xgt_data_value_uint16,
1001
15
          { "Value (WORD)", "xgt.data.value.uint16",
1002
15
            FT_UINT16, BASE_DEC, NULL, 0x0,
1003
15
            "Data value as 16-bit unsigned integer", HFILL }
1004
15
        },
1005
15
        { &hf_xgt_data_value_uint32,
1006
15
          { "Value (DWORD)", "xgt.data.value.uint32",
1007
15
            FT_UINT32, BASE_DEC, NULL, 0x0,
1008
15
            "Data value as 32-bit unsigned integer", HFILL }
1009
15
        },
1010
15
        { &hf_xgt_data_value_uint64,
1011
15
          { "Value (LWORD)", "xgt.data.value.uint64",
1012
15
            FT_UINT64, BASE_DEC, NULL, 0x0,
1013
15
            "Data value as 64-bit unsigned integer", HFILL }
1014
15
        },
1015
15
        { &hf_xgt_byte_count,
1016
15
          { "Byte Count", "xgt.byte_count",
1017
15
            FT_UINT16, BASE_DEC, NULL, 0x0,
1018
15
            "Number of data bytes", HFILL }
1019
15
        },
1020
15
        { &hf_xgt_word,
1021
15
          { "Word", "xgt.word",
1022
15
            FT_UINT16, BASE_DEC, NULL, 0x0,
1023
15
            "Word value (16-bit)", HFILL }
1024
15
        }
1025
15
    };
1026
1027
15
    static int *ett[] = {
1028
15
        &ett_xgt,
1029
15
        &ett_xgt_header,
1030
15
        &ett_xgt_instruction,
1031
15
        &ett_xgt_plc_info,
1032
15
        &ett_xgt_fenet_position,
1033
15
        &ett_xgt_block,
1034
15
        &ett_xgt_status
1035
15
    };
1036
1037
15
    static ei_register_info ei[] = {
1038
15
        { &ei_xgt_invalid_length,
1039
15
          { "xgt.invalid_length", PI_MALFORMED, PI_ERROR,
1040
15
            "Invalid length field", EXPFILL }
1041
15
        },
1042
15
        { &ei_xgt_error_response,
1043
15
          { "xgt.error_response", PI_RESPONSE_CODE, PI_WARN,
1044
15
            "XGT Error Response", EXPFILL }
1045
15
        },
1046
15
        { &ei_xgt_truncated_data,
1047
15
          { "xgt.truncated_data", PI_MALFORMED, PI_ERROR,
1048
15
            "Truncated data (packet too short)", EXPFILL }
1049
15
        },
1050
15
        { &ei_xgt_invalid_command,
1051
15
          { "xgt.invalid_command", PI_MALFORMED, PI_WARN,
1052
15
            "Unknown or invalid command code", EXPFILL }
1053
15
        },
1054
15
        { &ei_xgt_cpu_error,
1055
15
          { "xgt.cpu_error", PI_RESPONSE_CODE, PI_WARN,
1056
15
            "CPU Error detected", EXPFILL }
1057
15
        },
1058
15
        { &ei_xgt_suspicious_count,
1059
15
          { "xgt.suspicious_count", PI_MALFORMED, PI_WARN,
1060
15
            "Suspicious count value", EXPFILL }
1061
15
        }
1062
15
    };
1063
1064
15
    expert_module_t *expert_xgt;
1065
1066
    /* Register protocol */
1067
15
    proto_xgt = proto_register_protocol("XGT FEnet Protocol", "XGT", "xgt");
1068
1069
15
    proto_register_field_array(proto_xgt, hf, array_length(hf));
1070
15
    proto_register_subtree_array(ett, array_length(ett));
1071
1072
    /* Register expert info */
1073
15
    expert_xgt = expert_register_protocol(proto_xgt);
1074
15
    expert_register_field_array(expert_xgt, ei, array_length(ei));
1075
15
}
1076
1077
void
1078
proto_reg_handoff_xgt(void)
1079
15
{
1080
15
    static dissector_handle_t xgt_tcp_handle;
1081
15
    static dissector_handle_t xgt_udp_handle;
1082
1083
15
    xgt_tcp_handle = create_dissector_handle(dissect_xgt_tcp, proto_xgt);
1084
15
    xgt_udp_handle = create_dissector_handle(dissect_xgt_udp, proto_xgt);
1085
1086
15
    dissector_add_uint("tcp.port", XGT_TCP_PORT, xgt_tcp_handle);
1087
15
    dissector_add_uint("udp.port", XGT_UDP_PORT, xgt_udp_handle);
1088
15
}