Coverage Report

Created: 2026-05-14 06:28

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
0
#define XGT_COMPANY_ID "LSIS-XGT"
36
#define XGT_COMPANY_ID_LEN 8
37
38
/* Header sizes */
39
0
#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
                if (!is_request) {
477
0
                    uint16_t first_value = tvb_get_letohs(tvb, offset);
478
0
                    col_append_fstr(pinfo->cinfo, COL_INFO, "[%u bytes] = %u...", data_length, first_value);
479
0
                }
480
0
            } else if (data_length == 8 && data_type == XGT_DTYPE_LWORD) {
481
                /* For 64-bit values, show as uint64 using data_value field */
482
0
                uint64_t value = tvb_get_letoh64(tvb, offset);
483
0
                proto_tree_add_uint64_format(block_tree, hf_xgt_data_value_uint64, tvb, offset, data_length, value, "Data: %" G_GUINT64_FORMAT, value);
484
0
            } else if (data_length == 1 && (data_type == XGT_DTYPE_BIT || data_type == XGT_DTYPE_BYTE)) {
485
0
                uint8_t value = tvb_get_uint8(tvb, offset);
486
0
                proto_tree_add_uint_format(block_tree, hf_xgt_data_value_uint8, tvb, offset, data_length, value, "Data: %u", value);
487
0
            } else if (data_length == 2 && data_type == XGT_DTYPE_WORD) {
488
0
                uint16_t value = tvb_get_letohs(tvb, offset);
489
0
                proto_tree_add_uint_format(block_tree, hf_xgt_data_value_uint16, tvb, offset, data_length, value, "Data: %u", value);
490
0
            } else if (data_length == 4 && data_type == XGT_DTYPE_DWORD) {
491
0
                uint32_t value = tvb_get_letohl(tvb, offset);
492
0
                proto_tree_add_uint_format(block_tree, hf_xgt_data_value_uint32, tvb, offset, data_length, value, "Data: %u", value);
493
0
            } else {
494
                /* For multi-value or large data, show all bytes as hex */
495
0
                proto_tree_add_item(block_tree, hf_xgt_data, tvb, offset, data_length, ENC_NA);
496
0
            }
497
498
            /* Add data value to Info column for responses (for non-continuous types) */
499
0
            if (!is_request && data_type != XGT_DTYPE_CONTINUOUS) {
500
0
                if (data_length >= 2) {
501
                    /* For single values, show the value */
502
0
                    uint16_t value = tvb_get_letohs(tvb, offset);
503
0
                    col_append_fstr(pinfo->cinfo, COL_INFO, "= %u", value);
504
0
                } else if (data_length == 1) {
505
                    /* For single byte */
506
0
                    uint8_t value = tvb_get_uint8(tvb, offset);
507
0
                    col_append_fstr(pinfo->cinfo, COL_INFO, "= %u", value);
508
0
                }
509
0
            }
510
511
0
            offset += data_length;
512
0
        }
513
0
    }
514
515
0
    proto_item_set_len(ti, offset - start_offset);
516
0
    return offset;
517
0
}
518
519
/* Dissect XGT Instruction (Command and Data) */
520
static int
521
dissect_xgt_instruction(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, unsigned offset, unsigned invoke_id)
522
0
{
523
0
    proto_tree *inst_tree, *status_tree;
524
0
    proto_item *ti, *cmd_item, *count_item, *error_item;
525
0
    unsigned command, error_status, error_code, block_count, data_type;
526
0
    unsigned i;
527
0
    bool is_request, is_write, is_status;
528
0
    const char *cmd_str;
529
0
    xgt_conv_info_t *conv_info = NULL;
530
0
    conversation_t *conversation = NULL;
531
532
    /* Bounds check - need at least 8 bytes (command, data type, reserved, error/count) */
533
0
    if (tvb_reported_length_remaining(tvb, offset) < 8) {
534
0
        expert_add_info(pinfo, tree, &ei_xgt_truncated_data);
535
0
        return tvb_captured_length(tvb);
536
0
    }
537
538
0
    ti = proto_tree_add_item(tree, proto_xgt, tvb, offset, -1, ENC_NA);
539
0
    inst_tree = proto_item_add_subtree(ti, ett_xgt_instruction);
540
541
    /* Command */
542
0
    cmd_item = proto_tree_add_item_ret_uint(inst_tree, hf_xgt_command, tvb, offset, 2, ENC_LITTLE_ENDIAN, &command);
543
0
    cmd_str = val_to_str_const(command, xgt_command_vals, "Unknown");
544
0
    col_append_str(pinfo->cinfo, COL_INFO, cmd_str);
545
546
    /* Warn about unknown commands */
547
0
    if (try_val_to_str(command, xgt_command_vals) == NULL) {
548
0
        expert_add_info(pinfo, cmd_item, &ei_xgt_invalid_command);
549
0
    }
550
0
    offset += 2;
551
552
    /* Determine request/response and command type */
553
0
    is_request = (command == XGT_CMD_READ_REQUEST || command == XGT_CMD_WRITE_REQUEST ||
554
0
                  command == XGT_CMD_STATUS_REQUEST);
555
0
    is_write = (command == XGT_CMD_WRITE_REQUEST || command == XGT_CMD_WRITE_RESPONSE);
556
0
    is_status = (command == XGT_CMD_STATUS_REQUEST || command == XGT_CMD_STATUS_RESPONSE);
557
558
    /* Set up conversation tracking for request-response matching */
559
0
    conversation = find_or_create_conversation(pinfo);
560
0
    conv_info = (xgt_conv_info_t *)conversation_get_proto_data(conversation, proto_xgt);
561
562
0
    if (!conv_info) {
563
0
        conv_info = wmem_new(wmem_file_scope(), xgt_conv_info_t);
564
0
        conv_info->invoke_id_map = wmem_map_new(wmem_file_scope(), g_direct_hash, g_direct_equal);
565
0
        conversation_add_proto_data(conversation, proto_xgt, conv_info);
566
0
    }
567
568
    /* Data Type */
569
0
    proto_tree_add_item_ret_uint(inst_tree, hf_xgt_data_type, tvb, offset, 2, ENC_LITTLE_ENDIAN, &data_type);
570
571
    /* Add data type to Info column */
572
0
    const char *dtype_str = val_to_str_const(data_type, xgt_data_type_vals, "Unknown");
573
0
    col_append_fstr(pinfo->cinfo, COL_INFO, "(%s) ", dtype_str);
574
575
0
    offset += 2;
576
577
    /* Reserved Area */
578
0
    proto_tree_add_item(inst_tree, hf_xgt_reserved_area, tvb, offset, 2, ENC_LITTLE_ENDIAN);
579
0
    offset += 2;
580
581
    /* Error Status (responses only) */
582
0
    if (!is_request) {
583
0
        error_item = proto_tree_add_item_ret_uint(inst_tree, hf_xgt_error_status, tvb, offset, 2, ENC_LITTLE_ENDIAN, &error_status);
584
0
        offset += 2;
585
586
        /* Error Code (if error exists) */
587
0
        if (error_status != XGT_ERROR_NONE) {
588
            /* Bounds check for error code */
589
0
            if (tvb_reported_length_remaining(tvb, offset) < 2) {
590
0
                expert_add_info(pinfo, error_item, &ei_xgt_truncated_data);
591
0
                return tvb_captured_length(tvb);
592
0
            }
593
594
0
            proto_tree_add_item_ret_uint(inst_tree, hf_xgt_error_code, tvb, offset, 2, ENC_LITTLE_ENDIAN, &error_code);
595
0
            expert_add_info_format(pinfo, error_item, &ei_xgt_error_response,
596
0
                                   "XGT Error Response: Error Code 0x%04x", error_code);
597
0
            col_append_fstr(pinfo->cinfo, COL_INFO, " (Error: 0x%04x)", error_code);
598
0
            offset += 2;
599
0
            return offset;
600
0
        }
601
0
    }
602
603
    /* Handle Status command separately */
604
0
    if (is_status) {
605
0
        if (!is_request) {
606
            /* Bounds check for data size field */
607
0
            if (tvb_reported_length_remaining(tvb, offset) < 2) {
608
0
                expert_add_info(pinfo, inst_tree, &ei_xgt_truncated_data);
609
0
                return tvb_captured_length(tvb);
610
0
            }
611
612
            /* Status response data */
613
0
            unsigned data_size;
614
0
            ti = proto_tree_add_item_ret_uint(inst_tree, hf_xgt_data_length, tvb, offset, 2, ENC_LITTLE_ENDIAN, &data_size);
615
0
            offset += 2;
616
617
0
            if (data_size >= 24) {
618
                /* Bounds check for status data */
619
0
                if (tvb_reported_length_remaining(tvb, offset) < 24) {
620
0
                    expert_add_info(pinfo, ti, &ei_xgt_truncated_data);
621
0
                    return tvb_captured_length(tvb);
622
0
                }
623
624
0
                ti = proto_tree_add_item(inst_tree, hf_xgt_status_data, tvb, offset, 24, ENC_NA);
625
0
                status_tree = proto_item_add_subtree(ti, ett_xgt_status);
626
627
0
                proto_tree_add_item(status_tree, hf_xgt_slot_info, tvb, offset, 4, ENC_LITTLE_ENDIAN);
628
0
                offset += 4;
629
0
                proto_tree_add_item(status_tree, hf_xgt_cpu_type, tvb, offset, 2, ENC_LITTLE_ENDIAN);
630
0
                offset += 2;
631
0
                proto_tree_add_item(status_tree, hf_xgt_ver_num, tvb, offset, 2, ENC_LITTLE_ENDIAN);
632
0
                offset += 2;
633
0
                proto_tree_add_item(status_tree, hf_xgt_sys_state, tvb, offset, 4, ENC_LITTLE_ENDIAN);
634
0
                offset += 4;
635
0
                proto_tree_add_item(status_tree, hf_xgt_padt_cnf, tvb, offset, 2, ENC_LITTLE_ENDIAN);
636
0
                offset += 2;
637
0
                proto_tree_add_item(status_tree, hf_xgt_cnf_er, tvb, offset, 4, ENC_LITTLE_ENDIAN);
638
0
                offset += 4;
639
0
                proto_tree_add_item(status_tree, hf_xgt_cnf_war, tvb, offset, 4, ENC_LITTLE_ENDIAN);
640
0
                offset += 4;
641
                /* Reserved 2 bytes */
642
0
                if (tvb_reported_length_remaining(tvb, offset) >= 2) {
643
0
                    offset += 2;
644
0
                }
645
0
            }
646
0
        }
647
0
        return offset;
648
0
    }
649
650
    /* Bounds check for block count field */
651
0
    if (tvb_reported_length_remaining(tvb, offset) < 2) {
652
0
        expert_add_info(pinfo, inst_tree, &ei_xgt_truncated_data);
653
0
        return tvb_captured_length(tvb);
654
0
    }
655
656
    /* Block/Variable Count */
657
0
    if (is_request) {
658
0
        count_item = proto_tree_add_item_ret_uint(inst_tree, hf_xgt_variable_count, tvb, offset, 2, ENC_LITTLE_ENDIAN, &block_count);
659
0
    } else {
660
0
        count_item = proto_tree_add_item_ret_uint(inst_tree, hf_xgt_block_count, tvb, offset, 2, ENC_LITTLE_ENDIAN, &block_count);
661
0
    }
662
663
    /* Add block count to Info column if more than 1 */
664
0
    if (block_count > 1) {
665
0
        col_append_fstr(pinfo->cinfo, COL_INFO, "Blocks:%u ", block_count);
666
0
    }
667
668
    /* Warn about suspicious counts */
669
0
    if (block_count > 100) {
670
0
        expert_add_info_format(pinfo, count_item, &ei_xgt_suspicious_count,
671
0
                               "Suspicious block count: %u (possible malformed packet)", block_count);
672
0
    }
673
0
    offset += 2;
674
675
    /* Process blocks/variables with bounds checking */
676
0
    for (i = 0; i < block_count && i < 256; i++) {
677
        /* Check if we have remaining data */
678
0
        if (tvb_reported_length_remaining(tvb, offset) < 2) {
679
0
            expert_add_info(pinfo, count_item, &ei_xgt_truncated_data);
680
0
            break;
681
0
        }
682
683
        /* Get variable name for request-response matching */
684
0
        char *var_name = NULL;
685
686
        /* For requests, extract variable name and save it */
687
0
        if (is_request && conv_info) {
688
            /* Check if already in map */
689
0
            var_name = (char *)wmem_map_lookup(conv_info->invoke_id_map, (void *)(uintptr_t)invoke_id);
690
691
            /* If not found, peek ahead to get variable name and store it */
692
0
            if (!var_name && tvb_reported_length_remaining(tvb, offset) >= 2) {
693
0
                uint16_t var_length = tvb_get_letohs(tvb, offset);
694
0
                if (var_length > 0 && var_length <= 16 &&
695
0
                    tvb_reported_length_remaining(tvb, offset + 2) >= var_length) {
696
0
                    var_name = (char *)tvb_get_string_enc(pinfo->pool, tvb, offset + 2, var_length, ENC_ASCII);
697
                    /* Store in conversation map */
698
0
                    wmem_map_insert(conv_info->invoke_id_map, (void *)(uintptr_t)invoke_id, wmem_strdup(wmem_file_scope(), var_name));
699
0
                }
700
0
            }
701
0
        }
702
703
        /* For responses, retrieve variable name from conversation map */
704
0
        if (!is_request && conv_info) {
705
0
            var_name = (char *)wmem_map_lookup(conv_info->invoke_id_map, (void *)(uintptr_t)invoke_id);
706
0
        }
707
708
0
        offset = dissect_xgt_block(tvb, pinfo, inst_tree, offset, is_request, is_write, data_type, var_name);
709
710
        /* Safety check to prevent infinite loops */
711
0
        if (offset >= tvb_captured_length(tvb)) {
712
0
            break;
713
0
        }
714
0
    }
715
716
0
    return offset;
717
0
}
718
719
/* Return length of XGT PDU */
720
static unsigned
721
get_xgt_pdu_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_)
722
0
{
723
0
    uint16_t plen;
724
725
    /*
726
     * Get the length of the data from the header.
727
     * The length field is at offset 16-17 (little endian).
728
     */
729
0
    plen = tvb_get_letohs(tvb, offset + 16);
730
731
    /*
732
     * That length doesn't include the header itself;
733
     * add that in.
734
     */
735
0
    return plen + XGT_HEADER_LEN;
736
0
}
737
738
/* Dissect one complete XGT PDU */
739
static int
740
dissect_xgt_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
741
0
{
742
0
    proto_item *ti;
743
0
    proto_tree *xgt_tree;
744
0
    unsigned offset = 0;
745
0
    unsigned invoke_id = 0;
746
747
    /* Set protocol column */
748
0
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "XGT");
749
0
    col_clear(pinfo->cinfo, COL_INFO);
750
751
    /* Create protocol tree */
752
0
    ti = proto_tree_add_item(tree, proto_xgt, tvb, 0, -1, ENC_NA);
753
0
    xgt_tree = proto_item_add_subtree(ti, ett_xgt);
754
755
    /* Extract invoke_id from header (offset 16) for conversation tracking */
756
0
    if (tvb_captured_length(tvb) >= 18) {
757
0
        invoke_id = tvb_get_letohs(tvb, 16);
758
0
    }
759
760
    /* Dissect header */
761
0
    offset = dissect_xgt_header(tvb, pinfo, xgt_tree, offset);
762
763
    /* Dissect instruction if present */
764
0
    if (tvb_reported_length_remaining(tvb, offset) > 0) {
765
0
        dissect_xgt_instruction(tvb, pinfo, xgt_tree, offset, invoke_id);
766
0
    }
767
768
0
    return tvb_captured_length(tvb);
769
0
}
770
771
/* Check if packet looks like XGT protocol */
772
static bool
773
is_xgt(tvbuff_t *tvb, packet_info *pinfo)
774
0
{
775
    /* Make sure there's at least enough data to check company ID */
776
0
    if (!tvb_bytes_exist(tvb, 0, XGT_HEADER_LEN))
777
0
        return false;
778
779
    /* Check that it actually looks like XGT */
780
    /* Verify Company ID */
781
0
    if (tvb_strneql(tvb, 0, XGT_COMPANY_ID, 8) != 0)
782
0
        return false;
783
784
    /* Since XGT is registered on specific ports, give it benefit of the doubt */
785
0
    if ((pinfo->srcport != XGT_TCP_PORT) && (pinfo->destport != XGT_TCP_PORT) &&
786
0
        (pinfo->srcport != XGT_UDP_PORT) && (pinfo->destport != XGT_UDP_PORT))
787
0
        return false;
788
789
0
    return true;
790
0
}
791
792
/* Code to dissect XGT messages over TCP */
793
static int
794
dissect_xgt_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
795
0
{
796
0
    if (!is_xgt(tvb, pinfo))
797
0
        return 0;
798
799
    /* Build up protocol tree and iterate over multiple packets */
800
0
    tcp_dissect_pdus(tvb, pinfo, tree, true, XGT_HEADER_LEN,
801
0
                     get_xgt_pdu_len, dissect_xgt_pdu, data);
802
803
0
    return tvb_captured_length(tvb);
804
0
}
805
806
/* Code to dissect XGT messages over UDP */
807
static int
808
dissect_xgt_udp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
809
0
{
810
0
    if (!is_xgt(tvb, pinfo))
811
0
        return 0;
812
813
0
    return dissect_xgt_pdu(tvb, pinfo, tree, data);
814
0
}
815
816
/* Register protocol */
817
void
818
proto_register_xgt(void)
819
15
{
820
15
    static hf_register_info hf[] = {
821
        /* Header fields */
822
15
        { &hf_xgt_company_id,
823
15
          { "Company ID", "xgt.company_id",
824
15
            FT_STRING, BASE_NONE, NULL, 0x0,
825
15
            "Company identification string", HFILL }
826
15
        },
827
15
        { &hf_xgt_reserved1,
828
15
          { "Reserved", "xgt.reserved1",
829
15
            FT_UINT16, BASE_HEX, NULL, 0x0,
830
15
            NULL, HFILL }
831
15
        },
832
15
        { &hf_xgt_plc_info,
833
15
          { "PLC Info", "xgt.plc_info",
834
15
            FT_UINT16, BASE_HEX, NULL, 0x0,
835
15
            "PLC information field", HFILL }
836
15
        },
837
15
        { &hf_xgt_plc_info_cpu_type,
838
15
          { "CPU Type", "xgt.plc_info.cpu_type",
839
15
            FT_UINT16, BASE_DEC, VALS(xgt_cpu_type_vals), 0x003F,
840
15
            NULL, HFILL }
841
15
        },
842
15
        { &hf_xgt_plc_info_redundancy,
843
15
          { "Redundancy", "xgt.plc_info.redundancy",
844
15
            FT_BOOLEAN, 16, TFS(&tfs_slave_master), 0x0040,
845
15
            "Redundancy status", HFILL }
846
15
        },
847
15
        { &hf_xgt_plc_info_cpu_error,
848
15
          { "CPU Error", "xgt.plc_info.cpu_error",
849
15
            FT_BOOLEAN, 16, TFS(&tfs_error_normal), 0x0080,
850
15
            "CPU operation error status", HFILL }
851
15
        },
852
15
        { &hf_xgt_plc_info_sys_state,
853
15
          { "System State", "xgt.plc_info.sys_state",
854
15
            FT_UINT16, BASE_HEX, VALS(xgt_sys_state_vals), 0x1F00,
855
15
            NULL, HFILL }
856
15
        },
857
15
        { &hf_xgt_cpu_info,
858
15
          { "CPU Info", "xgt.cpu_info",
859
15
            FT_UINT8, BASE_HEX, VALS(xgt_cpu_info_vals), 0x0,
860
15
            NULL, HFILL }
861
15
        },
862
15
        { &hf_xgt_source,
863
15
          { "Source", "xgt.source",
864
15
            FT_UINT8, BASE_HEX, VALS(xgt_source_vals), 0x0,
865
15
            "Source of frame", HFILL }
866
15
        },
867
15
        { &hf_xgt_invoke_id,
868
15
          { "Invoke ID", "xgt.invoke_id",
869
15
            FT_UINT16, BASE_DEC, NULL, 0x0,
870
15
            "Frame sequence identifier", HFILL }
871
15
        },
872
15
        { &hf_xgt_length,
873
15
          { "Length", "xgt.length",
874
15
            FT_UINT16, BASE_DEC, NULL, 0x0,
875
15
            "Application instruction length", HFILL }
876
15
        },
877
15
        { &hf_xgt_fenet_position,
878
15
          { "FEnet Position", "xgt.fenet_position",
879
15
            FT_UINT8, BASE_HEX, NULL, 0x0,
880
15
            "FEnet module position", HFILL }
881
15
        },
882
15
        { &hf_xgt_fenet_slot,
883
15
          { "Slot", "xgt.fenet_position.slot",
884
15
            FT_UINT8, BASE_DEC, NULL, 0x0F,
885
15
            "FEnet module slot number", HFILL }
886
15
        },
887
15
        { &hf_xgt_fenet_base,
888
15
          { "Base", "xgt.fenet_position.base",
889
15
            FT_UINT8, BASE_DEC, NULL, 0xF0,
890
15
            "FEnet module base number", HFILL }
891
15
        },
892
15
        { &hf_xgt_reserved2,
893
15
          { "Reserved/BCC", "xgt.reserved2",
894
15
            FT_UINT8, BASE_HEX, NULL, 0x0,
895
15
            NULL, HFILL }
896
15
        },
897
898
        /* Instruction fields */
899
15
        { &hf_xgt_command,
900
15
          { "Command", "xgt.command",
901
15
            FT_UINT16, BASE_HEX, VALS(xgt_command_vals), 0x0,
902
15
            "Command code", HFILL }
903
15
        },
904
15
        { &hf_xgt_data_type,
905
15
          { "Data Type", "xgt.data_type",
906
15
            FT_UINT16, BASE_HEX, VALS(xgt_data_type_vals), 0x0,
907
15
            NULL, HFILL }
908
15
        },
909
15
        { &hf_xgt_reserved_area,
910
15
          { "Reserved", "xgt.reserved_area",
911
15
            FT_UINT16, BASE_HEX, NULL, 0x0,
912
15
            NULL, HFILL }
913
15
        },
914
15
        { &hf_xgt_error_status,
915
15
          { "Error Status", "xgt.error_status",
916
15
            FT_UINT16, BASE_HEX, VALS(xgt_error_status_vals), 0x0,
917
15
            NULL, HFILL }
918
15
        },
919
15
        { &hf_xgt_error_code,
920
15
          { "Error Code", "xgt.error_code",
921
15
            FT_UINT16, BASE_HEX, VALS(xgt_error_code_vals), 0x0,
922
15
            "XGT protocol error code", HFILL }
923
15
        },
924
15
        { &hf_xgt_block_count,
925
15
          { "Block Count", "xgt.block_count",
926
15
            FT_UINT16, BASE_DEC, NULL, 0x0,
927
15
            NULL, HFILL }
928
15
        },
929
15
        { &hf_xgt_variable_count,
930
15
          { "Variable Count", "xgt.variable_count",
931
15
            FT_UINT16, BASE_DEC, NULL, 0x0,
932
15
            NULL, HFILL }
933
15
        },
934
15
        { &hf_xgt_variable_length,
935
15
          { "Variable Length", "xgt.variable_length",
936
15
            FT_UINT16, BASE_DEC, NULL, 0x0,
937
15
            "Length of variable name", HFILL }
938
15
        },
939
15
        { &hf_xgt_variable_name,
940
15
          { "Variable Name", "xgt.variable_name",
941
15
            FT_STRING, BASE_NONE, NULL, 0x0,
942
15
            NULL, HFILL }
943
15
        },
944
15
        { &hf_xgt_data_length,
945
15
          { "Data Length", "xgt.data_length",
946
15
            FT_UINT16, BASE_DEC, NULL, 0x0,
947
15
            "Length of data in bytes", HFILL }
948
15
        },
949
15
        { &hf_xgt_data,
950
15
          { "Data", "xgt.data",
951
15
            FT_BYTES, BASE_NONE, NULL, 0x0,
952
15
            NULL, HFILL }
953
15
        },
954
955
        /* Status fields */
956
15
        { &hf_xgt_status_data,
957
15
          { "Status Data", "xgt.status_data",
958
15
            FT_BYTES, BASE_NONE, NULL, 0x0,
959
15
            NULL, HFILL }
960
15
        },
961
15
        { &hf_xgt_slot_info,
962
15
          { "Slot Info", "xgt.status.slot_info",
963
15
            FT_UINT32, BASE_HEX, NULL, 0x0,
964
15
            NULL, HFILL }
965
15
        },
966
15
        { &hf_xgt_cpu_type,
967
15
          { "CPU Type", "xgt.status.cpu_type",
968
15
            FT_UINT16, BASE_HEX, NULL, 0x0,
969
15
            NULL, HFILL }
970
15
        },
971
15
        { &hf_xgt_ver_num,
972
15
          { "Version Number", "xgt.status.ver_num",
973
15
            FT_UINT16, BASE_HEX, NULL, 0x0,
974
15
            "OS version number", HFILL }
975
15
        },
976
15
        { &hf_xgt_sys_state,
977
15
          { "System State", "xgt.status.sys_state",
978
15
            FT_UINT32, BASE_HEX, NULL, 0x0,
979
15
            "PLC mode and operation state", HFILL }
980
15
        },
981
15
        { &hf_xgt_padt_cnf,
982
15
          { "PADT Connection", "xgt.status.padt_cnf",
983
15
            FT_UINT16, BASE_HEX, NULL, 0x0,
984
15
            "XG5000 connection status", HFILL }
985
15
        },
986
15
        { &hf_xgt_cnf_er,
987
15
          { "Error Flags", "xgt.status.cnf_er",
988
15
            FT_UINT32, BASE_HEX, NULL, 0x0,
989
15
            "System error flags", HFILL }
990
15
        },
991
15
        { &hf_xgt_cnf_war,
992
15
          { "Warning Flags", "xgt.status.cnf_war",
993
15
            FT_UINT32, BASE_HEX, NULL, 0x0,
994
15
            "System warning flags", HFILL }
995
15
        },
996
997
        /* Data value fields */
998
15
        { &hf_xgt_data_value_uint8,
999
15
          { "Value (BYTE)", "xgt.data.value.uint8",
1000
15
            FT_UINT8, BASE_DEC, NULL, 0x0,
1001
15
            "Data value as 8-bit unsigned integer", HFILL }
1002
15
        },
1003
15
        { &hf_xgt_data_value_uint16,
1004
15
          { "Value (WORD)", "xgt.data.value.uint16",
1005
15
            FT_UINT16, BASE_DEC, NULL, 0x0,
1006
15
            "Data value as 16-bit unsigned integer", HFILL }
1007
15
        },
1008
15
        { &hf_xgt_data_value_uint32,
1009
15
          { "Value (DWORD)", "xgt.data.value.uint32",
1010
15
            FT_UINT32, BASE_DEC, NULL, 0x0,
1011
15
            "Data value as 32-bit unsigned integer", HFILL }
1012
15
        },
1013
15
        { &hf_xgt_data_value_uint64,
1014
15
          { "Value (LWORD)", "xgt.data.value.uint64",
1015
15
            FT_UINT64, BASE_DEC, NULL, 0x0,
1016
15
            "Data value as 64-bit unsigned integer", HFILL }
1017
15
        },
1018
15
        { &hf_xgt_byte_count,
1019
15
          { "Byte Count", "xgt.byte_count",
1020
15
            FT_UINT16, BASE_DEC, NULL, 0x0,
1021
15
            "Number of data bytes", HFILL }
1022
15
        },
1023
15
        { &hf_xgt_word,
1024
15
          { "Word", "xgt.word",
1025
15
            FT_UINT16, BASE_DEC, NULL, 0x0,
1026
15
            "Word value (16-bit)", HFILL }
1027
15
        }
1028
15
    };
1029
1030
15
    static int *ett[] = {
1031
15
        &ett_xgt,
1032
15
        &ett_xgt_header,
1033
15
        &ett_xgt_instruction,
1034
15
        &ett_xgt_plc_info,
1035
15
        &ett_xgt_fenet_position,
1036
15
        &ett_xgt_block,
1037
15
        &ett_xgt_status
1038
15
    };
1039
1040
15
    static ei_register_info ei[] = {
1041
15
        { &ei_xgt_invalid_length,
1042
15
          { "xgt.invalid_length", PI_MALFORMED, PI_ERROR,
1043
15
            "Invalid length field", EXPFILL }
1044
15
        },
1045
15
        { &ei_xgt_error_response,
1046
15
          { "xgt.error_response", PI_RESPONSE_CODE, PI_WARN,
1047
15
            "XGT Error Response", EXPFILL }
1048
15
        },
1049
15
        { &ei_xgt_truncated_data,
1050
15
          { "xgt.truncated_data", PI_MALFORMED, PI_ERROR,
1051
15
            "Truncated data (packet too short)", EXPFILL }
1052
15
        },
1053
15
        { &ei_xgt_invalid_command,
1054
15
          { "xgt.invalid_command", PI_MALFORMED, PI_WARN,
1055
15
            "Unknown or invalid command code", EXPFILL }
1056
15
        },
1057
15
        { &ei_xgt_cpu_error,
1058
15
          { "xgt.cpu_error", PI_RESPONSE_CODE, PI_WARN,
1059
15
            "CPU Error detected", EXPFILL }
1060
15
        },
1061
15
        { &ei_xgt_suspicious_count,
1062
15
          { "xgt.suspicious_count", PI_MALFORMED, PI_WARN,
1063
15
            "Suspicious count value", EXPFILL }
1064
15
        }
1065
15
    };
1066
1067
15
    expert_module_t *expert_xgt;
1068
1069
    /* Register protocol */
1070
15
    proto_xgt = proto_register_protocol(
1071
15
        "XGT FEnet Protocol",    /* name */
1072
15
        "XGT",                 /* short name */
1073
15
        "xgt"                  /* filter name */
1074
15
    );
1075
1076
15
    proto_register_field_array(proto_xgt, hf, array_length(hf));
1077
15
    proto_register_subtree_array(ett, array_length(ett));
1078
1079
    /* Register expert info */
1080
15
    expert_xgt = expert_register_protocol(proto_xgt);
1081
15
    expert_register_field_array(expert_xgt, ei, array_length(ei));
1082
15
}
1083
1084
void
1085
proto_reg_handoff_xgt(void)
1086
15
{
1087
15
    static dissector_handle_t xgt_tcp_handle;
1088
15
    static dissector_handle_t xgt_udp_handle;
1089
1090
15
    xgt_tcp_handle = create_dissector_handle(dissect_xgt_tcp, proto_xgt);
1091
15
    xgt_udp_handle = create_dissector_handle(dissect_xgt_udp, proto_xgt);
1092
1093
15
    dissector_add_uint("tcp.port", XGT_TCP_PORT, xgt_tcp_handle);
1094
15
    dissector_add_uint("udp.port", XGT_UDP_PORT, xgt_udp_handle);
1095
15
}