Coverage Report

Created: 2026-06-30 07:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/epan/dissectors/packet-trdp.c
Line
Count
Source
1
/* packet-trdp.c
2
 * Routines for trdp packet dissection
3
 *
4
 * The Train Real-Time Data Protocol (TRDP) is defined in IEC 61375-2-3. The
5
 * protocol is used to exchange Train Communication Network (TCN) process data
6
 * and message data.
7
 *
8
 * Copyright Bombardier Transportation Inc. or its subsidiaries and others, 2013. Florian Weispfenning
9
 * Copyright Universität Rostock, 2019 (substantial changes leading to GLib-only version). Thorsten Schulz
10
 * Copyright Stadler Deutschland GmbH, 2022-2026. Thorsten Schulz
11
 *
12
 * The new display-filter approach contains aspects and code
13
 * snippets from the wimaxasncp dissector by Stephen Croll.
14
 *
15
 * Wireshark - Network traffic analyzer
16
 * By Gerald Combs <gerald@wireshark.org>
17
 * Copyright 1998 Gerald Combs
18
 *
19
 * SPDX-License-Identifier: GPL-2.0-or-later
20
 */
21
22
#include <epan/packet.h>
23
#include <epan/packet_info.h>
24
#include <epan/prefs.h>
25
#include <epan/to_str.h>
26
#include <epan/addr_resolv.h>
27
#include <epan/strutil.h>
28
#include <epan/tvbuff.h>
29
#include <wsutil/crc32.h>
30
#include <epan/crc32-tvb.h>
31
#include <epan/column-utils.h>
32
#include <epan/dissectors/packet-tcp.h>
33
#include <epan/expert.h>
34
#include <wsutil/report_message.h>
35
#include <wsutil/filesystem.h>
36
#include <libxml/xmlreader.h>
37
#include <libxml/tree.h>
38
39
40
/*******************************************************************************
41
 * DEFINES
42
 */
43
44
enum TrdpTypeId {
45
    TRDP_BITSET8=1,     /**< =UINT8, n:[1..8] bits relevant, see subtype  */
46
    TRDP_CHAR8,       /**< char, can be used also as UTF8 */
47
    TRDP_UTF16,       /**< Unicode UTF-16 character */
48
    TRDP_INT8,        /**< Signed integer, 8 bit */
49
    TRDP_INT16,       /**< Signed integer, 16 bit */
50
    TRDP_INT32,       /**< Signed integer, 32 bit */
51
    TRDP_INT64,       /**< Signed integer, 64 bit */
52
    TRDP_UINT8,       /**< Unsigned integer, 8 bit */
53
    TRDP_UINT16,      /**< Unsigned integer, 16 bit */
54
    TRDP_UINT32,      /**< Unsigned integer, 32 bit */
55
    TRDP_UINT64,      /**< Unsigned integer, 64 bit */
56
    TRDP_REAL32,      /**< Floating point real, 32 bit */
57
    TRDP_REAL64,      /**< Floating point real, 64 bit */
58
    TRDP_TIMEDATE32,  /**< 32 bit UNIX time */
59
    TRDP_TIMEDATE48,  /**< 48 bit TCN time (32 bit seconds and 16 bit ticks) */
60
    TRDP_TIMEDATE64,  /**< 32 bit seconds and 32 bit microseconds */
61
    TRDP_UUID,        /**< UINT8*16 == UUID, not official type though */
62
};
63
64
0
#define TRDP_BITSUBTYPE_BITS    8
65
66
enum TrdpBitSubtypeId {
67
    TRDP_BITSUBTYPE_BITSET8,    /**< =UINT8, all 8bits displayed */
68
    TRDP_BITSUBTYPE_BOOL8,      /**< =UINT8, 1 bit relevant (equal to zero -> false, not equal to zero -> true) */
69
    TRDP_BITSUBTYPE_ANTIVALENT8 /**< =UINT8, 2 bit relevant ('01'B -> false, '10'B -> true) */
70
};
71
72
enum TrdpEndianSubtypeId {
73
    TRDP_ENDSUBTYPE_BIG, /**< Big Endian */
74
    TRDP_ENDSUBTYPE_LIT  /**< Little Endian */
75
};
76
77
enum TrdpFoldSetting {
78
    TRDP_FOLD_NEVER,
79
    TRDP_FOLD_CONFIG,
80
    TRDP_FOLD_ALWAYS
81
};
82
83
0
#define TRDP_STANDARDTYPE_MAX TRDP_UUID /**< The last standard data type */
84
85
#define TRDP_DEFAULT_UDPTCP_MD_PORT 17225 /**< Default port address for Message data (MD) communication */
86
#define TRDP_DEFAULT_UDP_PD_PORT    17224 /**< Default port address for Process data (PD) communication */
87
#define TRDP_DEFAULT_STR_PD_PORT   "17224"
88
#define TRDP_DEFAULT_STR_MD_PORT   "17225"
89
90
0
#define TRDP_DEFAULT_SC32_SID      0xFFFFFFFF
91
#define TRDP_DEFAULT_STR_SC32_SID "0xFFFFFFFF"
92
#define TRDP_SDTPROT_VERSION       0x0002
93
94
0
#define TRDP_MAX_DATASET_RECURSION 15 /**< limit the hierarchy of datasets. This is an arbitrary value  */
95
96
14
#define PROTO_TAG_TRDP "TRDP"
97
14
#define PROTO_NAME_TRDP "Train Real Time Data Protocol"
98
14
#define PROTO_DISSECTORNAME_TRDP "TRDP"
99
14
#define PROTO_DISSECTORNAME_TRDPTCP "TRDP.tcp"
100
14
#define PROTO_FILTERNAME_TRDP "trdp"
101
0
#define PROTO_FILTERNAME_TRDP_PDU PROTO_FILTERNAME_TRDP ".pdu"
102
103
#define WS_LOG_DOMAIN PROTO_TAG_TRDP
104
105
34
#define TRDP_HEADER_OFFSET_SEQCNT 0
106
68
#define TRDP_HEADER_OFFSET_PROTOVER 4
107
72
#define TRDP_HEADER_OFFSET_TYPE 6
108
72
#define TRDP_HEADER_OFFSET_COMID 8
109
34
#define TRDP_HEADER_OFFSET_ETB_TOPOCNT 12
110
34
#define TRDP_HEADER_OFFSET_OP_TRN_TOPOCNT 16
111
68
#define TRDP_HEADER_OFFSET_DATASETLENGTH 20
112
113
40
#define TRDP_HEADER_PD_OFFSET_RESERVED 24
114
3
#define TRDP_HEADER_PD_OFFSET_REPLY_COMID 28
115
3
#define TRDP_HEADER_PD_OFFSET_REPLY_IPADDR 32
116
3
#define TRDP_HEADER_PD_OFFSET_FCSHEAD 36
117
0
#define TRDP_HEADER_PD_OFFSET_DATA 40
118
119
0
#define TRDP_HEADER_MD_OFFSET_REPLY_STATUS 24
120
0
#define TRDP_HEADER_MD_SESSIONID 28
121
0
#define TRDP_HEADER_MD_REPLY_TIMEOUT 44
122
0
#define TRDP_HEADER_MD_SRC_URI 48
123
0
#define TRDP_HEADER_MD_DEST_URI 80
124
0
#define TRDP_HEADER_MD_OFFSET_FCSHEAD 112
125
59
#define TRDP_HEADER_MD_OFFSET_DATA 116
126
127
59
#define TRDP_MD_HEADERLENGTH TRDP_HEADER_MD_OFFSET_DATA
128
129
#define TRDP_FCS_LENGTH 4
130
#define TRDP_SC32_LENGTH 4
131
132
typedef enum {
133
/* basically copied from GMarkup */
134
    XML_BAD_UTF8,
135
    XML_EMPTY,
136
    XML_PARSE,
137
    XML_UNKNOWN_ELEMENT,
138
    XML_UNKNOWN_ATTRIBUTE,
139
    XML_INVALID_CONTENT,
140
    XML_INTERNAL
141
} XmlErrorCode;
142
/*******************************************************************************
143
 * CLASS Definition
144
 */
145
146
typedef struct collectedParameter{
147
    char* ifName; /* info, interface name from bus-def */
148
    char* hostIp; /* as defined in xml, should be identical to captured packet */
149
    char* leadIp; /* leader ip, if this is the follower declaration */
150
/* time-monitoring not yet implemented */
151
    char* pdComTo;
152
    char* mdComConfirmTo;
153
    char* mdComReplyTo;
154
    char* pdTo;   /* pd timeout in µs */
155
    char* cycle;  /* pd transmission cycle */
156
    char* mdConfirmTo; /* md confirmation timeout in µs */
157
    char* mdReplyTo; /* md reply timeout in µs */
158
/* */
159
    char* uri;    /* dst: destination uri, !dst: if set filter for source uri */
160
    char* smi;    /* SMI of telegram */
161
    char* udv;    /* userdata version 0<udv<256, in the packet udv=1 -> 0x0100 --BE-> 00 01 */
162
    char* uri2;   /* !dst-only, filter for redundancy device */
163
    char* smi2;   /* !dst-only, SMI for message from follower */
164
} XMLCollectedPar;
165
166
/* Assistant type to cater the type duality of a BITSET8 */
167
typedef struct ElementType {
168
    char name[64];
169
    uint32_t id;
170
    uint32_t subtype;
171
} ElementType;
172
173
typedef struct Bit {
174
    char name[32];  /**< Name of the element, maybe a stringified index within the dataset, never NULL */
175
    int  hf_id;
176
    int  ett_id;
177
} Bit;
178
179
typedef struct Element {
180
    /* R/O */
181
    char *name;  /**< Name of the element, maybe a stringified index within the dataset, never NULL */
182
    char *unit;  /**< Unit to display, may point to an empty string */
183
    struct knownUnit {
184
        bool fold0, hide0, sc32, version, ssc;
185
    } isUnit;
186
187
    /*public:*/
188
189
    ElementType type; /**< Numeric type of the variable (see Usermanual, chapter 4.2) or defined at ::TRDP_BOOL8, ::TRDP_UINT8, ::TRDP_UINT16 and so on, and its typeName[1..30]*/
190
191
    int32_t     array_size; /**< Amount this value occurred. 1 is default; 0 indicates a dynamic list (the dynamic list is preceded by an integer revealing the actual size.) */
192
    double      scale;      /**< A factor the given value is scaled */
193
    int32_t     offset;     /**< Offset that is added to the values. displayed value = scale * raw value + offset */
194
195
    Bit*        bits;       /**< Array of bit-name definitions. Only allocated for bitsets */
196
    int32_t     bitindex;   /**< target for next to write bit, if it has no explicit position */
197
    int **      bitfields;  /**< Array of bit-hf refs. Only allocated for bitsets */
198
    int         bits_ett_id;
199
200
    int32_t     width; /**< Contains the Element's size as returned by trdp_dissect_width(this->type) */
201
    struct Dataset *linkedDS; /**< points to DS for non-standard types */
202
    int         hf_id;
203
    int         ett_id;
204
    struct Element *next;
205
206
} Element;
207
208
/** @class Dataset
209
 *  @brief Description of one dataset.
210
 */
211
typedef struct Dataset {
212
    /* private */
213
    int32_t  size;            /**< Cached size of Dataset, including subsets. negative, if size cannot be calculated due to a missing/broken sub-dataset definition, 0, if contains var-array and must be recalculated */
214
    ElementType type;         /**< Description of the dataset, maybe stringified datasetId, never NULL */
215
                              /**< Unique identification of one dataset */
216
217
    /* public */
218
    int      ett_id;          /**< GUI-id for packet subtree */
219
    int      duplicates;      /**< incremented on multiple instances */
220
    char     *source;         /**< file name of first appearance for debugging */
221
222
    struct Element *listOfElements; /**< All elements, this dataset consists of. */
223
    struct Element *lastOfElements; /**< other end of the Bratwurst */
224
    struct Dataset *next;    /**< next dataset in linked list */
225
} Dataset;
226
227
typedef struct Connection {
228
    char*    name;       /**< name given in XML, may be an empty string, never NULL */
229
    address src, dst;
230
    uint32_t src_raw, dst_raw; /**< IPv4 only, referenced in address structs */
231
232
    uint16_t udv;      /**< userDataVersion, should match what's in the VDP_TRAILER */
233
    uint32_t smi;      /**< safeMessageIdentifier, sourced from the xml-config */
234
    uint32_t sequence; /**< this would have to be reset per dissection */
235
    //char uuid[16];   /**< not used here, it's either zeros or something application specific. set in prefs */
236
237
    struct Connection* next;
238
} Connection;
239
240
/** @class ComId
241
 *
242
 *  @brief This struct makes a mapping between one comId and one dataset.
243
 *
244
 */
245
typedef struct ComId {
246
    char*    name;       /**< name given in XML, may be an empty string, never NULL */
247
248
    /* public: */
249
    uint32_t comId;      /**< Communication Id, used as key*/
250
    ElementType dataset; /**< Id for a dataset ( @link #Dataset see Dataset structure @endlink) */
251
    int32_t  size;       /**< cached size derived from linked dataset */
252
    int      ett_id;     /**< GUI-id for root-subtree */
253
    int      duplicates; /**< incremented on multiple instances */
254
    char*    source;     /**< file name of first appearance for debugging */
255
256
    struct Connection* con;   /**< */
257
    struct Dataset* linkedDS; /**< cached dataset for id in #dataset */
258
    struct ComId*   next;     /**< next comId item in linked list */
259
} ComId;
260
261
typedef struct TrdpXmlContext {
262
    const char* currentFile;  /**< name of currently parsed file */
263
    bool        isShippedXml; /**< mark datasets when shipped to be displayed accordingly */
264
    GError**    error;
265
266
    xmlTextReader* reader;    /**< will be acquired on first call and reused */
267
} TrdpXmlContext;
268
269
/** @struct TrdpDict
270
 *
271
 *  @brief This struct is the root container for the type dictionary read from XML
272
 *
273
 *  The old QtXML-based application used hash-tables instead of lists.
274
 *  GLib offers GHashTable as an alternative.
275
 *  However, once the structure is built, there are not that many look-ups, since Datasets and Elements are directly linked.
276
 *  Only in case of large ComId databases, this would become relevant again. Mañana, mañana ...
277
 */
278
typedef struct TrdpDict {
279
    TrdpXmlContext parseCtx;
280
281
    /* pub */
282
    struct Dataset *mTableDataset; /**< first item of linked list of Dataset items. Use it to iterate if necessary or use TrdpDict_get_Dataset for a pointer. */
283
284
    /* pub-R/O */
285
    struct Dataset *mCyclicDataset;/**< on dict creation, this is set, if a Dataset causes cyclic recursion. It is an internal error flag.  */
286
    size_t maxDatasetDepth;      /**< stats, maximum depth. if >TRDP_MAX_DATASET_RECURSION, this is an error indication */
287
    unsigned int   knowledge;    /**< number of found ComIds */
288
    unsigned int   datasets;     /**< number of Datasets */
289
    struct ComId  *mTableComId;  /**< first item of linked list of ComId items. Use it to iterate if necessary or use TrdpDict_lookup_ComId for a pointer. */
290
} TrdpDict;
291
292
293
/* this is a construct, to point empty strings (name, unit, ...) to this const
294
 * instead of NULL, so readers don't have to catch for NULL - though w/o
295
 * wasting needless heap allocations.
296
 */
297
static const char* const SEMPTY = "";
298
static uint8_t cstUUID[16] = {0};
299
static bool    have_cstUUID = false;
300
static bool invalid_cstUUID = false;
301
static GQuark q_xml;
302
static wmem_allocator_t *wmem_dic;
303
304
/* these settings are also used on dictionary parsing */
305
static int g_uuid_subtype = TRDP_ENDSUBTYPE_BIG;
306
static int g_bitset_subtype = TRDP_BITSUBTYPE_BOOL8;
307
static int g_endian_subtype = TRDP_ENDSUBTYPE_BIG;
308
static int g_wchar_subtype = TRDP_ENDSUBTYPE_BIG;
309
310
/*******************************************************************************
311
 * XML-DEFINES
312
 */
313
314
37
#define FALLBACK(a,b) ((a)?(a):(b))
315
316
#define FILE_SUFFIX_LOWCASE ".xml"
317
#define FILE_SUFFIX_UPCASE ".XML"
318
319
320
/*******************************************************************************
321
 * some Definitions for "early" use internal functions
322
 * actually, this triple calls each other after XML parsing to check and build
323
 * the whole dict-tree
324
 */
325
326
static Dataset* TrdpDict_get_Dataset(const TrdpDict* self, ElementType *dataset);
327
static ComId* TrdpDict_lookup_ComId(const TrdpDict* self, uint32_t comId);
328
static int32_t ComId_preCalculate(ComId* self, TrdpDict* dict);
329
static void ComId_connection_parse(ComId* com, const char* name, bool dst, XMLCollectedPar* par, GError** error);
330
static int32_t Dataset_preCalculate(Dataset* self, TrdpDict* dict, Dataset** hierarchyStack, size_t depth);
331
static bool Element_checkConsistency(Element* self, TrdpDict* dict, Dataset** hierarchyStack, size_t depth);
332
static void Element_add_bit(Element* self, const char* name, const char* _position, int32_t position, GError** err);
333
334
static Element* Element_new(const char* _type, const char* _name, const char* _unit, const char* _array_size,
335
                            const char* _scale, const char* _offset, const char* _bitnames,
336
                            unsigned int cnt, GError** error);
337
static Dataset* Dataset_new(const char* dsId, const char* aname, const char* filename, GError** error);
338
static ComId* ComId_new(const char* id, const char* aname, const char* dsId, const char* filename, GError** error);
339
340
static bool Element_equals(const Element* self, const Element* other, GError** error);
341
static bool Dataset_equals(const Dataset* self, const Dataset* other, GError** error);
342
static bool ComId_equals(const ComId* self, const ComId* other, GError** error);
343
344
static void TrdpDict_delete(TrdpDict* self, int parent_id);
345
static void Element_delete(Element* self);
346
static void Dataset_delete(Dataset* self, int parent_id);
347
static void ComId_delete(ComId* self);
348
static void XMLCollectedPar_delete(XMLCollectedPar* self);
349
350
/*******************************************************************************
351
 * type lookup handler
352
 *
353
 * there may be better library structures to do string to ID and vice versa.
354
 * Though, this will also work
355
 */
356
357
static const ElementType ElBasics[] = {
358
    { "", 0, 0 },
359
    { "BITSET8", TRDP_BITSET8, TRDP_BITSUBTYPE_BITSET8 },
360
    { "BOOL8", TRDP_BITSET8, TRDP_BITSUBTYPE_BOOL8 },
361
    { "ANTIVALENT8", TRDP_BITSET8, TRDP_BITSUBTYPE_ANTIVALENT8 },
362
    { "CHAR8", TRDP_CHAR8, 0 },
363
    { "UTF16", TRDP_UTF16, TRDP_ENDSUBTYPE_BIG },
364
    { "UTF16_LE", TRDP_UTF16, TRDP_ENDSUBTYPE_LIT },
365
    { "INT8", TRDP_INT8, 0 },
366
    { "INT16", TRDP_INT16, TRDP_ENDSUBTYPE_BIG },
367
    { "INT16_LE", TRDP_INT16, TRDP_ENDSUBTYPE_LIT },
368
    { "INT32", TRDP_INT32, TRDP_ENDSUBTYPE_BIG },
369
    { "INT32_LE", TRDP_INT32, TRDP_ENDSUBTYPE_LIT },
370
    { "INT64", TRDP_INT64, TRDP_ENDSUBTYPE_BIG },
371
    { "INT64_LE", TRDP_INT64, TRDP_ENDSUBTYPE_LIT },
372
    { "UINT8", TRDP_UINT8, 0 },
373
    { "UINT16", TRDP_UINT16, TRDP_ENDSUBTYPE_BIG },
374
    { "UINT16_LE", TRDP_UINT16, TRDP_ENDSUBTYPE_LIT },
375
    { "UINT32", TRDP_UINT32, TRDP_ENDSUBTYPE_BIG },
376
    { "UINT32_LE", TRDP_UINT32, TRDP_ENDSUBTYPE_LIT },
377
    { "UINT64", TRDP_UINT64, TRDP_ENDSUBTYPE_BIG },
378
    { "UINT64_LE", TRDP_UINT64, TRDP_ENDSUBTYPE_LIT },
379
    { "REAL32", TRDP_REAL32, TRDP_ENDSUBTYPE_BIG },
380
    { "REAL32_LE", TRDP_REAL32, TRDP_ENDSUBTYPE_LIT },
381
    { "REAL64", TRDP_REAL64, TRDP_ENDSUBTYPE_BIG },
382
    { "REAL64_LE", TRDP_REAL64, TRDP_ENDSUBTYPE_LIT },
383
    { "TIMEDATE32", TRDP_TIMEDATE32, 0 },
384
    { "TIMEDATE48", TRDP_TIMEDATE48, 0 },
385
    { "TIMEDATE64", TRDP_TIMEDATE64, 0 },
386
    { "UUID", TRDP_UUID, TRDP_ENDSUBTYPE_BIG },
387
    { "UUID_LE", TRDP_UUID, TRDP_ENDSUBTYPE_LIT },
388
};
389
390
391
/* this parse part tries to allow quite a lot of input slob in terms of id and name */
392
393
0
static bool TrdpDict_parseType(const char* _id, const char* _name, ElementType* type, GError** error) {
394
0
    uint32_t id = 0;
395
0
    char *endptr=NULL;
396
0
    if (!type) return FALSE;
397
398
    /* try to parse a type based on a number */
399
0
    if (_id && *_id) {
400
0
        id = (uint32_t)g_ascii_strtoull(_id, &endptr, 10);
401
0
        if (*endptr!='\0') id=0; /* reset half-way numbers */
402
0
    }
403
    /* fail in case of an invalid number */
404
0
    if (!((_id && *_id) || (_name && *_name)) || (!id && (endptr != _id))) { /* fail if not a complete number */
405
0
        g_set_error(error, q_xml, XML_INVALID_CONTENT, // error code
406
0
                    "id=\"%s\" What is this? ID definition was unparsable (%s).", _id, g_strerror(61));
407
0
        return FALSE;
408
0
    }
409
    /* check, whether it's a known type in an element definition */
410
0
    if (!id && !_name) {
411
0
        for (size_t i = 0; i < array_length(ElBasics); i++) {
412
0
            if (0 == g_ascii_strcasecmp(_id, ElBasics[i].name)) {
413
0
                *type = ElBasics[i];
414
0
                return TRUE;
415
0
            }
416
0
        }
417
0
    }
418
419
0
    type->id = id;
420
    /* _name is only set in dataset calls and in that case only _name or _id can be used */
421
0
    snprintf(type->name, sizeof(type->name), "%s", (_name && *_name)?_name:_id);
422
    //if (!memccpy(type->name, (_name && *_name)?_name:_id, 0, sizeof(type->name))) type->name[sizeof(type->name)-1] = '\0';
423
424
0
    bool isNumber = (id >= TRDP_INT8) && (id <= TRDP_REAL64);
425
0
    bool isUUID   = (id == TRDP_UUID);
426
0
    bool isBit    = (id == TRDP_BITSET8);
427
0
    bool isWChar  = (id == TRDP_UTF16);
428
0
    type->subtype = isBit ? g_bitset_subtype : (isNumber ? g_endian_subtype : (isUUID ? g_uuid_subtype : (isWChar ? g_wchar_subtype : 0)));
429
0
    if (id>0 && id<=TRDP_STANDARDTYPE_MAX) {
430
0
        for (unsigned int i = 1; type && i < array_length(ElBasics); i++) {
431
0
            if (type->id == ElBasics[i].id && type->subtype == ElBasics[i].subtype) {
432
0
                *type = ElBasics[i];
433
0
                break;
434
0
            }
435
0
        }
436
0
    }
437
0
    return TRUE;
438
0
}
439
440
0
static void XML_translate_element(TrdpDict* self, xmlNode* node, unsigned int* cnt, GError** error) {
441
0
    GError* err=NULL;
442
443
0
    if (node) {
444
445
0
        xmlChar *type = xmlGetProp(node, (const xmlChar *)"type");
446
0
        xmlChar *name = xmlGetProp(node, (const xmlChar *)"name");
447
0
        xmlChar *unit = xmlGetProp(node, (const xmlChar *)"unit");
448
0
        xmlChar *array_size = xmlGetProp(node, (const xmlChar *)"array-size");
449
0
        xmlChar *scale = xmlGetProp(node, (const xmlChar *)"scale");
450
0
        xmlChar *offset = xmlGetProp(node, (const xmlChar *)"offset");
451
0
        xmlChar *bits = xmlGetProp(node, (const xmlChar *)"bits");
452
0
        Element* el = Element_new(
453
0
            (const char*)type, (const char*)name, (const char*)unit, (const char*)array_size,
454
0
            (const char*)scale, (const char*)offset, (const char*)bits,
455
0
            ++(*cnt),
456
0
            &err);
457
458
0
        xmlFree(type);
459
0
        xmlFree(name);
460
0
        xmlFree(unit);
461
0
        xmlFree(array_size);
462
0
        xmlFree(scale);
463
0
        xmlFree(offset);
464
0
        xmlFree(bits);
465
466
0
        if (el) {
467
            /* update the element in the list */
468
0
            if (!self->mTableDataset->listOfElements)
469
0
                self->mTableDataset->listOfElements = el;
470
0
            else
471
0
                self->mTableDataset->lastOfElements->next = el;
472
0
            self->mTableDataset->lastOfElements = el;
473
474
            /* read additional bit-elements, do it inline here */
475
0
            xmlNode* bit = xmlFirstElementChild(node);
476
0
            while (bit && !err) {
477
0
                char* bit_name = (char*)xmlGetProp(bit, (const xmlChar*)"name");
478
0
                char* position = (char*)xmlGetProp(bit, (const xmlChar*)"position");
479
0
                Element_add_bit(el, bit_name, position, -1, &err);
480
0
                xmlFree(bit_name);
481
0
                xmlFree(position);
482
483
0
                bit = xmlNextElementSibling(bit);
484
0
            }
485
0
        }
486
0
        if (err) g_propagate_error(error, err);
487
0
    }
488
0
}
489
490
0
static void XML_translate_dataset(TrdpDict* self, xmlNode* node, GError** error) {
491
0
    GError* err=NULL;
492
0
    unsigned int element_cnt = 0;
493
494
0
    if (node) {
495
0
        xmlChar* id = xmlGetProp(node, (const xmlChar *)"id");
496
0
        xmlChar* name = xmlGetProp(node, (const xmlChar *)"name");
497
0
        Dataset* ds = Dataset_new(
498
0
            (const char*)id, (const char*)name,
499
0
            self->parseCtx.isShippedXml ? NULL : self->parseCtx.currentFile,
500
0
            &err);
501
0
        xmlFree(id);
502
0
        xmlFree(name);
503
504
0
        if (ds) {
505
0
            ds->next = self->mTableDataset;
506
0
            self->mTableDataset = ds;
507
0
            self->datasets++;
508
0
            element_cnt = 0;
509
            /* we need to duplicate-check at the end */
510
511
0
            xmlNode* element = xmlFirstElementChild(node);
512
0
            while (element && !err) {
513
0
                XML_translate_element(self, element, &element_cnt, &err);
514
0
                element = xmlNextElementSibling(element);
515
0
            }
516
0
            if (!err) {
517
0
                Dataset* newest = self->mTableDataset;
518
0
                self->mTableDataset = newest->next; /* unqueue the newest Dataset for a sec */
519
0
                Dataset* preExists = TrdpDict_get_Dataset(self, &newest->type);
520
0
                if (preExists) {
521
0
                    if (!Dataset_equals(preExists, newest, &err))
522
0
                        g_propagate_error(error, err);
523
0
                    preExists->duplicates++;
524
0
                    Dataset_delete(newest, -1);
525
0
                    self->datasets--;
526
0
                } else {
527
0
                    self->mTableDataset = newest;
528
0
                }
529
0
            }
530
0
        }
531
0
        if (err) g_propagate_error(error, err);
532
0
    }
533
0
}
534
535
0
static void XML_translate_com_connection(TrdpDict* self, ComId* com, xmlNode* node, XMLCollectedPar* par, GError** error) {
536
0
    bool dst;
537
538
0
    if (!self || !com || !node || !par) return;
539
540
0
    if        (0==xmlStrcmp(node->name, (const xmlChar *)"source")) {
541
0
        dst = false;
542
0
    } else if (0==xmlStrcmp(node->name, (const xmlChar *)"destination")) {
543
0
        dst = true;
544
0
    } else {
545
        /*skip*/
546
0
        return;
547
0
    }
548
549
0
    xmlFree(par->uri);
550
0
    xmlFree(par->uri2);
551
0
    par->uri  = (char*)xmlGetProp(node, (const xmlChar *)(dst?"uri":"uri1"));
552
0
    par->uri2 = (char*)(dst ? xmlGetProp(node, (const xmlChar *)"uri2") : NULL);
553
554
0
    xmlNode* sdt_par = xmlFirstElementChild(node);
555
0
    xmlFree(par->smi);
556
0
    xmlFree(par->smi2);
557
0
    xmlFree(par->udv);
558
0
    if (sdt_par && (0==xmlStrcmp(sdt_par->name, (const xmlChar *)"sdt-parameter"))) {
559
0
        par->smi  = (char*)xmlGetProp(sdt_par, (const xmlChar *)"smi1");
560
0
        par->smi2 = (char*)xmlGetProp(sdt_par, (const xmlChar *)"smi2");
561
0
        par->udv  = (char*)xmlGetProp(sdt_par, (const xmlChar *)"udv");
562
0
    } else {
563
0
        par->smi=NULL;
564
0
        par->smi2=NULL;
565
0
        par->udv=NULL;
566
0
    }
567
0
    if (xmlNextElementSibling(sdt_par)) {
568
0
        g_set_error(error, q_xml, XML_INVALID_CONTENT, "Extra SDT-Parameter elements in XML.");
569
0
        return;
570
0
    }
571
572
0
    char* name = (char*)xmlGetProp(node, (const xmlChar*)"name");
573
0
    ComId_connection_parse(com, name, dst, par, error);
574
0
    xmlFree(name);
575
0
}
576
577
0
static void XML_translate_com(TrdpDict* self, xmlNode* node, XMLCollectedPar* par, GError** error) {
578
0
    GError* err=NULL;
579
580
0
    if (node) {
581
0
        xmlChar *com_id = xmlGetProp(node, (const xmlChar *)"com-id");
582
0
        xmlChar *name = xmlGetProp(node, (const xmlChar *)"name");
583
0
        xmlChar *data_set_id = xmlGetProp(node, (const xmlChar *)"data-set-id");
584
0
        ComId* com = ComId_new(
585
0
            (char*)com_id, (char*)name, (char*)data_set_id,
586
0
            self->parseCtx.currentFile,
587
0
            &err);
588
0
        xmlFree(com_id);
589
0
        xmlFree(name);
590
0
        xmlFree(data_set_id);
591
592
0
        if (com && !err) {
593
594
            /* check for an existing duplicate in terms of com-ID */
595
0
            ComId* com2 = TrdpDict_lookup_ComId(self, com->comId);
596
0
            if (!com2 && !err) {
597
0
                com->next = self->mTableComId;
598
0
                self->mTableComId = com;
599
0
                self->knowledge++;
600
0
            } else {
601
0
                if (!err) ComId_equals(com2, com, &err);
602
603
0
                com2->duplicates++;
604
0
                ComId_delete(com);
605
0
                com = !err ? com2 : NULL;
606
0
            }
607
608
0
            xmlNode* compar = xmlFirstElementChild(node);
609
0
            while (compar && !err) {
610
0
                char* s;
611
0
                if (0==xmlStrcmp(compar->name, (const xmlChar *)"pd-parameter")) {
612
0
                    if ((s = (char*)xmlGetProp(compar, (const xmlChar *)"timeout"))) {
613
0
                        if (!par->pdTo       ) {
614
0
                            par->pdTo        = s;
615
0
                        } else {
616
0
                            g_set_error(&err, q_xml, XML_INVALID_CONTENT, "Extra pd-parameter element.");
617
0
                            xmlFree(s);
618
0
                        }
619
0
                    }
620
0
                    if ((s = (char*)xmlGetProp(compar, (const xmlChar *)"cycle"))) {
621
0
                        if (!par->cycle      ) {
622
0
                            par->cycle       = s;
623
0
                        } else {
624
0
                            g_set_error(&err, q_xml, XML_INVALID_CONTENT, "Extra pd-parameter element.");
625
0
                            xmlFree(s);
626
0
                        }
627
0
                    }
628
0
                }
629
0
                if (0==xmlStrcmp(compar->name, (const xmlChar *)"md-parameter")) {
630
0
                    if ((s = (char*)xmlGetProp(compar, (const xmlChar *)"confirm-timeout"))) {
631
0
                        if (!par->mdConfirmTo) {
632
0
                            par->mdConfirmTo = s;
633
0
                        } else {
634
0
                            g_set_error(&err, q_xml, XML_INVALID_CONTENT, "Extra md-parameter element.");
635
0
                            xmlFree(s);
636
0
                        }
637
0
                    }
638
0
                    if ((s = (char*)xmlGetProp(compar, (const xmlChar *)"reply-timeout"))) {
639
0
                        if (!par->mdReplyTo  ) {
640
0
                            par->mdReplyTo   = s;
641
0
                        } else {
642
0
                            g_set_error(&err, q_xml, XML_INVALID_CONTENT, "Extra md-parameter element.");
643
0
                            xmlFree(s);
644
0
                        }
645
0
                    }
646
0
                }
647
0
                compar = xmlNextElementSibling(compar);
648
0
            }
649
            /* only take the common params, if there are no specific */
650
0
            if (!par->pdTo       ) par->pdTo =        par->pdComTo;
651
0
            if (!par->mdConfirmTo) par->mdConfirmTo = par->mdComConfirmTo;
652
0
            if (!par->mdReplyTo  ) par->mdReplyTo =   par->mdComReplyTo;
653
654
0
            xmlNode* srcdst = xmlFirstElementChild(node);
655
0
            while (srcdst && !err) {
656
0
                XML_translate_com_connection(self, com, srcdst, par, error);
657
0
                srcdst = xmlNextElementSibling(srcdst);
658
0
            }
659
660
0
            xmlFree(par->cycle);
661
0
            par->cycle = NULL;
662
            /* Don't free anything that is a shallow copy of another param */
663
0
            if (par->pdTo != par->pdComTo)
664
0
                xmlFree(par->pdTo);
665
0
            par->pdTo = NULL;
666
0
            if (par->mdConfirmTo != par->mdComConfirmTo)
667
0
                xmlFree(par->mdConfirmTo);
668
0
            par->mdConfirmTo = NULL;
669
0
            if (par->mdReplyTo != par->mdComReplyTo)
670
0
                xmlFree(par->mdReplyTo);
671
0
            par->mdReplyTo = NULL;
672
0
        }
673
0
        if (err) g_propagate_error(error, err);
674
0
    }
675
0
}
676
677
0
static void XML_translate(TrdpDict* self, xmlDoc* doc, GError** error) {
678
679
0
    xmlNode* device = doc->children; /* can be only be the device element by known XPath-Expr. */
680
0
    xmlNode* node = xmlFirstElementChild(device);
681
0
    while (node && !*error) {
682
0
        if (0==xmlStrcmp(node->name, (const xmlChar *)"bus-interface-list")) {
683
0
            xmlNode* busInterface = xmlFirstElementChild(node);
684
0
            while (busInterface && !*error) {
685
0
                XMLCollectedPar par = {
686
0
                    .ifName = (char*)xmlGetProp(busInterface, (const xmlChar *)"name"),
687
0
                    .hostIp = (char*)xmlGetProp(busInterface, (const xmlChar *)"host-ip"),
688
0
                    .leadIp = (char*)xmlGetProp(busInterface, (const xmlChar *)"leader-ip"),
689
0
                    NULL,};
690
691
0
                xmlNode* comParam = xmlFirstElementChild(busInterface);
692
0
                while (comParam && !*error) {
693
0
                    char* s;
694
0
                    if (0==xmlStrcmp(comParam->name, (const xmlChar *)"pd-com-parameter")) {
695
0
                        if ((s = (char*)xmlGetProp(comParam, (const xmlChar *)"timeout-value"))) {
696
0
                            if (!par.pdComTo) {
697
0
                                par.pdComTo        = s;
698
0
                            } else {
699
0
                                g_set_error(error, q_xml, XML_INVALID_CONTENT, "Extra pd-com-parameter element.");
700
0
                                xmlFree(s);
701
0
                            }
702
0
                        }
703
0
                    }
704
0
                    if (0==xmlStrcmp(comParam->name, (const xmlChar *)"md-com-parameter")) {
705
0
                        if ((s = (char*)xmlGetProp(comParam, (const xmlChar *)"confirm-timeout"))) {
706
0
                            if (!par.mdComConfirmTo) {
707
0
                                par.mdComConfirmTo = s;
708
0
                            } else {
709
0
                                g_set_error(error, q_xml, XML_INVALID_CONTENT, "Extra md-com-parameter element.");
710
0
                                xmlFree(s);
711
0
                            }
712
0
                        }
713
0
                        if ((s = (char*)xmlGetProp(comParam, (const xmlChar *)"reply-timeout"))) {
714
0
                            if (!par.mdComReplyTo) {
715
0
                                par.mdComReplyTo   = s;
716
0
                            } else {
717
0
                                g_set_error(error, q_xml, XML_INVALID_CONTENT, "Extra md-com-parameter element.");
718
0
                                xmlFree(s);
719
0
                            }
720
0
                        }
721
0
                    }
722
0
                    comParam = xmlNextElementSibling(comParam);
723
0
                }
724
725
0
                xmlNode* telegram = xmlFirstElementChild(busInterface);
726
0
                while (telegram && !*error) {
727
0
                    if (0==xmlStrcmp(telegram->name, (const xmlChar *)"telegram")) {
728
0
                        XML_translate_com(self, telegram, &par, error);
729
0
                    }
730
0
                    telegram = xmlNextElementSibling(telegram);
731
0
                }
732
733
0
                busInterface = xmlNextElementSibling(busInterface);
734
0
                XMLCollectedPar_delete(&par);
735
0
            }
736
0
        } else if (0==xmlStrcmp(node->name, (const xmlChar *)"data-set-list")) {
737
0
            xmlNode* dataSet = xmlFirstElementChild(node);
738
0
            while (dataSet && !*error) {
739
0
                XML_translate_dataset(self, dataSet, error);
740
0
                dataSet = xmlNextElementSibling(dataSet);
741
0
            }
742
0
        }
743
0
        node = xmlNextElementSibling(node);
744
0
    }
745
0
}
746
747
#if LIBXML_VERSION < 21200
748
static void XML_errorHook(void *user_data, xmlError *xmlerror) {
749
#else
750
0
static void XML_errorHook(void *user_data, const xmlError *xmlerror) {
751
0
#endif
752
0
    g_set_error_literal((GError **)user_data, q_xml, xmlerror ? xmlerror->code : 0, xmlerror ? xmlerror->message : "[NULL]");
753
0
}
754
755
/*******************************************************************************
756
 * TrdpDict functions
757
 * holds both lists (telegrams and datasets) and takes care of parsing and
758
 * cleanup.
759
 */
760
761
0
#define TRDP_XML_PATHS (xmlChar*)\
762
0
    "/device/bus-interface-list/bus-interface/pd-com-parameter|"\
763
0
    "/device/bus-interface-list/bus-interface/md-com-parameter|"\
764
0
    "/device/bus-interface-list/bus-interface/telegram|"\
765
0
    "/device/bus-interface-list/bus-interface/telegram/pd-parameter|"\
766
0
    "/device/bus-interface-list/bus-interface/telegram/md-parameter|"\
767
0
    "/device/bus-interface-list/bus-interface/telegram/destination/sdt-parameter|"\
768
0
    "/device/bus-interface-list/bus-interface/telegram/source/sdt-parameter|"\
769
0
    "/device/data-set-list/data-set/element|"\
770
0
    "/device/data-set-list/data-set/element/bit"
771
772
static void TrdpDict_parseXML(TrdpDict* self, const char* filename, bool isShipped, GError** error )
773
0
{
774
0
    xmlDoc *doc;
775
0
    int options = XML_PARSE_NOENT | XML_PARSE_COMPACT;
776
0
    self->parseCtx.currentFile = filename;
777
0
    self->parseCtx.isShippedXml = isShipped;
778
0
    self->parseCtx.error = error;
779
0
    if (!self->parseCtx.reader) {
780
0
        if (!(self->parseCtx.reader = xmlReaderForFile(filename, NULL, options))) {
781
0
            g_set_error(error, q_xml, XML_INTERNAL, "Failed to create XML reader.");
782
0
            return;
783
0
        }
784
0
        xmlTextReaderSetStructuredErrorHandler(self->parseCtx.reader, XML_errorHook, self);
785
0
    } else {
786
0
        if (xmlReaderNewFile(self->parseCtx.reader, filename, NULL, options) < 0) {
787
0
            g_set_error(error, q_xml, XML_INTERNAL, "Failed to switch file in XML reader.");
788
0
            return;
789
0
        }
790
0
    }
791
0
    xmlTextReaderPreservePattern(self->parseCtx.reader, TRDP_XML_PATHS, NULL);
792
793
0
    int ret = xmlTextReaderRead(self->parseCtx.reader);
794
0
    while (ret == 1 && !*error) {
795
        //xmlTextReaderDepth(self->parseCtx.reader);
796
        //xmlNode* node = xmlTextReaderExpand(self->parseCtx.reader);
797
0
        ret = xmlTextReaderRead(self->parseCtx.reader);
798
0
    }
799
0
    if (ret < 0 || !(doc = xmlTextReaderCurrentDoc(self->parseCtx.reader)) ) {
800
0
        g_set_error(error, q_xml, XML_PARSE, "Failed to parse the file, but I don't know what happened.");
801
0
    } else {
802
0
        XML_translate(self, doc, error);
803
0
    }
804
    /* do not xmlFreeDoc(doc), it's weirdly implemented. The xmlreader does not notice and will double free later */
805
0
}
806
807
static bool TrdpDict_check(TrdpDict* self, const char* baseXmlConfig, const char* customXmlConfig, GError** error)
808
2
{
809
2
    ComId* com;
810
811
2
    if (!self->knowledge) {
812
        /* wrap up and summarize after all XMLs did not provide anything */
813
2
        if (baseXmlConfig && customXmlConfig && *customXmlConfig)
814
0
            g_set_error(error, q_xml, XML_INVALID_CONTENT,
815
0
                        "%s parsed ok, but did not provide any ComId.", customXmlConfig);
816
2
    } else {
817
818
        /* try to construct the com-id -- datasets - tree */
819
0
        for( com = self->mTableComId; com; com=com->next) {
820
0
            if (ComId_preCalculate(com, self) < 0) break; /* oops, logical inconsistency */
821
0
        }
822
823
        /* catch some parse faults and turn them into error messages */
824
0
        if (self->mCyclicDataset) {
825
0
            g_set_error(error, q_xml, XML_INVALID_CONTENT, // error code
826
0
                        "Checks detected a cyclic recursion of datasets. Check references of %d (%s)", self->mCyclicDataset->type.id, self->mCyclicDataset->type.name);
827
0
            self->knowledge = 0;
828
0
        } else if (self->maxDatasetDepth > TRDP_MAX_DATASET_RECURSION) {
829
0
            g_set_error(error, q_xml, XML_INVALID_CONTENT, // error code
830
0
                        "Final dictionary violates the max level (%d) of dataset hierarchies.", TRDP_MAX_DATASET_RECURSION);
831
0
            self->knowledge = 0;
832
0
        } else if (com) {
833
0
            g_set_error(error, q_xml, XML_INVALID_CONTENT, // error code
834
0
                        "Lastly \"%s\" parsed ok and found %d ComIDs. However, com-ID %d FAILED to compute.", customXmlConfig, self->knowledge, com->comId);
835
0
            self->knowledge = 0;
836
0
        }
837
0
    }
838
839
2
    return !!self->knowledge;
840
2
}
841
842
/** @fn  TrdpDict *TrdpDict_new    (const char *xmlconfigFile, gint parent_id, GError **error)
843
 *
844
 *  @brief Create a new TrdpDict container
845
 *
846
 *  @param baseXmlConfig      path to included xml files, can be file or folder (all files are read)
847
 *  @param customXmlConfig    path to user-provided xml files, can be file or folder (all files are read)
848
 *  @param error              Will be set to non-null on any error.
849
 *
850
 *  @return pointer to the container or NULL on problems. See error then for the cause.
851
 */
852
static TrdpDict* TrdpDict_new(const char* baseXmlConfig, const char* customXmlConfig, GError** error)
853
2
{
854
2
    GError* err2 = NULL;
855
2
    bool isShippedXml = !!baseXmlConfig;
856
2
    const char* xmlConfig = isShippedXml ? baseXmlConfig : customXmlConfig;
857
2
    TrdpDict* self = wmem_new0(wmem_dic, TrdpDict);
858
2
    q_xml = g_quark_from_static_string("XML error");
859
860
4
    while (xmlConfig && *xmlConfig && !*error) {
861
2
        GError* err = NULL;
862
2
        GDir* dir = NULL;
863
2
        char* dirname = NULL;
864
2
        char* currentPath = NULL;
865
2
        const char* potentialNextFile = NULL;
866
867
        /* check, if path is a file or folder, check permissions */
868
869
2
        if (!g_file_test(xmlConfig, G_FILE_TEST_EXISTS)) {
870
2
            g_set_error(error, G_FILE_ERROR, G_FILE_ERROR_NOENT, // error code
871
2
                        "The configured XML-file \"%s\" could not be accessed. Check the Wireshark settings -> Protocols -> TRDP.", xmlConfig);
872
2
        } else {
873
874
0
            if (g_file_test(xmlConfig, G_FILE_TEST_IS_DIR)) {
875
0
                dirname = g_strdup(xmlConfig);
876
0
            } else {
877
0
                dirname = g_path_get_dirname(xmlConfig);
878
0
                potentialNextFile = xmlConfig + strlen(dirname) + 1;
879
0
            }
880
0
            if (!potentialNextFile || !*potentialNextFile) {
881
0
                dir = g_dir_open(dirname, 0, &err);
882
0
                if (dir) {
883
0
                    potentialNextFile = g_dir_read_name(dir);
884
0
                } else {
885
0
                    g_propagate_prefixed_error(error, err, "Entering XML source location (%s) failed.\n", dirname);
886
0
                }
887
0
            }
888
0
        }
889
890
2
        while (potentialNextFile && !*error) {
891
0
            if (!dir || g_str_has_suffix(potentialNextFile, FILE_SUFFIX_LOWCASE) || g_str_has_suffix(potentialNextFile, FILE_SUFFIX_UPCASE)) {
892
0
                currentPath = wmem_strdup_printf(wmem_dic, "%s"G_DIR_SEPARATOR_S"%s", dirname, potentialNextFile);
893
894
0
                TrdpDict_parseXML(self, currentPath, isShippedXml, &err);
895
0
                if (err)
896
0
                    g_propagate_prefixed_error(error, err, "XML Source %s:\n", isShippedXml ? currentPath : potentialNextFile);
897
898
0
                wmem_free(wmem_dic, currentPath);
899
0
            }
900
0
            potentialNextFile = dir ? g_dir_read_name(dir) : NULL;
901
902
0
        }
903
904
2
        g_free(dirname); /* dirname may come from g_path_get_dirname */
905
2
        if (dir) g_dir_close(dir);
906
2
        xmlConfig = isShippedXml ? customXmlConfig : NULL;
907
2
        isShippedXml = FALSE; /* switch the flag to signal using the custom xml */
908
2
    }
909
910
2
    xmlFreeDoc(xmlTextReaderCurrentDoc(self->parseCtx.reader));
911
2
    xmlFreeTextReader(self->parseCtx.reader);
912
2
    self->parseCtx.reader = NULL;
913
914
    /* If init was unsuccessful, clean up the whole thing */
915
2
    if (!TrdpDict_check(self, baseXmlConfig, customXmlConfig, &err2)) {
916
2
        if (err2 && !*error) g_propagate_error(error, err2);
917
2
        TrdpDict_delete(self, -1);
918
2
        self = NULL;
919
2
    }
920
921
2
    return self;
922
2
}
923
924
/** @fn  TrdpDict *TrdpDict_delete(TrdpDict *self)
925
 *
926
 *  @brief Delete the TrdpDict container
927
 *
928
 *  This will also clear all associated ComId, Dataset and Element items.
929
 *
930
 *  @param self           TrdpDict instance
931
 *  @param parent_id      The parent protocol handle (from proto_register_protocol() ).
932
 */
933
static void TrdpDict_delete(TrdpDict* self, int parent_id)
934
4
{
935
4
    if (self) {
936
2
        while (self->mTableComId) {
937
0
            ComId* com = self->mTableComId;
938
0
            self->mTableComId = self->mTableComId->next;
939
0
            ComId_delete(com); /* only removes itself, no linkedDS */
940
0
        }
941
2
        while (self->mTableDataset) {
942
0
            Dataset* ds = self->mTableDataset;
943
0
            self->mTableDataset = self->mTableDataset->next;
944
0
            Dataset_delete(ds, parent_id);
945
0
            self->datasets--;
946
0
        }
947
2
        wmem_free(wmem_dic, self);
948
2
    }
949
4
}
950
951
/** @fn  const ComId   *TrdpDict_lookup_ComId(const TrdpDict *self, uint32_t comId)
952
 *
953
 *  @brief Lookup a given comId in the dictionary self
954
 *
955
 *  You may only read on the returned item.
956
 *
957
 *  @param self  TrdpDict instance
958
 *  @param comId The number referencing the ComId item
959
 *
960
 *  @return  pointer to the ComId item in the dictionary or NULL if not found
961
 */
962
static ComId* TrdpDict_lookup_ComId(const TrdpDict* self, uint32_t comId)
963
37
{
964
37
    if (self)
965
0
        for (ComId* com = self->mTableComId; com; com = com->next)
966
0
            if (com->comId == comId)
967
0
                return com;
968
969
37
    return NULL;
970
37
}
971
972
/** @fn  Dataset *TrdpDict_get_Dataset (const TrdpDict* self, ElementType* type)
973
 *
974
 *  @brief Lookup a given datasetId in the dictionary self
975
 *
976
 *  You may read and change information on the returned item, but do not free it
977
 *
978
 *  @param self      TrdpDict instance
979
 *  @param type      The type-ref containing the number referencing the Dataset item
980
 *
981
 *  @return  pointer to the Dataset item in the dictionary or NULL if not found
982
 */
983
static Dataset* TrdpDict_get_Dataset(const TrdpDict* self, ElementType* type)
984
0
{
985
0
    if (self && type) {
986
0
        for (Dataset* ds = self->mTableDataset; ds; ds = ds->next) {
987
0
            if (type->id && (ds->type.id == type->id)) return ds;
988
0
            if (*type->name && (0 == g_ascii_strcasecmp(ds->type.name, type->name))) return ds;
989
0
        }
990
0
    }
991
0
    return NULL;
992
0
}
993
994
/************************************************************************************
995
 *                          ELEMENT
996
 ************************************************************************************/
997
998
0
static int32_t Element_width(Element* self) {
999
0
    switch (self->type.id) {
1000
0
        case TRDP_BITSET8: // BITSET8     1
1001
0
        case TRDP_CHAR8:   // CHAR8   2 char, can be used also as UTF8
1002
0
        case TRDP_INT8:    // INT8    4 Signed integer, 8 bit
1003
0
        case TRDP_UINT8:   // UINT8   8 Unsigned integer, 8 bit
1004
0
            return 1;
1005
0
        case TRDP_UTF16:  // UTF16    3 Unicode UTF-16 character
1006
0
        case TRDP_INT16:  // INT16    5 Signed integer, 16 bit
1007
0
        case TRDP_UINT16: // UINT16   9 Unsigned integer, 16 bit
1008
0
            return 2;
1009
0
        case TRDP_INT32:      // INT32    6 Signed integer, 32 bit
1010
0
        case TRDP_UINT32:     // UINT32   10  Unsigned integer, 32 bit
1011
0
        case TRDP_REAL32:     // REAL32   12  Floating point real, 32 bit
1012
0
        case TRDP_TIMEDATE32: // TIMEDATE32 14  32 bit UNIX time
1013
0
            return 4;
1014
0
        case TRDP_INT64:      // INT64    7 Signed integer, 64 bit
1015
0
        case TRDP_UINT64:     // UINT64   11  Unsigned integer, 64 bit
1016
0
        case TRDP_REAL64:     // REAL64   13  Floating point real, 64 bit
1017
0
        case TRDP_TIMEDATE64: // TIMEDATE64 16  32 bit seconds and 32 bit
1018
            // microseconds
1019
0
            return 8;
1020
0
        case TRDP_TIMEDATE48: // TIMEDATE48 15  48 bit TCN time (32 bit seconds
1021
            // and 16 bit ticks)
1022
0
            return 6;
1023
0
        case TRDP_UUID:       // UUID   18  UUID, not official but improves handling in WS
1024
0
            return 16;
1025
0
        default:
1026
0
            return -1;
1027
0
    }
1028
0
}
1029
1030
// NOLINTNEXTLINE(misc-no-recursion) -- recursion depth guard in place
1031
static bool Element_checkConsistency(Element* self, TrdpDict* dict, Dataset** hierarchyStack, size_t depth)
1032
0
{
1033
0
    if (!self || !dict || !hierarchyStack || depth > TRDP_MAX_DATASET_RECURSION)
1034
0
        return FALSE;
1035
1036
0
    if (self->type.id > TRDP_STANDARDTYPE_MAX || !self->type.id) {
1037
1038
0
        if (!self->linkedDS) {
1039
0
            self->linkedDS = TrdpDict_get_Dataset(dict, &self->type);
1040
0
            if (self->linkedDS && !*self->type.name) {
1041
0
                if (*self->linkedDS->type.name)
1042
0
                    memcpy(self->type.name, self->linkedDS->type.name, sizeof(self->type.name));
1043
0
                else
1044
0
                    uint32_to_str_buf(self->linkedDS->type.id, self->type.name, sizeof(self->type.name));
1045
0
            }
1046
0
        }
1047
1048
        /* check, if the referenced dataset already occurs in the call chain, if so, stop and whack the user later. */
1049
0
        for(size_t i=0; i<depth; i++) {
1050
0
            if (hierarchyStack[i] && (hierarchyStack[i] == self->linkedDS)) {
1051
0
                dict->mCyclicDataset = hierarchyStack[i];
1052
0
                return FALSE;
1053
0
            }
1054
0
        }
1055
        /* the above check should prevent infinite / cyclic recursion, so tell clang, we care */
1056
1057
        // NOLINTNEXTLINE(misc-no-recursion)
1058
0
        self->width = Dataset_preCalculate(self->linkedDS, dict, hierarchyStack, depth);
1059
0
        return self->width >= 0;
1060
1061
0
    } else
1062
0
        return TRUE;
1063
0
}
1064
1065
static Element* Element_new(const char* _type, const char* _name, const char* _unit, const char* _array_size,
1066
                            const char* _scale, const char* _offset, const char* _bitnames,
1067
                            unsigned int cnt, GError** error)
1068
0
{
1069
0
    gdouble scale;
1070
0
    int32_t offset;
1071
0
    int32_t array_size;
1072
0
    ElementType type;
1073
0
    char* endptr = NULL;
1074
0
    errno = 0;
1075
0
    array_size = _array_size ? (int32_t)g_ascii_strtoull(_array_size, &endptr, 10) : 1;
1076
0
    if (errno) {
1077
0
        g_set_error(error, q_xml, XML_INVALID_CONTENT, // error code
1078
0
                    "array-size=\"%s\" What is this? <element>'s attribute was unparsable. (%s)", endptr, g_strerror(errno));
1079
0
        return NULL;
1080
0
    }
1081
0
    offset = _offset ? (int32_t)g_ascii_strtoll(_offset, &endptr, 10) : 0;
1082
0
    if (errno) {
1083
0
        g_set_error(error, q_xml, XML_INVALID_CONTENT, // error code
1084
0
                    "offset=\"%s\" What is this? <element>'s attribute was unparsable. (%s)", endptr, g_strerror(errno));
1085
0
        return NULL;
1086
0
    }
1087
0
    scale = _scale ? g_ascii_strtod(_scale, &endptr) : 0;
1088
0
    if (errno) {
1089
0
        g_set_error(error, q_xml, XML_INVALID_CONTENT, // error code
1090
0
                    "scale=\"%s\" What is this? <element>'s attribute was unparsable. (%s)", endptr, g_strerror(errno));
1091
0
        return NULL;
1092
0
    }
1093
0
    GError* err=NULL;
1094
0
    if (!TrdpDict_parseType(_type, NULL, &type, &err)) {
1095
0
        g_propagate_prefixed_error(error, err, "While looking at element \"%s\", ", _name);
1096
0
        return NULL;
1097
0
    }
1098
1099
0
    Element* self = wmem_new0(wmem_dic, Element);
1100
0
    self->ett_id = -1;
1101
0
    self->bits_ett_id = -1;
1102
0
    self->array_size = array_size;
1103
0
    self->name = _name && *_name ? wmem_strdup(wmem_dic, _name) : wmem_strdup_printf(wmem_dic, "%u", cnt); /* in case the name is empty, take a running number */
1104
0
    self->scale = scale;
1105
0
    self->offset = offset;
1106
0
    self->type = type;
1107
0
    self->unit = _unit ? wmem_strdup(wmem_dic, _unit) : (char*)SEMPTY;
1108
0
    self->isUnit.fold0 = (0 == g_ascii_strcasecmp(self->unit, "fold0"));
1109
0
    self->isUnit.hide0 = (0 == g_ascii_strcasecmp(self->unit, "hide0"));
1110
0
    self->isUnit.sc32  = (0 == g_ascii_strcasecmp(self->unit, "sc32"));
1111
0
    self->isUnit.version = (0 == g_ascii_strcasecmp(self->unit, "version"));
1112
0
    self->isUnit.ssc   = (0 == g_ascii_strcasecmp(self->unit, "ssc"));
1113
1114
0
    if (_bitnames && *_bitnames) {
1115
0
        char**  bitnametoks = wmem_strsplit(wmem_dic, _bitnames, ",", TRDP_BITSUBTYPE_BITS);
1116
0
        for (int b=0; bitnametoks[b]; b++) Element_add_bit(self, bitnametoks[b], NULL, b, error);
1117
0
        wmem_free(wmem_dic, bitnametoks[0]); /* free the actual character mem */
1118
0
        wmem_free(wmem_dic, bitnametoks   ); /* free the token-pointers into it */
1119
0
    }
1120
1121
0
    self->width = Element_width(self);
1122
0
    return self;
1123
0
}
1124
1125
static void Element_add_bit(Element* self, const char* name, const char* _position, int32_t position, GError** error)
1126
0
{
1127
0
    char* endptr = NULL;
1128
0
    if (self && self->type.id == TRDP_BITSET8 && self->type.subtype == TRDP_BITSUBTYPE_BITSET8 && name && *name) {
1129
0
        if (_position) {
1130
0
            position = (int32_t)g_ascii_strtoll(_position, &endptr, 10);
1131
0
            if (errno) {
1132
0
                g_set_error(error, q_xml, XML_INVALID_CONTENT, // error code
1133
0
                            "position=\"%s\" What is this? <bit>'s attribute was unparsable. (%s)", endptr, g_strerror(errno));
1134
0
                return;
1135
0
            }
1136
0
        }
1137
1138
0
        if (position == -1) {
1139
0
            position = self->bitindex;
1140
0
        }
1141
1142
0
        if ((position >= TRDP_BITSUBTYPE_BITS) || (position < 0)) {
1143
0
            g_set_error(error, q_xml, XML_INVALID_CONTENT, // error code
1144
0
                        "position=\"%d\" is out of range - <bit>'s attribute was unparsable.", position);
1145
0
            return;
1146
0
        }
1147
1148
0
        if (!self->bits) {
1149
0
            self->bits = wmem_alloc0_array(wmem_dic, Bit, TRDP_BITSUBTYPE_BITS);
1150
0
            self->bitfields = wmem_alloc0_array(wmem_dic, int*, TRDP_BITSUBTYPE_BITS + 1);
1151
0
            for (unsigned b = 0; b < TRDP_BITSUBTYPE_BITS; b++) {
1152
0
                self->bits[b].hf_id = -1;
1153
0
                self->bits[b].ett_id = -1;
1154
0
            }
1155
0
        }
1156
1157
0
        g_strlcpy(self->bits[position].name, name, sizeof(self->bits[position].name));
1158
1159
0
        self->bitindex = position + 1;
1160
0
    }
1161
0
}
1162
1163
static bool Element_equals(const Element* self, const Element* other, GError** error)
1164
0
{
1165
0
    bool eq = ((self == other)
1166
0
    || ((self->type.id == other->type.id)
1167
0
    && (self->type.subtype == other->type.subtype)
1168
0
    && !g_ascii_strcasecmp(self->name, other->name)
1169
0
    && ((!self->unit && !other->unit) || (self->unit && other->unit && !g_ascii_strcasecmp(self->unit, other->unit)))
1170
0
    && (self->array_size == other->array_size)
1171
0
    && (self->scale == other->scale)
1172
0
    && (self->offset == other->offset)
1173
0
    && ((!self->bits && !other->bits) || (self->bits && other->bits && !(memcmp(self->bits, other->bits, sizeof(other->bits[0])))))));
1174
1175
0
    if (!eq && error) {
1176
0
        g_set_error(error, q_xml, XML_INVALID_CONTENT,
1177
0
                    "Type: %s / %s, Name: %s / %s, Unit: %s / %s, Array: %d / %d, Scale: %f / %f, Offset: %d / %d, Bits: %d / %d\n ",
1178
0
                    self->type.name, other->type.name,
1179
0
                    self->name, other->name,
1180
0
                    FALLBACK(self->unit,"nil"), FALLBACK(other->unit,"nil"),
1181
0
                    self->array_size, other->array_size,
1182
0
                    self->scale, other->scale,
1183
0
                    self->offset, other->offset,
1184
0
                    !!self->bits, !!other->bits);
1185
0
    }
1186
0
    return eq;
1187
0
}
1188
1189
static void Element_delete(Element* self)
1190
0
{
1191
0
    if (self) {
1192
0
        wmem_free(wmem_dic, self->bits);
1193
0
        wmem_free(wmem_dic, self->bitfields);
1194
0
        if (self->name) wmem_free(wmem_dic, self->name);
1195
0
        if (self->unit && self->unit != SEMPTY)
1196
0
            wmem_free(wmem_dic, self->unit);
1197
0
        wmem_free(wmem_dic, self);
1198
0
    }
1199
0
}
1200
1201
/** @fn     int32_t TrdpDict_element_size(const Element *element, uint32_t array_size);
1202
 *
1203
 *  @brief  Calculate the size of an element and its subtree if there is one.
1204
 *
1205
 *  @param  self       The element to calculate
1206
 *  @param  array_size Hand in the dynamic size of the array (kept from the previous element) or set to 1 to use the predefined size from the dictionary.
1207
 *  @return -1 on error, or the type-size multiplied by the array-size.
1208
 */
1209
1210
static int32_t TrdpDict_element_size(const Element* self, uint32_t array_size /* = 1*/)
1211
0
{
1212
0
    return self ? (self->width * FALLBACK(self->array_size,(int32_t)array_size)) : -1;
1213
0
}
1214
1215
1216
/************************************************************************************
1217
 *                          DATASET
1218
 ************************************************************************************/
1219
1220
static Dataset* Dataset_new(const char* _id, const char* _name, const char* filename, GError** error)
1221
0
{
1222
0
    ElementType type;
1223
0
    GError* err=NULL;
1224
0
    if (!TrdpDict_parseType(_id, _name, &type, error)) {
1225
0
        g_propagate_prefixed_error(error, err,
1226
0
                                   "While looking at \"%s\", around dataset %s:%s, ",
1227
0
                                   FALLBACK(filename,"baked-in XML"), _id, _name);
1228
0
        return NULL;
1229
0
    }
1230
1231
0
    Dataset* self = wmem_new0(wmem_dic, Dataset);
1232
0
    self->type = type;
1233
0
    self->ett_id = -1;
1234
0
    self->duplicates = 0;
1235
0
    self->source = wmem_strdup(wmem_dic, filename);
1236
0
    return self;
1237
0
}
1238
1239
static bool Dataset_equals(const Dataset* self, const Dataset* other, GError** error)
1240
0
{
1241
0
    if (self == other)
1242
0
        return TRUE;
1243
1244
0
    if (self->type.id != other->type.id)
1245
0
        return FALSE;
1246
1247
0
    GError* err = NULL;
1248
0
    if (0 == g_ascii_strcasecmp(self->type.name, other->type.name)) {
1249
0
        Element* elSelf  = self->listOfElements;
1250
0
        Element* elOther = other->listOfElements;
1251
0
        while (elSelf && elOther && Element_equals(elSelf, elOther, error ? &err : NULL)) {
1252
0
            elSelf = elSelf->next;
1253
0
            elOther = elOther->next;
1254
0
        }
1255
0
        if (error && err) {
1256
0
            g_propagate_prefixed_error(error, err,
1257
0
                                       "Dataset %d:%s differs between \"%s\" and \"%s\" error-causing element: ",
1258
0
                                       self->type.id, self->type.name, FALLBACK(self->source,"baked-in XML"), FALLBACK(other->source,"baked-in XML"));
1259
0
        }
1260
0
        return (!elSelf && !elOther);
1261
0
    } else if (!self->type.id && !other->type.id) {
1262
0
        g_set_error(error, q_xml, XML_INVALID_CONTENT,
1263
0
                    "Dataset %d differ between \"%s\" and \"%s\" in name: %s / %s",
1264
0
                    self->type.id, FALLBACK(self->source,"baked-in XML"), FALLBACK(other->source,"baked-in XML"), self->type.name, other->type.name);
1265
0
    }
1266
0
    return FALSE;
1267
0
}
1268
1269
/* this must be called for all DS *after* config reading */
1270
/** Calculate the size of the elements and its contents
1271
 * @brief calculateSize
1272
 * @return size (==getSize()), or -1 on error, 0 on variable elements
1273
 */
1274
// NOLINTNEXTLINE(misc-no-recursion) -- recursion depth guard in place
1275
static int32_t Dataset_preCalculate(Dataset* self, TrdpDict* dict, Dataset** hierarchyStack, size_t depth)
1276
0
{
1277
    /* If data is missing, we cannot calculate at all. This is an error. */
1278
0
    if (!self || !dict || depth > TRDP_MAX_DATASET_RECURSION)
1279
0
        return -1;
1280
1281
    /* remember about the maximum depth and break if we pass the limit */
1282
0
    if (dict->maxDatasetDepth < depth+1) dict->maxDatasetDepth = depth+1;
1283
0
    if (dict->maxDatasetDepth > TRDP_MAX_DATASET_RECURSION) {
1284
0
        return -1;
1285
0
    }
1286
1287
0
    if (!self->size) {
1288
0
        int32_t size = 0;
1289
0
        bool var_array = FALSE;
1290
0
        hierarchyStack[depth++] = self;
1291
1292
0
        for (Element* el = self->listOfElements; el; el = el->next) {
1293
1294
0
            if (!Element_checkConsistency(el, dict, hierarchyStack, depth)) {
1295
0
                size = -1;
1296
0
                break;
1297
0
            }
1298
            /* if a DS contains at least one variable array, we cannot pre-calc this datasets size */
1299
0
            if (!el->array_size || !el->width) {
1300
0
                size = 0;
1301
0
                var_array = TRUE;
1302
                /* instead of breaking here, we need to continue through more elements to check them all, but sticking
1303
                 * size at zero */
1304
0
            }
1305
1306
            /* add simple size to datasets size */
1307
0
            if (!var_array) {
1308
0
                size += TrdpDict_element_size(el, 1);
1309
0
            }
1310
0
        }
1311
0
        self->size = size;
1312
0
    }
1313
0
    return self->size;
1314
0
}
1315
1316
static void Dataset_delete(Dataset* self, int parent_id)
1317
0
{
1318
0
    if (self) {
1319
0
        while (self->listOfElements) {
1320
0
            Element* el = self->listOfElements;
1321
0
            self->listOfElements = self->listOfElements->next;
1322
0
            if (parent_id > -1 && el->hf_id > -1) {
1323
0
                proto_deregister_field(parent_id, el->hf_id);
1324
0
            }
1325
            /* no idea how to clean up subtree handler el->ett_id */
1326
0
            Element_delete(el);
1327
0
        }
1328
0
        wmem_free(wmem_dic, self->source);
1329
0
        wmem_free(wmem_dic, self);
1330
0
    }
1331
0
}
1332
1333
/************************************************************************************
1334
 *                          COMID
1335
 ************************************************************************************/
1336
1337
static void ComId_delete(ComId* self)
1338
0
{
1339
0
    for (Connection* con = self->con; con; con = self->con) {
1340
0
        self->con = con->next;
1341
0
        if (con->name && con->name != SEMPTY) wmem_free(wmem_dic, con->name);
1342
0
        wmem_free(wmem_dic, con);
1343
0
    }
1344
0
    if (self->name && self->name != SEMPTY)
1345
0
        wmem_free(wmem_dic, self->name);
1346
0
    wmem_free(wmem_dic, self->source);
1347
0
    wmem_free(wmem_dic, self);
1348
0
}
1349
1350
static ComId* ComId_new(const char* _id, const char* aname, const char* _dsId, const char* file, GError** error)
1351
0
{
1352
    /* check params */
1353
0
    char* endptr=NULL;
1354
0
    errno = (_id && *_id) ? 0 : 61 /*ENODATA*/;
1355
0
    uint32_t id = errno ? 0 : (uint32_t)g_ascii_strtoull(_id, &endptr, 10);
1356
0
    if (errno) {
1357
0
        g_set_error(error, q_xml, XML_INVALID_CONTENT, // error code
1358
0
                    "com-id=\"%s\" What is this? <telegram>'s attribute was unparsable. (%s)", endptr, g_strerror(errno));
1359
0
        return NULL;
1360
0
    }
1361
0
    ElementType type = ElBasics[0];
1362
0
    if (_dsId && *_dsId) { /* dataset id is optional in telegram-def, ie, empty or opaque payload */
1363
0
        GError* err=NULL;
1364
0
        if (!TrdpDict_parseType(_dsId, NULL, &type, &err)) {
1365
0
            g_propagate_prefixed_error(error, err,
1366
0
                                       "While looking at \"%s\", around telegram %d:%s, ",
1367
0
                                       FALLBACK(file,"baked-in XML"), id, aname);
1368
1369
0
            return NULL;
1370
0
        }
1371
0
    }
1372
1373
0
    if (!(aname && *aname) && !(type.id || *type.name)) {
1374
0
        g_set_error(error, q_xml, XML_INVALID_CONTENT, // error code
1375
0
                    "com-id=\"%s\" provides no definition. Neither name nor data-set is given. Please check.", _id);
1376
0
        return NULL;
1377
0
    }
1378
1379
0
    ComId* self = wmem_new0(wmem_dic, ComId);
1380
0
    self->comId = id;
1381
0
    self->dataset = type;
1382
0
    self->ett_id = -1;
1383
0
    self->name = aname ? wmem_strdup(wmem_dic, aname) : (char*)SEMPTY;
1384
0
    self->source = wmem_strdup(wmem_dic, file);
1385
0
    return self;
1386
0
}
1387
1388
/* compare two connection structures for equality. They are expected to belong to the same com-id parent
1389
 */
1390
0
static bool ComId_connection_equals(const Connection* self, const Connection* other, GError** error) {
1391
0
    if (self == other) return TRUE;
1392
0
    if (!self || !other) return FALSE;
1393
0
    bool eq = (self->src_raw == other->src_raw) && (self->dst_raw == other->dst_raw);
1394
0
    if (eq) {
1395
0
        char src_buf[16], dst_buf[16];
1396
0
        address_to_str_buf(&self->src, src_buf, sizeof(src_buf));
1397
0
        address_to_str_buf(&self->dst, dst_buf, sizeof(dst_buf));
1398
0
        if (self->udv != other->udv) {
1399
0
            g_set_error(error, q_xml, XML_INVALID_CONTENT, // error code
1400
0
                        "Incompatible instances of source and/or destination were defined. Mismatch in \"udv\"=%hu/%hu for %s -> %s", self->udv, other->udv, src_buf, dst_buf);
1401
0
            return FALSE;
1402
0
        }
1403
0
        if (self->smi != other->smi) {
1404
0
            g_set_error(error, q_xml, XML_INVALID_CONTENT, // error code
1405
0
                        "Incompatible instances of source and/or destination were defined. Mismatch in \"smi\"=%u/%u for %s -> %s", self->smi, other->smi, src_buf, dst_buf);
1406
0
            return FALSE;
1407
0
        }
1408
0
    }
1409
0
    return eq;
1410
0
}
1411
1412
/*
1413
 * char*    name;
1414
 * address src, dst;
1415
 * uint32_t src_raw, dst_raw;
1416
 *
1417
 * uint16_t udv;
1418
 * uint32_t smi;
1419
 * uint32_t sequence;
1420
 *
1421
 */
1422
0
static void ComId_connection_add(ComId* com, const char* name, uint32_t src, uint32_t dst, uint16_t udv, uint32_t smi, GError** error) {
1423
0
    Connection* con = wmem_new0(wmem_dic, Connection);
1424
0
    con->name = name ? wmem_strdup(wmem_dic, name) : (char*)SEMPTY;
1425
0
    con->src_raw = src;
1426
0
    con->dst_raw = dst;
1427
0
    set_address(&con->src, AT_IPv4, sizeof(con->src_raw), &con->src_raw);
1428
0
    set_address(&con->dst, AT_IPv4, sizeof(con->dst_raw), &con->dst_raw);
1429
0
    con->udv = udv;
1430
0
    con->smi = smi;
1431
1432
0
    if (com->con) {
1433
0
        Connection* comcon = com->con;
1434
        /* check, for duplicates */
1435
0
        while (comcon) {
1436
0
            if (ComId_connection_equals(comcon, con, error)) {
1437
0
                if (con->name && con->name != SEMPTY) wmem_free(wmem_dic, con->name);
1438
0
                wmem_free(wmem_dic, con);
1439
0
                return;
1440
0
            } else {
1441
                /* panic, if the comparison had issues */
1442
0
                if (*error) return;
1443
                /* if we are done, enqueue te newbie */
1444
0
                if (!comcon->next) {
1445
0
                    comcon->next = con;
1446
0
                    return;
1447
0
                } else {
1448
0
                    comcon = comcon->next;
1449
0
                }
1450
0
            }
1451
0
        }
1452
0
    } else {
1453
0
        com->con = con;
1454
0
    }
1455
0
}
1456
1457
/* add aparameters for a specific instance of a telegram-path ("connection") between devices. This is sort of a template
1458
 * for an actual data connection. However, Wireshark connection tracking is not yet implemented in the current iteration.
1459
 */
1460
0
static void ComId_connection_parse(ComId* com, const char* name, bool dst, XMLCollectedPar* par, GError** error) {
1461
    /* name   - info, connection name
1462
     * dst    - true if defined in xml as destination, ie., host is source, uri is sink
1463
     */
1464
    /* strategy aspects,
1465
     * The Wireshark capture is not necessarily done on a device defined in the xml, so may neither be a source or a sink
1466
     * The TRDP traffic been watched may not be covered by the XML config, or only partially. So there is quite some
1467
     * guessing / tolerant evaluation necessary.
1468
     * (1) Timing is not supervised at all.
1469
     * (2) How can I differentiate between being on consist-level or on etb-level?
1470
     * (3) How can I resolve uri to ip and vice versa?
1471
     * (4) sequence numbers are monitored per IP-source
1472
     */
1473
0
    if (com && par) {
1474
0
        char* endptr = NULL;
1475
0
        uint64_t udv = 0;
1476
0
        uint64_t smi = 0;
1477
0
        uint32_t host = 0;
1478
0
        uint32_t uri = 0;
1479
1480
0
        errno = 0;
1481
0
        if (par->udv) {
1482
0
            udv = g_ascii_strtoull(par->udv, &endptr, 0);
1483
0
            if (errno || udv > 0xffff) {
1484
0
                g_set_error(error, q_xml, XML_INVALID_CONTENT, // error code
1485
0
                            "udv=\"%s\" What is this? <sdt-parameter>'s attribute was unparsable. (%s)", par->udv, g_strerror(errno));
1486
0
                return;
1487
0
            }
1488
0
            udv = (udv <= 0xff && strnlen(par->udv, 4) <= 3) ? udv<<8 : udv;
1489
0
        }
1490
1491
0
        if (par->smi) {
1492
0
            smi = g_ascii_strtoull(par->smi, &endptr, 10);
1493
0
            if (errno || smi > 0xffffffff) {
1494
0
                g_set_error(error, q_xml, XML_INVALID_CONTENT, // error code
1495
0
                            "smi1=\"%s\" What is this? <%s><sdt-parameter>'s attribute was unparsable. (%s)", par->smi, dst?"destination":"source", g_strerror(errno));
1496
0
                return;
1497
0
            }
1498
0
        }
1499
1500
0
        if (par->hostIp) {
1501
0
            if (!str_to_ip(par->hostIp, &host)) {
1502
0
                g_set_error(error, q_xml, XML_INVALID_CONTENT, // error code
1503
0
                                "host-ip=\"%s\" What is this? <%s>'s attribute was unparsable.", par->hostIp, "bus-interface");
1504
0
                return;
1505
0
            }
1506
0
        }
1507
1508
0
        if (par->uri) {
1509
0
            if (!str_to_ip(par->uri, &uri)) {
1510
                /* don't bark if it may be a host name, which we cannot resolve right here */
1511
0
                if (g_ascii_isdigit(par->uri[0])) {
1512
0
                    g_set_error(error, q_xml, XML_INVALID_CONTENT, // error code
1513
0
                        "uri=\"%s\" What is this? <%s>'s attribute was unparsable.", par->uri, dst?"destination":"source");
1514
0
                    return;
1515
0
                }
1516
0
            }
1517
0
        }
1518
1519
0
        if (!dst) {
1520
0
            ComId_connection_add(com, name, uri, host, (uint16_t)udv, (uint32_t)smi, error);
1521
0
            if (*error) return;
1522
0
            if (par->uri2) {
1523
0
                uint32_t uri2 = 0;
1524
0
                if (str_to_ip(par->uri2, &uri2)) {
1525
1526
0
                    uint64_t smi2 = 0;
1527
0
                    if (par->smi2) {
1528
0
                        smi2 = g_ascii_strtoull(par->smi2, &endptr, 10);
1529
0
                        if (errno || smi2 > 0xffffffff) {
1530
0
                            g_set_error(error, q_xml, XML_INVALID_CONTENT, // error code
1531
0
                                        "smi2=\"%s\" What is this? <%s><sdt-parameter>'s attribute was unparsable. (%s)", par->smi2, dst?"destination":"source", g_strerror(errno));
1532
0
                            return;
1533
0
                        }
1534
0
                    }
1535
1536
0
                    if (smi && !smi2) {
1537
0
                        g_set_error(error, q_xml, XML_INVALID_CONTENT,
1538
0
                                    "uri2=\"%s\" but smi2 is undefined, while smi is. That is broken, please fix.", par->smi2);
1539
0
                        return;
1540
0
                    }
1541
0
                } else {
1542
                    /* don't bark if it may be a host name, which we cannot resolve right here */
1543
0
                    if (g_ascii_isdigit(par->uri2[0])) {
1544
0
                        g_set_error(error, q_xml, XML_INVALID_CONTENT, // error code
1545
0
                                    "uri2=\"%s\" What is this? <%s>'s attribute was unparsable.", par->uri2, "source");
1546
0
                        return;
1547
0
                    }
1548
0
                }
1549
0
                ComId_connection_add(com, name, uri2, host, (uint16_t)udv, (uint32_t)smi, error);
1550
0
            }
1551
0
        } else {
1552
0
            ComId_connection_add(com, name, host, uri, (uint16_t)udv, (uint32_t)smi, error);
1553
0
        }
1554
0
    }
1555
0
}
1556
1557
static bool ComId_equals(const ComId* self, const ComId* other, GError** error)
1558
0
{
1559
    /* the good thing is, we don't really care about sub-tag such as source/sink/com-parameters. */
1560
    /* Name should be ignored */
1561
0
    bool eq = (self == other || (self->comId == other->comId && ((self->dataset.id && (self->dataset.id == other->dataset.id)) || (0==g_ascii_strcasecmp(self->dataset.name,other->dataset.name)))));
1562
0
    if (!eq) {
1563
0
        g_set_error(error, q_xml, XML_INVALID_CONTENT,
1564
0
                    "ComId %d differ in Dataset-ID %d:%s / %d:%s. Check \"%s\" against \"%s\".",
1565
0
                    self->comId, self->dataset.id, self->dataset.name, other->dataset.id, other->dataset.name, self->source, other->source);
1566
0
    }
1567
0
    return eq;
1568
0
}
1569
1570
/* Tries to get the size for the comId-related DS. Will only work, if all DS are non-variable. */
1571
/**< must only be called after full config initialization */
1572
static int32_t ComId_preCalculate(ComId* self, TrdpDict* dict)
1573
0
{
1574
0
    if (!dict) {
1575
0
        self->size = -1;
1576
0
    } else {
1577
0
        if (!self->linkedDS)
1578
0
            self->linkedDS = TrdpDict_get_Dataset(dict, &self->dataset);
1579
1580
        /* setup the dataset-call-stack to detect cyclic dependencies */
1581
0
        Dataset* hierarchyStack[TRDP_MAX_DATASET_RECURSION] = {NULL, };
1582
        /* this is ok to use, because the root dataset is not an element, thus cannot be an array */
1583
0
        self->size = self->linkedDS ? Dataset_preCalculate(self->linkedDS, dict, hierarchyStack, 0) : 0;
1584
0
    }
1585
0
    return self->size;
1586
0
}
1587
1588
/* look up the specific telegram src/dst parameters, if provided. Will typically not work for the predefined telegrams
1589
 * from the standard, as they require special name resolution and have no source defined
1590
 */
1591
0
static Connection* ComId_connection_lookup(ComId* self, const packet_info* pinfo) {
1592
0
    if (!self || !pinfo || !self->con) return NULL;
1593
1594
0
    Connection* comcon = self->con;
1595
0
    while (comcon) {
1596
0
        if (addresses_equal(&pinfo->src, &comcon->src) && addresses_equal(&pinfo->dst, &comcon->dst)) break;
1597
0
        comcon = comcon->next;
1598
0
    }
1599
0
    return comcon;
1600
0
}
1601
1602
/*
1603
    uint32_t safeTopoCnt; //> STC: unique identification of the actual train composition (opTrnTopoCnt) for train wide communication over ETB. 0 for consist network internal communication. see 5.3.3.2.13
1604
*/
1605
1606
0
static bool ComId_make_SID(ComId* self, const packet_info* pinfo, uint32_t stc, uint32_t* sid) {
1607
1608
0
    if (!self || !sid) return FALSE;
1609
1610
0
    Connection* con = ComId_connection_lookup(self, pinfo);
1611
0
    if (!con) return FALSE;
1612
1613
0
    uint32_t smi = con->smi; /* get from config */
1614
1615
0
    if (!smi /*|| (!stc != !have_cstUUID)*/) return FALSE;
1616
1617
0
    uint16_t input16[16] = {0,};
1618
0
    uint32_t* input32 = (uint32_t*)&input16;
1619
0
    input32[0] = g_htonl(smi);
1620
0
    input16[3] = g_htons(TRDP_SDTPROT_VERSION);
1621
0
    if (have_cstUUID) memcpy(input32+2, cstUUID, 16);
1622
0
    input32[6] = g_htonl(stc);
1623
1624
0
    *sid = crc32_sc32_seed((const uint8_t*)input16, sizeof(input16), TRDP_DEFAULT_SC32_SID);
1625
0
    return TRUE;
1626
0
}
1627
1628
0
static bool ComId_assert_SSC(ComId* self, const packet_info* pinfo, uint32_t ssc, int* result) {
1629
0
    if (!self || !result) return FALSE;
1630
1631
0
    if (pinfo->fd->visited) return FALSE;
1632
0
    pinfo->fd->visited = TRUE;
1633
1634
0
    Connection* con = ComId_connection_lookup(self, pinfo);
1635
0
    if (!con) return FALSE;
1636
1637
0
    *result = (con->sequence < ssc) ? 1 : 0;
1638
0
    if (con->sequence > 0xfffffff7 && ssc < 8) *result = 1; /* silently tolerate an overflow */
1639
0
    con->sequence = ssc;
1640
1641
0
    return TRUE;
1642
0
}
1643
1644
0
static void XMLCollectedPar_delete(XMLCollectedPar* self) {
1645
1646
0
    if (self) {
1647
0
        xmlFree(self->ifName);
1648
0
        xmlFree(self->hostIp);
1649
0
        xmlFree(self->leadIp);
1650
0
        xmlFree(self->pdComTo);
1651
0
        xmlFree(self->mdComConfirmTo);
1652
0
        xmlFree(self->mdComReplyTo);
1653
0
        xmlFree(self->pdTo);
1654
0
        xmlFree(self->cycle);
1655
0
        xmlFree(self->mdConfirmTo);
1656
0
        xmlFree(self->mdReplyTo);
1657
0
        xmlFree(self->uri);
1658
0
        xmlFree(self->smi);
1659
0
        xmlFree(self->udv);
1660
0
        xmlFree(self->uri2);
1661
0
        xmlFree(self->smi2);
1662
0
    }
1663
0
}
1664
1665
/*****************************************************************************
1666
 * Actual dissector code
1667
 *****************************************************************************/
1668
1669
1670
86
#define API_TRACE  ws_noisy("%s:%d : %s\n", __FILE__, __LINE__, __FUNCTION__)
1671
1672
/* Reply status indication names
1673
 * Signed int: <0: NOK; 0: OK; >0: user reply status
1674
 * (taken from TRDP-EKE) */
1675
1676
static const value_string reply_status_names[] = {
1677
    {-1, "Reserved"},
1678
    {-2, "Session abort"},
1679
    {-3, "No replier instance (at replier side)"},
1680
    {-4, "No memory (at replier side)"},
1681
    {-5, "No memory (local)"},
1682
    {-6, "No reply"},
1683
    {-7, "Not all replies"},
1684
    {-8, "No confirm"},
1685
    {-9, "Reserved"},
1686
    {-10, "Sending failed"},
1687
    {0, "Ok"},
1688
    {0, NULL}};
1689
1690
/* TRDP-packet-type map */
1691
static const char *trdp_types[] = {
1692
    "Pr", "PD Request",
1693
    "Pp", "PD Reply  ",
1694
    "Pe", "PD Error  ",
1695
    "Pd", "PD Data   ",
1696
    "Mn", "MD Notification (Req. w/o reply)",
1697
    "Mr", "MD Request with reply",
1698
    "Mp", "MD Reply ( w/o confrm)",
1699
    "Mq", "MD Reply (with confrm)",
1700
    "Mc", "MD Confirm",
1701
    "Me", "MD error  ",
1702
    NULL, "Unknown TRDP Type"
1703
};
1704
1705
/* Initialize the protocol and registered fields */
1706
static int proto_trdp = -1;
1707
static dissector_handle_t trdp_handle;
1708
static dissector_handle_t trdp_TCP_handle;
1709
1710
void proto_reg_handoff_trdp(void);
1711
void proto_register_trdp(void);
1712
1713
/*For All*/
1714
static int hf_trdp_sequencecounter;  /*uint32*/
1715
static int hf_trdp_protocolversion;  /*uint16*/
1716
static int hf_trdp_type;             /*uint16*/
1717
static int hf_trdp_etb_topocount;    /*uint32*/
1718
static int hf_trdp_op_trn_topocount; /*uint32*/
1719
static int hf_trdp_comid;            /*uint32*/
1720
static int hf_trdp_datasetlength;    /*uint16*/
1721
static int hf_trdp_padding;          /*bytes */
1722
1723
/*For All (user data)*/
1724
static int hf_trdp_fcs_head;      /*uint32*/
1725
static int hf_trdp_fcs_head_status;
1726
static int hf_trdp_sc32_status;
1727
static int hf_trdp_userdata;      /* userdata */
1728
1729
/*needed only for PD messages*/
1730
static int hf_trdp_reserved;               /*uint32*/
1731
static int hf_trdp_reply_comid; /*uint32*/ /*for MD-family only*/
1732
static int hf_trdp_reply_ipaddress;        /*uint32*/
1733
1734
/* needed only for MD messages*/
1735
static int hf_trdp_replystatus;    /*uint32*/
1736
static int hf_trdp_sessionid;      /*uuid*/
1737
static int hf_trdp_replytimeout;   /*uint32*/
1738
static int hf_trdp_sourceURI;      /*string*/
1739
static int hf_trdp_destinationURI; /*string*/
1740
1741
static bool g_basexml = TRUE;
1742
static const char *g_customTrdpDictionary;
1743
static const char *g_customTrdpDictionaryFolder;
1744
static unsigned int g_pd_port = TRDP_DEFAULT_UDP_PD_PORT;
1745
static unsigned int g_md_port = TRDP_DEFAULT_UDPTCP_MD_PORT;
1746
static bool g_scaled = TRUE;
1747
static bool g_char8_is_utf8 = TRUE;
1748
static bool g_0strings;
1749
static bool g_time_local = TRUE;
1750
static bool g_time_raw;
1751
static const char* g_cstUUID = NULL;
1752
static int g_fold0_mode = TRDP_FOLD_CONFIG;
1753
1754
/* Initialize the subtree pointers */
1755
static int ett_trdp = -1;
1756
1757
/* Expert fields */
1758
static expert_field ei_trdp_type_unknown;
1759
static expert_field ei_trdp_packet_small;
1760
static expert_field ei_trdp_userdata_empty;
1761
static expert_field ei_trdp_userdata_wrong;
1762
static expert_field ei_trdp_config_notparsed;
1763
static expert_field ei_trdp_padding_not_zero;
1764
static expert_field ei_trdp_array_wrong;
1765
static expert_field ei_trdp_faulty_antivalent;
1766
static expert_field ei_trdp_reserved_not_zero;
1767
static expert_field ei_trdp_header_checksum;
1768
static expert_field ei_trdp_sdtv2_safetycode;
1769
static expert_field ei_trdp_sdtv2_sequence;
1770
static expert_field ei_trdp_proc_comid_zero;
1771
1772
/* static container for dynamic fields and subtree handles */
1773
static struct {
1774
    wmem_array_t *hf;
1775
    wmem_array_t *ett;
1776
} trdp_build_dict;
1777
1778
static TrdpDict *pTrdpParser;
1779
1780
/* @fn *static void checkPaddingAndOffset(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, uint32_t offset)
1781
 *
1782
 * @brief Check for correct padding
1783
 *
1784
 * @param[in]   tvb     Buffer with the captured data
1785
 * @param[in]   pinfo   Necessary to mark status of this packet
1786
 * @param[in]   tree    The information is appended
1787
 * @param[in]   offset  Actual offset where the padding starts
1788
 *
1789
 * @return position in the buffer
1790
 */
1791
1
static int checkPaddingAndOffset(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, uint32_t offset) {
1792
1
    int remainingBytes;
1793
1
    bool isPaddingZero = TRUE;
1794
1
    int maxpadding= ((offset + 3) & (~3))-offset; /* round up to add padding */
1795
1796
1
    remainingBytes = tvb_reported_length_remaining(tvb, offset);
1797
1
    ws_noisy("The remaining bytes are %d (padding=%d)(maxpadding=%d)", remainingBytes, remainingBytes%4, maxpadding);
1798
1799
1
    if (remainingBytes < 0) { /* There is no space for user data */
1800
0
        return offset;
1801
1
    } else {
1802
1
        if (remainingBytes < maxpadding) maxpadding = remainingBytes;
1803
1
        if (maxpadding > 0) {
1804
0
            for (int i = 0; i < maxpadding; i++) {
1805
0
                if (tvb_get_uint8(tvb, offset + i) != 0) {
1806
0
                    isPaddingZero = FALSE;
1807
0
                    break;
1808
0
                }
1809
0
            }
1810
0
            proto_tree_add_bytes_format_value(tree, hf_trdp_padding, tvb, offset, maxpadding, NULL, "%s", (isPaddingZero ? "[ok]" : "not zero"));
1811
1812
            /* Mark this packet in the statistics also as "not perfect" */
1813
0
            if (!isPaddingZero) expert_add_info_format(pinfo, tree, &ei_trdp_padding_not_zero, "Padding not zero");
1814
0
        }
1815
1
    }
1816
1
    return offset+maxpadding;
1817
1
}
1818
1819
/** @fn uint32_t dissect_trdp_generic_body(tvbuff_t* tvb, packet_info* pinfo,
1820
 * proto_tree* trdp_tree, ComId* com, Dataset* ds, uint32_t
1821
 * offset, unsigned int clength, uint8_t dataset_level, const char* title, const int32_t
1822
 * arr_idx )
1823
 *
1824
 * @brief Extract all information from the userdata (uses the parsebody module for unmarshalling)
1825
 *
1826
 * @param tvb               buffer
1827
 * @param pinfo             info for the packet
1828
 * @param trdp_tree         to which the information are added
1829
 * @param com               com data from dictionary
1830
 * @param ds                dataset data from dictionary
1831
 * @param offset            where the userdata starts in the TRDP packet
1832
 * @param clength           Amount of bytes, that are given in the PDU
1833
 * @param dataset_level     is set to 0 for the beginning
1834
 * @param title             presents the instance-name of the dataset for the sub-tree
1835
 * @param arr_idx           index for presentation when a dataset occurs in an array element
1836
 * @param zeroScanMode      if set, look for zeros and don't work on the tree
1837
 * @param isAllZeros        return value of zeroCheck
1838
 *
1839
 * @return the actual offset in the packet
1840
 */
1841
// NOLINTNEXTLINE(misc-no-recursion) -- increment_dissection_depth() is used as guard
1842
static int dissect_trdp_dataset(tvbuff_t* tvb, packet_info *pinfo, proto_tree* trdp_tree, ComId* com, Dataset* ds,
1843
                                     int offset, int clength,
1844
                                     uint8_t dataset_level, const char* title, const int32_t arr_idx,
1845
                                     bool zeroScanMode, bool *isAllZeros)
1846
1
{
1847
1848
1
    if (!com || !ds || !isAllZeros) return offset+clength;
1849
1850
1851
0
    int start_offset = offset; /* mark beginning of the dataset */
1852
0
    proto_tree* trdp_userdata = NULL;
1853
0
    proto_tree* userdata_element = NULL;
1854
0
    proto_item* pi = NULL;
1855
0
    int array_index = 0;
1856
0
    int element_count = 0; /* the expected number of array-elements in the current element */
1857
0
    int potential_array_size = -1; /* hold on to the value, for the next element, as it may be its element_count */
1858
0
    bool zero_here = TRUE;
1859
1860
    /* start with the dataset "header", and make it foldable */
1861
0
    ws_debug("%d:%s ([%d] octets)", ds->type.id, ds->type.name, ds->size);
1862
0
    if (!zeroScanMode) {
1863
0
        trdp_userdata = (arr_idx >= 0)
1864
0
                      ? proto_tree_add_subtree_format(trdp_tree, tvb, offset, FALLBACK(ds->size,-1), ds->ett_id, &pi, "%s.%d", title, arr_idx)
1865
0
                      : (ds->type.id /*if available, show its dataset-id*/
1866
0
                          ? proto_tree_add_subtree_format(trdp_tree, tvb, offset, FALLBACK(ds->size,-1), ds->ett_id, &pi, "%s: %s (%d)", title, ds->type.name, ds->type.id)
1867
0
                          : proto_tree_add_subtree_format(trdp_tree, tvb, offset, FALLBACK(ds->size,-1), ds->ett_id, &pi, "%s: %s", title, ds->type.name));
1868
0
    }
1869
1870
0
    for (Element *el = ds->listOfElements; el; el = el->next) {
1871
1872
0
        int foldableDatasetMode = (((g_fold0_mode == TRDP_FOLD_ALWAYS) || ((g_fold0_mode == TRDP_FOLD_CONFIG) && el->isUnit.fold0)) && (!el->type.id || (el->type.id > TRDP_STANDARDTYPE_MAX)));
1873
1874
0
        ws_debug("[%d] Offset %5u ----> Element: type=%2d %s\tname=%s\tarray-size=%d\tunit=%s\tscale=%f\toffset=%d",
1875
0
                 dataset_level, offset, el->type.id, el->type.name, el->name, el->array_size, el->unit, el->scale, el->offset);
1876
1877
        // at startup of a new item, check if it is an array or not
1878
0
        int remainder = 0;
1879
0
        element_count = el->array_size;
1880
1881
0
        if (!element_count) { // handle variable element count
1882
0
            if (g_0strings && (el->type.id == TRDP_CHAR8 || el->type.id == TRDP_UTF16)) {
1883
                /* handle the special elements CHAR8 and UTF16: */
1884
0
                if (potential_array_size && (el != ds->listOfElements)) zero_here = FALSE; /* then, the previous element cannot be ignored */
1885
0
            } else {
1886
0
                element_count = potential_array_size;
1887
0
                if (element_count < 1) { /* the previous element's data is special */
1888
0
                    if (element_count == 0) {
1889
0
                        potential_array_size = -1;
1890
0
                        continue; /* if, at the end of the day, the array is intentionally 0, skip the element */
1891
0
                    } else {
1892
0
                        expert_add_info_format(pinfo, trdp_tree, &ei_trdp_array_wrong, "%s : was introduced by an unsupported length field. (%d)", el->name, potential_array_size);
1893
0
                        return 0; /* in this case, the whole packet is garbled */
1894
0
                    }
1895
0
                } else {
1896
0
                    ws_debug("[%d] Offset %5u Dynamic array, with %d elements found", dataset_level, offset, element_count);
1897
0
                }
1898
1899
                // check if the specified amount is available in the package
1900
0
                remainder = (int)tvb_reported_length_remaining(tvb, offset);
1901
0
                if (remainder < TrdpDict_element_size(el, element_count)) {
1902
0
                    expert_add_info_format(pinfo, trdp_tree, &ei_trdp_userdata_wrong, "%s : has %d elements [%d byte each], but only %d left", el->name, element_count, TrdpDict_element_size(el, 1), remainder);
1903
0
                    return 0;
1904
0
                }
1905
0
            }
1906
0
        } else {
1907
0
            if (potential_array_size && (el != ds->listOfElements)) zero_here = FALSE; /* then, the previous element cannot be ignored */
1908
0
        }
1909
0
        if (element_count > 1) {
1910
0
            ws_debug("[%d] Offset %5u -- Array found, expecting %d elements using %d bytes", dataset_level, offset, element_count, TrdpDict_element_size(el, element_count));
1911
0
        }
1912
1913
0
        if (!zeroScanMode) {
1914
            /* For an array, inject a new node in the graphical dissector, tree (also the extracted dynamic information, see above are added) */
1915
0
            userdata_element = ((element_count == 1) || (el->type.id == TRDP_CHAR8) || (el->type.id == TRDP_UTF16)) /* for single line */
1916
0
                             ? trdp_userdata           /* take existing branch */
1917
0
                             : proto_tree_add_subtree_format( trdp_userdata, tvb, offset, TrdpDict_element_size(el, element_count), el->ett_id, &pi,
1918
0
                                                              "%s (%d): %s[%d]", el->type.name, el->type.id, el->name, element_count);
1919
0
        }
1920
1921
0
        do {
1922
0
            int64_t vals = 0;
1923
0
            uint64_t valu = 0;
1924
0
            const char *text = NULL;
1925
0
            int slen = 0;
1926
0
            int bytelen = el->width;
1927
0
            double real64 = 0;
1928
0
            nstime_t nstime = {0, 0};
1929
0
            char bits[TRDP_BITSUBTYPE_BITS+1];
1930
0
            uint32_t valuOrig = 0;
1931
0
            uint32_t calced_crc = 0;
1932
0
            e_guid_t guid;
1933
0
            bool zero_there = TRUE;
1934
1935
            /* first do some value preprocessing depending on the element type */
1936
0
            switch (el->type.id) {
1937
0
            case TRDP_BITSET8:
1938
0
                valu = tvb_get_uint8(tvb, offset);
1939
0
                if (valu) zero_here = FALSE;
1940
0
                break;
1941
1942
0
            case TRDP_CHAR8:
1943
0
                bytelen = (element_count || !g_0strings) ? (unsigned int)element_count : tvb_strsize(tvb, offset);
1944
0
                slen = (element_count || !g_0strings) ? bytelen : (bytelen - 1);
1945
0
                if (tvb_skip_uint8(tvb, offset, bytelen, 0) != (unsigned)(offset+bytelen)) zero_here = FALSE;
1946
0
                break;
1947
1948
0
            case TRDP_UTF16:
1949
0
                bytelen = (element_count || !g_0strings) ? (unsigned int)(2 * element_count) : tvb_unicode_strsize(tvb, offset);
1950
0
                slen = (element_count || !g_0strings) ? bytelen : (bytelen - 2);
1951
0
                if (tvb_skip_uint8(tvb, offset, bytelen, 0) != (unsigned)(offset+bytelen)) zero_here = FALSE;
1952
0
                break;
1953
1954
0
            case TRDP_INT8:
1955
0
                vals = tvb_get_int8(tvb, offset);
1956
0
                break;
1957
1958
0
            case TRDP_INT16:
1959
0
                vals = el->type.subtype == TRDP_ENDSUBTYPE_LIT ? tvb_get_letohis(tvb, offset) : tvb_get_ntohis(tvb, offset);
1960
0
                break;
1961
1962
0
            case TRDP_INT32:
1963
0
                vals = el->type.subtype == TRDP_ENDSUBTYPE_LIT ? tvb_get_letohil(tvb, offset) : tvb_get_ntohil(tvb, offset);
1964
0
                break;
1965
1966
0
            case TRDP_INT64:
1967
0
                vals = el->type.subtype == TRDP_ENDSUBTYPE_LIT ? tvb_get_letohi64(tvb, offset) : tvb_get_ntohi64(tvb, offset);
1968
0
                break;
1969
1970
0
            case TRDP_UINT8:
1971
0
                valu = tvb_get_uint8(tvb, offset);
1972
0
                break;
1973
1974
0
            case TRDP_UINT16:
1975
0
                valuOrig = tvb_get_ntohs(tvb, offset);
1976
0
                valu = el->type.subtype == TRDP_ENDSUBTYPE_LIT ? tvb_get_letohs(tvb, offset) : valuOrig;
1977
0
                break;
1978
1979
0
            case TRDP_UINT32:
1980
0
                valuOrig = tvb_get_ntohl(tvb, offset);
1981
                /* valuOrig always interpreted as BE for SC32 & SSC */
1982
0
                valu = el->type.subtype == TRDP_ENDSUBTYPE_LIT ? tvb_get_letohl(tvb, offset) : valuOrig;
1983
0
                break;
1984
1985
0
            case TRDP_UINT64:
1986
0
                valu = tvb_get_ntoh64(tvb, offset);
1987
0
                valuOrig = (uint32_t)valu; /* should not be used with U64 */
1988
0
                valu = el->type.subtype == TRDP_ENDSUBTYPE_LIT ? tvb_get_letoh64(tvb, offset) : valu;
1989
0
                break;
1990
1991
0
            case TRDP_REAL32:
1992
0
                real64 = el->type.subtype == TRDP_ENDSUBTYPE_LIT ? tvb_get_letohieee_float(tvb, offset) : tvb_get_ntohieee_float(tvb, offset);
1993
0
                break;
1994
1995
0
            case TRDP_REAL64:
1996
0
                real64 = el->type.subtype == TRDP_ENDSUBTYPE_LIT ? tvb_get_letohieee_double(tvb, offset) : tvb_get_ntohieee_double(tvb, offset);
1997
0
                break;
1998
1999
0
            case TRDP_TIMEDATE32:
2000
                /* This should be time_t from general understanding, which is UNIX time,
2001
                 * seconds since 1970 time_t is a signed long in modern POSIX ABIs, ie.
2002
                 * often s64! However, vos_types.h defines this as u32, which may
2003
                 * introduce some odd complications -- later. IEC61375-2-1 says for
2004
                 * UNIX-time: SIGNED32 - ok, will respect!
2005
                 */
2006
0
                vals = tvb_get_ntohil(tvb, offset);
2007
0
                nstime.secs = (long int)vals;
2008
0
                break;
2009
2010
0
            case TRDP_TIMEDATE48:
2011
0
                vals = tvb_get_ntohil(tvb, offset);
2012
0
                nstime.secs = (time_t)vals;
2013
0
                valu = tvb_get_ntohs(tvb, offset + 4);
2014
0
                nstime.nsecs = (int)(valu * (1000000000ULL / 256ULL)) / 256;
2015
0
                break;
2016
2017
0
            case TRDP_TIMEDATE64:
2018
0
                vals = tvb_get_ntohil(tvb, offset);
2019
0
                nstime.secs = (time_t)vals;
2020
0
                vals = tvb_get_ntohil(tvb, offset + 4);
2021
0
                nstime.nsecs = (int)vals * 1000;
2022
0
                break;
2023
2024
0
            case TRDP_UUID:
2025
0
                tvb_get_guid(tvb, offset, &guid, ((el->type.subtype == TRDP_ENDSUBTYPE_LIT) ? ENC_LITTLE_ENDIAN : ENC_BIG_ENDIAN));
2026
0
                if (tvb_skip_uint8(tvb, offset, bytelen, 0) != (unsigned)(offset+bytelen)) zero_here = FALSE;
2027
0
                break;
2028
2029
0
            default:
2030
0
                ws_debug("Unique type %d for %s", el->type.id, el->name);
2031
                /* safe guard against excessive recursion of datasets by using dissection-depth().
2032
                 * Problems should have been caught at dictionary reading. If it breaks here, it is some weird bug.
2033
                 */
2034
                /* use Wireshark's own protection. However, in the current dev-build of WS this value's (gui.max_tree_depth) default was much higher. */
2035
                /* be aware that each array introduces an extra level, as well as other protocol layers */
2036
2037
0
                bytelen = clength - (offset - start_offset);
2038
0
                if (zeroScanMode || foldableDatasetMode) {
2039
0
                    int offs = -1;
2040
0
                    increment_dissection_depth(pinfo);
2041
                    // NOLINTNEXTLINE(misc-no-recursion)
2042
0
                    offs = dissect_trdp_dataset( tvb, pinfo, userdata_element, com, el->linkedDS,
2043
0
                                                 offset, bytelen, dataset_level + 1,
2044
0
                                                 el->name, (element_count != 1) ? array_index : -1,
2045
0
                                                 zeroScanMode || foldableDatasetMode, &zero_there);
2046
0
                    decrement_dissection_depth(pinfo);
2047
0
                    bytelen = offs-offset;
2048
                    /* TODO The result of a sub-dataset being zero or not should be stored/cached, because this is run way too repeatedly */
2049
0
                    if (!zero_there) {
2050
0
                        zero_here = FALSE;
2051
0
                        *isAllZeros = FALSE;
2052
0
                    }
2053
0
                    if (!offs) return 0;
2054
0
                }
2055
0
                break;
2056
0
            }
2057
2058
            /* do the actual tree insertions */
2059
0
            if (!zeroScanMode) switch (el->type.id) {
2060
0
            case TRDP_BITSET8:
2061
0
                switch (el->type.subtype) {
2062
0
                    case TRDP_BITSUBTYPE_BOOL8:
2063
0
                        proto_tree_add_boolean(userdata_element, el->hf_id, tvb, offset, el->width, (uint32_t)valu);
2064
0
                        break;
2065
0
                    case TRDP_BITSUBTYPE_BITSET8:
2066
0
                        if (!el->bitfields) {
2067
0
                            bits[sizeof(bits) - 1] = 0;
2068
0
                            uint64_t v = valu;
2069
0
                            for (int i = sizeof(bits) - 1; i--; v >>= 1) bits[i] = v & 1 ? '1' : '.';
2070
0
                            proto_tree_add_uint_format_value(userdata_element, el->hf_id, tvb, offset, el->width, (uint32_t)valu,
2071
0
                                                             "0x%#02x ( %s )", (uint32_t)valu, bits);
2072
0
                        } else {
2073
0
                            proto_tree_add_bitmask(userdata_element, tvb, offset, el->hf_id, el->bits_ett_id, el->bitfields, ENC_BIG_ENDIAN);
2074
0
                        }
2075
0
                        break;
2076
0
                    case TRDP_BITSUBTYPE_ANTIVALENT8:
2077
0
                        switch (valu) {
2078
0
                            case 1:
2079
0
                            case 2:
2080
0
                                proto_tree_add_boolean(userdata_element, el->hf_id, tvb, offset, el->width, (uint32_t)(valu==2));
2081
0
                                break;
2082
2083
0
                            default:
2084
0
                                proto_tree_add_expert_format(userdata_element, pinfo, &ei_trdp_faulty_antivalent, tvb, offset, el->width,
2085
0
                                                             "%#2x is an invalid ANTIVALENT8 value.", (uint32_t)valu);
2086
0
                                break;
2087
0
                        }
2088
0
                        break;
2089
0
                }
2090
0
                break;
2091
2092
0
            case TRDP_CHAR8:
2093
0
                text = (g_char8_is_utf8 && element_count > 1)
2094
0
                    ? (const char *)tvb_get_string_enc(pinfo->pool, tvb, offset, slen, ENC_UTF_8)
2095
0
                    : tvb_format_text(pinfo->pool, tvb, offset, slen);
2096
2097
0
                if (element_count == 1) {
2098
0
                    proto_tree_add_string(userdata_element, el->hf_id, tvb, offset, bytelen, text);
2099
0
                } else {
2100
0
                    proto_tree_add_string_format_value(userdata_element, el->hf_id, tvb, offset, bytelen, text, "[%d] \"%s\"", slen, text);
2101
0
                }
2102
0
                break;
2103
2104
0
            case TRDP_UTF16:
2105
0
                text = (const char *)tvb_get_string_enc(pinfo->pool, tvb, offset, slen, ENC_UTF_16 | ((el->type.subtype == TRDP_ENDSUBTYPE_LIT) ? ENC_LITTLE_ENDIAN : ENC_BIG_ENDIAN));
2106
0
                proto_tree_add_string_format_value(userdata_element, el->hf_id, tvb, offset, bytelen, text, "[%d] \"%s\"", slen / 2, text);
2107
0
                break;
2108
2109
            /* case TRDP_INT8 ... TRDP_INT64: */
2110
0
            case TRDP_INT8:
2111
0
            case TRDP_INT16:
2112
0
            case TRDP_INT32:
2113
0
            case TRDP_INT64:
2114
2115
0
                if (el->isUnit.hide0) {
2116
0
                    if (vals != 0) {
2117
0
                        if (array_index)
2118
0
                            proto_tree_add_expert_format(userdata_element, pinfo, &ei_trdp_reserved_not_zero,
2119
0
                                                         tvb, offset, el->width, "Element [%d/%d] is not zero (%" PRId64 ").", array_index, element_count, vals);
2120
0
                        else
2121
0
                            proto_tree_add_expert_format(userdata_element, pinfo, &ei_trdp_reserved_not_zero,
2122
0
                                                         tvb, offset, el->width, "Element is not zero (%" PRId64 ").", vals);
2123
0
                    }
2124
0
                } else if (el->scale && g_scaled) {
2125
0
                    double formated_value = vals * el->scale + el->offset;
2126
0
                    proto_tree_add_double_format_value(userdata_element, el->hf_id, tvb, offset, el->width, formated_value,
2127
0
                                                       "%lg %s (raw=%" PRId64 ")", formated_value, el->unit, vals);
2128
0
                } else {
2129
0
                    if (g_scaled) vals += el->offset;
2130
0
                    proto_tree_add_int64(userdata_element, el->hf_id, tvb, offset, el->width, vals);
2131
0
                }
2132
0
                break;
2133
2134
            /* case TRDP_UINT8 ... TRDP_UINT64: */
2135
0
            case TRDP_UINT8:
2136
0
            case TRDP_UINT16:
2137
0
            case TRDP_UINT32:
2138
0
            case TRDP_UINT64:
2139
0
                if (el->isUnit.sc32) {
2140
0
                    uint32_t sid;
2141
0
                    int flags = PROTO_CHECKSUM_NO_FLAGS;
2142
0
                    bool trailerZero = tvb_skip_uint8(tvb, offset-12, 12+el->width, 0) == (unsigned)(offset+el->width);
2143
0
                    if (trailerZero) {
2144
0
                        flags |= PROTO_CHECKSUM_NOT_PRESENT;
2145
0
                    } else {
2146
0
                        uint32_t opTrnTopoCnt = tvb_get_ntohl(tvb, TRDP_HEADER_OFFSET_OP_TRN_TOPOCNT);
2147
0
                        if (ComId_make_SID(com, pinfo, opTrnTopoCnt, &sid)) {
2148
0
                            calced_crc = crc32_sc32_tvb_offset_seed(tvb, TRDP_HEADER_PD_OFFSET_DATA, offset-TRDP_HEADER_PD_OFFSET_DATA, sid);
2149
0
                            if (calced_crc == 0) calced_crc = 0xffffffff;
2150
0
                            flags |= PROTO_CHECKSUM_VERIFY;
2151
0
                        }
2152
0
                    }
2153
2154
0
                    proto_tree_add_checksum(userdata_element, tvb, offset, el->hf_id, hf_trdp_sc32_status, &ei_trdp_sdtv2_safetycode,
2155
0
                                            pinfo, calced_crc, ENC_BIG_ENDIAN, flags);
2156
2157
0
                    if (!trailerZero && invalid_cstUUID)
2158
0
                        proto_tree_add_expert_format(userdata_element, pinfo, &ei_trdp_sdtv2_safetycode, tvb, offset, el->width,
2159
0
                                                     "Preferences for TRDP protocol contain invalid consist UUID. Ignored. Please check format.");
2160
2161
0
                } else if (el->isUnit.ssc) { /* safeSequenceCount */
2162
0
                    int isOk = -1;
2163
                    /* approach conversation / pinfo->fd->visited */
2164
0
                    if (ComId_assert_SSC(com, pinfo, valuOrig, &isOk) && !isOk) {
2165
0
                        proto_tree_add_expert_format(userdata_element, pinfo, &ei_trdp_sdtv2_sequence, tvb, offset, el->width, "%d is not sequential.", valuOrig);
2166
0
                    }
2167
0
                    proto_tree_add_uint_format_value(userdata_element, el->hf_id, tvb, offset, el->width, valuOrig, "%u [%s]", valuOrig, isOk<0?"cannot verify":(isOk?"ok":"bad"));
2168
0
                } else if (el->isUnit.hide0) {
2169
0
                    if (valu != 0) {
2170
0
                        if (array_index)
2171
0
                            proto_tree_add_expert_format(userdata_element, pinfo, &ei_trdp_reserved_not_zero,
2172
0
                                                         tvb, offset, el->width, "Element [%d/%d] is not zero (%" PRIu64 ").", array_index, element_count, valu);
2173
0
                        else
2174
0
                            proto_tree_add_expert_format(userdata_element, pinfo, &ei_trdp_reserved_not_zero,
2175
0
                                                         tvb, offset, el->width, "Element is not zero (%" PRIu64 ").", valu);
2176
0
                    }
2177
0
                } else if (el->isUnit.version) {
2178
0
                    proto_tree_add_uint_format_value(userdata_element, el->hf_id, tvb, offset, el->width, valuOrig, "%02u.%02u", (valuOrig>>8)&0xff, (valuOrig>>0)&0xff);
2179
0
                } else if (el->scale && g_scaled) {
2180
0
                    double formated_value = valu * el->scale + el->offset;
2181
0
                    proto_tree_add_double_format_value(userdata_element, el->hf_id, tvb, offset, el->width, formated_value, "%lg %s (raw=%" PRIu64 ")", formated_value, el->unit, valu);
2182
0
                } else {
2183
0
                    if (g_scaled) valu += el->offset;
2184
0
                    proto_tree_add_uint64(userdata_element, el->hf_id, tvb, offset, el->width, valu);
2185
0
                }
2186
0
                break;
2187
2188
0
            case TRDP_REAL32:
2189
0
            case TRDP_REAL64:
2190
0
                if (el->scale && g_scaled) {
2191
0
                    double formated_value = real64 * el->scale + el->offset;
2192
0
                    proto_tree_add_double_format_value(userdata_element, el->hf_id, tvb, offset, el->width, formated_value,
2193
0
                                                       "%lg %s (raw=%lf)", formated_value, el->unit, real64);
2194
0
                } else {
2195
0
                    if (g_scaled) real64 += el->offset;
2196
0
                    proto_tree_add_double(userdata_element, el->hf_id, tvb, offset, el->width, real64);
2197
0
                }
2198
0
                bytelen = el->width;
2199
0
                break;
2200
2201
            /* case TRDP_TIMEDATE32 ... TRDP_TIMEDATE64: */
2202
0
            case TRDP_TIMEDATE32:
2203
0
            case TRDP_TIMEDATE48:
2204
0
            case TRDP_TIMEDATE64:
2205
                /* Is it allowed to have offset / scale?? I am not going to scale
2206
                 * seconds, but there could be use for an offset, esp. when misused as
2207
                 * relative time. */
2208
0
                if (g_scaled) nstime.secs += el->offset;
2209
0
                if (g_time_raw) {
2210
0
                    switch (el->type.id) {
2211
0
                    case TRDP_TIMEDATE32:
2212
0
                        proto_tree_add_time_format_value(userdata_element, el->hf_id, tvb, offset, el->width, &nstime,
2213
0
                                                         "%ji seconds", (intmax_t)nstime.secs);
2214
0
                    break;
2215
0
                    case TRDP_TIMEDATE48:
2216
0
                        proto_tree_add_time_format_value(userdata_element, el->hf_id, tvb, offset, el->width, &nstime,
2217
0
                                                         "%ji.%05d seconds (=%" PRIu64 " ticks)", (intmax_t)nstime.secs, (nstime.nsecs + 5000) / 10000, valu);
2218
0
                    break;
2219
0
                    case TRDP_TIMEDATE64:
2220
0
                        proto_tree_add_time_format_value(userdata_element, el->hf_id, tvb, offset, el->width, &nstime,
2221
0
                                                         "%ji.%06d seconds", (intmax_t)nstime.secs, nstime.nsecs / 1000);
2222
0
                    break;
2223
2224
0
                    }
2225
0
                } else
2226
0
                    proto_tree_add_time(userdata_element, el->hf_id, tvb, offset, el->width, &nstime);
2227
2228
0
                break;
2229
2230
0
            case TRDP_UUID:
2231
0
                proto_tree_add_guid(userdata_element, el->hf_id, tvb, offset, el->width, &guid);
2232
0
                break;
2233
2234
0
            default:
2235
0
                if (zero_there && (foldableDatasetMode || (g_fold0_mode == TRDP_FOLD_ALWAYS))) {
2236
                    //proto_tree_add_none_format(userdata_element, el->hf_id, tvb, offset, bytelen, "%s [zero, not used]", title);
2237
0
                    proto_tree_add_bytes_format_value(userdata_element, el->hf_id, tvb, offset, bytelen, NULL, "%s [zero, not used]", el->linkedDS->type.name);
2238
0
                } else {
2239
0
                    increment_dissection_depth(pinfo);
2240
                    // NOLINTNEXTLINE(misc-no-recursion)
2241
0
                    int offs = dissect_trdp_dataset( tvb, pinfo, userdata_element, com, el->linkedDS,
2242
0
                                                        offset, bytelen, dataset_level + 1,
2243
0
                                                        el->name, (element_count != 1) ? array_index : -1,
2244
0
                                                        FALSE, &zero_there);
2245
0
                    decrement_dissection_depth(pinfo);
2246
0
                    if (!offs) return 0; /* break dissecting, if things went sideways */
2247
0
                    bytelen = offs-offset;
2248
0
                }
2249
0
                break;
2250
0
            }
2251
2252
0
            if (nstime.secs || nstime.nsecs || real64) zero_here = FALSE;
2253
0
            if (el->type.id == TRDP_CHAR8 || el->type.id == TRDP_UTF16) element_count = 1; /* needs reset in that case, these are never branched in the tree */
2254
0
            offset += bytelen;
2255
2256
0
            if (array_index || element_count != 1) {
2257
                /* handle arrays */
2258
0
                ws_debug( "[%d / %d]", array_index, element_count);
2259
0
                if (++array_index >= element_count) {
2260
0
                    array_index = 0;
2261
0
                    userdata_element = trdp_userdata; /* restore the array-sub-tree when done */
2262
0
                }
2263
0
                potential_array_size = -1;
2264
0
                if (valu || vals) zero_here = FALSE;
2265
0
            } else {
2266
0
                ws_debug("[%d / %d], (type=%d) val-u=%" PRIu64 " val-s=%" PRId64 ".", array_index, element_count, el->type.id, valu, vals);
2267
2268
0
                potential_array_size = (el->type.id < TRDP_INT8 || el->type.id > TRDP_UINT64) ? -1 : (el->type.id >= TRDP_UINT8 ? (int)valu : (int)vals);
2269
0
            }
2270
2271
0
        } while (array_index);
2272
0
        if (!zero_here) *isAllZeros = false;
2273
0
    }
2274
2275
0
    return offset;
2276
0
}
2277
2278
/**
2279
 * @internal
2280
 * Build the special header for PD and MD datasets (and calls the function to extract the userdata)
2281
 *
2282
 * @param tvb               buffer
2283
 * @param pinfo             info for tht packet
2284
 * @param tree              to which the information are added
2285
 * @param ti_type           unused
2286
 * @param com               the already extracted com object
2287
 * @param trdp_string       extracted packet type
2288
 *
2289
 * @return size of the user data
2290
 */
2291
37
static uint32_t build_trdp_tree(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, proto_item **ti_type, ComId* com, char *trdp_string) {
2292
37
    proto_item *ti = NULL;
2293
37
    proto_tree *trdp_tree = NULL;
2294
37
    proto_item *_ti_type_tmp = NULL;
2295
37
    proto_item **pti_type = FALLBACK(ti_type,&_ti_type_tmp);
2296
2297
37
    uint32_t datasetlength = 0;
2298
37
    uint32_t offset = 0;
2299
2300
37
    API_TRACE;
2301
2302
    /* when the package is big enough extract some data. */
2303
37
    if (tvb_reported_length_remaining(tvb, 0) > TRDP_HEADER_PD_OFFSET_RESERVED) {
2304
34
        ti = proto_tree_add_item(tree, proto_trdp, tvb, 0, -1, ENC_NA);
2305
34
        trdp_tree = proto_item_add_subtree(ti, ett_trdp);
2306
2307
34
        proto_tree_add_item(trdp_tree, hf_trdp_sequencecounter, tvb, TRDP_HEADER_OFFSET_SEQCNT, 4, ENC_BIG_ENDIAN);
2308
34
        int verMain = tvb_get_uint8(tvb, TRDP_HEADER_OFFSET_PROTOVER);
2309
34
        int verSub = tvb_get_uint8(tvb, (TRDP_HEADER_OFFSET_PROTOVER + 1));
2310
34
        proto_tree_add_bytes_format_value(trdp_tree, hf_trdp_protocolversion, tvb, 4, 2, NULL, "%d.%d", verMain, verSub);
2311
2312
34
        *pti_type = proto_tree_add_item(trdp_tree, hf_trdp_type, tvb, TRDP_HEADER_OFFSET_TYPE, 2, ENC_ASCII);
2313
34
        proto_tree_add_item(trdp_tree, hf_trdp_comid,            tvb, TRDP_HEADER_OFFSET_COMID, 4, ENC_BIG_ENDIAN);
2314
34
        proto_tree_add_item(trdp_tree, hf_trdp_etb_topocount,    tvb, TRDP_HEADER_OFFSET_ETB_TOPOCNT, 4, ENC_BIG_ENDIAN);
2315
34
        proto_tree_add_item(trdp_tree, hf_trdp_op_trn_topocount, tvb, TRDP_HEADER_OFFSET_OP_TRN_TOPOCNT, 4, ENC_BIG_ENDIAN);
2316
34
        proto_tree_add_item_ret_uint(trdp_tree, hf_trdp_datasetlength,    tvb, TRDP_HEADER_OFFSET_DATASETLENGTH, 4, ENC_BIG_ENDIAN, &datasetlength);
2317
34
    } else {
2318
3
        expert_add_info_format(pinfo, tree, &ei_trdp_packet_small, "Packet too small for header information");
2319
3
    }
2320
2321
37
    if (trdp_string) {
2322
37
        switch (trdp_string[0]) {
2323
3
        case 'P':
2324
            /* PD specific stuff */
2325
3
            proto_tree_add_item(trdp_tree, hf_trdp_reserved,        tvb, TRDP_HEADER_PD_OFFSET_RESERVED, 4, ENC_BIG_ENDIAN);
2326
3
            proto_tree_add_item(trdp_tree, hf_trdp_reply_comid,     tvb, TRDP_HEADER_PD_OFFSET_REPLY_COMID, 4, ENC_BIG_ENDIAN);
2327
3
            proto_tree_add_item(trdp_tree, hf_trdp_reply_ipaddress, tvb, TRDP_HEADER_PD_OFFSET_REPLY_IPADDR, 4, ENC_BIG_ENDIAN);
2328
3
            offset = TRDP_HEADER_PD_OFFSET_FCSHEAD;
2329
3
            break;
2330
0
        case 'M':
2331
            /* MD specific stuff */
2332
0
            proto_tree_add_item(trdp_tree, hf_trdp_replystatus,     tvb, TRDP_HEADER_MD_OFFSET_REPLY_STATUS, 4, ENC_BIG_ENDIAN);
2333
0
            proto_tree_add_item(trdp_tree, hf_trdp_sessionid,       tvb, TRDP_HEADER_MD_SESSIONID, 16, ENC_BIG_ENDIAN);
2334
0
            proto_tree_add_item(trdp_tree, hf_trdp_replytimeout,    tvb, TRDP_HEADER_MD_REPLY_TIMEOUT, 4, ENC_BIG_ENDIAN);
2335
0
            proto_tree_add_item(trdp_tree, hf_trdp_sourceURI,       tvb, TRDP_HEADER_MD_SRC_URI, 32, ENC_ASCII);
2336
0
            proto_tree_add_item(trdp_tree, hf_trdp_destinationURI,  tvb, TRDP_HEADER_MD_DEST_URI, 32, ENC_ASCII);
2337
0
            offset = TRDP_HEADER_MD_OFFSET_FCSHEAD;
2338
0
            break;
2339
34
        default:
2340
34
            break;
2341
37
        }
2342
37
    }
2343
37
    if (offset) {
2344
3
        proto_tree_add_checksum(trdp_tree, tvb, offset, hf_trdp_fcs_head, hf_trdp_fcs_head_status, &ei_trdp_header_checksum,
2345
3
                                pinfo, crc32_802_tvb(tvb, offset), ENC_BIG_ENDIAN, PROTO_CHECKSUM_VERIFY);
2346
3
        offset += 4;
2347
2348
3
        if (datasetlength) {
2349
3
            bool isAllZeros = (g_fold0_mode == TRDP_FOLD_ALWAYS);
2350
3
            proto_tree_add_item(trdp_tree, hf_trdp_userdata, tvb, offset, datasetlength, ENC_NA);
2351
3
            if (isAllZeros) {
2352
                /* do a first run through the packet to assure the PDU is all zeros (or not). The actual tree is not modified. */
2353
0
                dissect_trdp_dataset(tvb, pinfo, trdp_tree, com, com?com->linkedDS:NULL, offset, datasetlength, 0, "dataset", -1,  TRUE, &isAllZeros);
2354
0
            }
2355
3
            if (!isAllZeros || g_fold0_mode == TRDP_FOLD_NEVER) {
2356
                /* grow the tree, either because the PDU is non-zero, or it is forced by fold-mode  */
2357
1
                dissect_trdp_dataset(tvb, pinfo, trdp_tree, com, com?com->linkedDS:NULL, offset, datasetlength, 0, "dataset", -1, FALSE, &isAllZeros);
2358
1
            }
2359
3
            offset = checkPaddingAndOffset(tvb, pinfo, trdp_tree, offset);
2360
3
        }
2361
3
    }
2362
37
    return offset;
2363
37
}
2364
2365
38
static int dissect_trdp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) {
2366
38
    uint32_t    trdp_comid;
2367
38
    char*       trdp_string;
2368
38
    uint32_t    parsed_size;
2369
38
    proto_item* ti_type = NULL;
2370
38
    ComId*      com = NULL;
2371
2372
    /* Load header fields and dictionary if not already done */
2373
38
    if (hf_trdp_type <= 0) {
2374
2
        proto_registrar_get_byname("trdp.type");
2375
2
    }
2376
2377
    /* Make entries in Protocol column ... */
2378
38
    if (col_get_writable(pinfo->cinfo, COL_PROTOCOL)) col_set_str(pinfo->cinfo, COL_PROTOCOL, PROTO_TAG_TRDP);
2379
2380
    /* and "info" column on summary display */
2381
38
    if (col_get_writable(pinfo->cinfo, COL_INFO))     col_clear(pinfo->cinfo, COL_INFO);
2382
2383
    /* Read required values from the package: */
2384
38
    trdp_string = (char *)tvb_format_text(pinfo->pool, tvb, TRDP_HEADER_OFFSET_TYPE, 2);
2385
38
    trdp_comid  = tvb_get_ntohl(tvb, TRDP_HEADER_OFFSET_COMID);
2386
38
    if (!trdp_comid && (trdp_string[0] == 'P'))
2387
0
        expert_add_info(pinfo, tree, &ei_trdp_proc_comid_zero);
2388
38
    else
2389
38
        com = TrdpDict_lookup_ComId(pTrdpParser, trdp_comid);
2390
2391
    /* Telegram that fits into one packet, or the header of huge telegram, that was reassembled */
2392
38
    parsed_size = build_trdp_tree(tvb, pinfo, tree, &ti_type, com, trdp_string);
2393
38
    if (tree == NULL) ws_debug("Dissector did not get a tree passed (type=%s, comid=%u, parsed=%u).", trdp_string, trdp_comid, parsed_size);
2394
2395
    /* Append the packet type into the information description */
2396
38
    if (col_get_writable(pinfo->cinfo, COL_INFO)) {
2397
        /* Display a info line */
2398
0
        col_append_fstr(pinfo->cinfo, COL_INFO, "comId: %5u ", trdp_comid);
2399
2400
        /* look-up the packet-type name */
2401
0
        const char **tt = trdp_types;
2402
0
        while (*tt && strcmp(trdp_string, *tt)) tt+=2;
2403
0
        col_append_str(pinfo->cinfo, COL_INFO, *(tt+1));
2404
0
        if (!*tt) expert_add_info_format(pinfo, ti_type, &ei_trdp_type_unknown, "Unknown TRDP Type: %s", trdp_string);
2405
2406
        /* Help with high-level name of ComId / Dataset */
2407
0
        if (com) {
2408
0
            if (com->name && *com->name) {
2409
0
                col_append_fstr(pinfo->cinfo, COL_INFO, " -> %s", com->name);
2410
0
            } else if (com->linkedDS) {
2411
0
                if (*com->linkedDS->type.name) {
2412
0
                    col_append_fstr(pinfo->cinfo, COL_INFO, " -> [%s]", com->linkedDS->type.name);
2413
0
                } else {
2414
0
                    col_append_fstr(pinfo->cinfo, COL_INFO, " -> [%u]", com->linkedDS->type.id);
2415
0
                }
2416
0
            }
2417
0
        }
2418
0
    }
2419
38
    ws_debug("Returning a parsed_size=%d", parsed_size); // tvb_captured_length(tvb)
2420
38
    return parsed_size;
2421
38
}
2422
2423
/** @fn static unsigned int get_trdp_tcp_message_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset)
2424
 *  @internal
2425
 *  @brief retrieve the expected size of the transmitted packet.
2426
 */
2427
34
static unsigned int get_trdp_tcp_message_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_) {
2428
34
    unsigned int datasetlength   = (unsigned int)tvb_get_ntohl(tvb, offset + TRDP_HEADER_OFFSET_DATASETLENGTH);
2429
34
    unsigned int without_padding = datasetlength + TRDP_MD_HEADERLENGTH/* + TRDP_FCS_LENGTH*/;
2430
34
    ws_debug("get_trdp_tcp_message_len (datasetlength=%d w/ padding=%d tvb_reported_length=%d / captured=%d)", datasetlength, (without_padding + 3) & (~3), tvb_reported_length(tvb), tvb_captured_length(tvb));
2431
34
    return (without_padding + 3) & (~3); /* round up to add padding */
2432
34
}
2433
2434
/**
2435
 * Code to analyze the actual TRDP packet, transmitted via TCP
2436
 *
2437
 * @param tvb       buffer
2438
 * @param pinfo     info for the packet
2439
 * @param tree      to which the information are added
2440
 * @param data      Collected information
2441
 *
2442
 * @return length
2443
 */
2444
15
static int dissect_trdp_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) {
2445
15
    API_TRACE;
2446
15
    if (!tvb_bytes_exist(tvb, 0, TRDP_MD_HEADERLENGTH)) {
2447
5
        ws_debug("Missing enough bytes %d/%d", tvb_captured_length(tvb), TRDP_MD_HEADERLENGTH);
2448
5
        return 0;
2449
5
    }
2450
2451
10
    tcp_dissect_pdus(tvb, pinfo, tree, TRUE, TRDP_MD_HEADERLENGTH, get_trdp_tcp_message_len, dissect_trdp, data);
2452
2453
10
    return tvb_reported_length(tvb);
2454
15
}
2455
2456
/* ========================================================================= */
2457
/* Register the protocol fields and subtrees with Wireshark
2458
 * (strongly inspired by the wimaxasncp plugin)
2459
 */
2460
2461
/* ========================================================================= */
2462
/* Modify the given string to make a suitable display filter                 */
2463
/*                                             copied from wimaxasncp plugin */
2464
0
static char *alnumerize(char *name) {
2465
0
    char *r = name; /* read pointer */
2466
0
    char *w = name; /* write pointer */
2467
0
    char c;
2468
2469
0
    for (; (c = *r); ++r) {
2470
0
        if (g_ascii_isalnum(c) || c == '_' || c == '.') { /* These characters are fine - copy them */
2471
0
            *(w++) = c;
2472
0
        } else if (c == ' ' || c == '-' || c == '/') {
2473
0
            if (w == name)       continue; /* Skip these others if haven't written any characters out yet */
2474
0
            if (*(w - 1) == '_') continue; /* Skip if we would produce multiple adjacent '_'s */
2475
2476
0
            *(w++) = '_'; /* OK, replace with underscore */
2477
0
        }
2478
        /* Other undesirable characters are just skipped */
2479
0
    }
2480
0
    *w = '\0'; /* Terminate and return modified string */
2481
0
    return name;
2482
0
}
2483
2484
/* ========================================================================= */
2485
2486
0
static void add_reg_info(int *hf_ptr, const char *name, const char *abbrev, enum ftenum type, int display, int bitmask, const char *blurb) {
2487
2488
0
    hf_register_info hf = {hf_ptr, {name, abbrev, type, display, NULL, bitmask, blurb, HFILL}};
2489
2490
0
    wmem_array_append_one(trdp_build_dict.hf, hf);
2491
0
}
2492
2493
/* ========================================================================= */
2494
2495
0
static void Element_add_reg_info(Element* el, const char* parentName) {
2496
0
    char *name;
2497
0
    char *abbrev;
2498
0
    char *blurb;
2499
0
    int *pett_id = &el->ett_id;
2500
2501
0
    name = wmem_strdup(NULL, el->name);
2502
0
    abbrev = alnumerize(wmem_strdup_printf(NULL, PROTO_FILTERNAME_TRDP_PDU ".%s.%s", parentName, el->name));
2503
2504
0
    if (el->scale || el->offset) {
2505
0
        blurb = wmem_strdup_printf(NULL, "An element of type=%s(%u) scaling *%4g plus offset %+0d in unit %s",
2506
0
                                el->type.name, el->type.id, FALLBACK(el->scale,1.0), el->offset, el->unit);
2507
0
    } else {
2508
0
        blurb = wmem_strdup_printf(NULL, "An element of type=%s(%u) with unit %s",
2509
0
                                el->type.name, el->type.id, el->unit);
2510
0
    }
2511
2512
0
    if (!((el->array_size == 1) || (el->type.id == TRDP_CHAR8) || (el->type.id == TRDP_UTF16))) {
2513
0
        wmem_array_append_one(trdp_build_dict.ett, pett_id);
2514
0
    }
2515
2516
0
    switch (el->type.id) {
2517
0
        case TRDP_BITSET8:
2518
0
            if (el->type.subtype == TRDP_BITSUBTYPE_BITSET8) {
2519
                /* TODO an Array of bitsets is currently not supported */
2520
0
                if (el->bits /*&& el->array_size == 1*/) {
2521
0
                    int **bitfields = el->bitfields;
2522
0
                    int *pb_ett_id = &el->bits_ett_id;
2523
0
                    wmem_array_append_one(trdp_build_dict.ett, pb_ett_id);
2524
0
                    for (int i=0;i<TRDP_BITSUBTYPE_BITS;i++) {
2525
0
                        if (*el->bits[i].name) {
2526
0
                            char* abbrev2 = alnumerize(wmem_strdup_printf(NULL, PROTO_FILTERNAME_TRDP_PDU ".%s.%s.%s", parentName, el->name, el->bits[i].name));
2527
0
                            add_reg_info( &el->bits[i].hf_id, el->bits[i].name, abbrev2, FT_BOOLEAN, 8, 1<<i, NULL);
2528
0
                            *bitfields = &el->bits[i].hf_id;
2529
0
                            bitfields++;
2530
0
                        }
2531
0
                    }
2532
0
                }
2533
0
                wmem_free(NULL, blurb);
2534
0
                add_reg_info(&el->hf_id, name, abbrev, FT_UINT8, BASE_HEX, 0, NULL);
2535
0
            } else {
2536
0
                add_reg_info(&el->hf_id, name, abbrev, FT_BOOLEAN, 8, 0, blurb);
2537
0
            }
2538
0
            break;
2539
0
        case TRDP_CHAR8:
2540
0
        case TRDP_UTF16:
2541
0
            add_reg_info(&el->hf_id, name, abbrev, el->array_size ? FT_STRING : FT_STRINGZ, BASE_NONE, 0, blurb);
2542
0
            break;
2543
2544
        /*    case TRDP_INT8 ... TRDP_INT64: not supported in MSVC :( */
2545
0
        case TRDP_INT8:
2546
0
        case TRDP_INT16:
2547
0
        case TRDP_INT32:
2548
0
        case TRDP_INT64:
2549
0
            if (el->scale && g_scaled) {
2550
0
                add_reg_info(&el->hf_id, name, abbrev, FT_DOUBLE, BASE_NONE, 0, blurb);
2551
0
            } else
2552
0
                add_reg_info(&el->hf_id, name, abbrev, FT_INT64, BASE_DEC, 0, blurb);
2553
0
            break;
2554
2555
        /*    case TRDP_UINT8 ... TRDP_UINT64: */
2556
0
        case TRDP_UINT8:
2557
0
        case TRDP_UINT16:
2558
0
        case TRDP_UINT32:
2559
0
        case TRDP_UINT64:
2560
0
            if (el->isUnit.sc32) {
2561
0
                add_reg_info(&el->hf_id, name, abbrev, FT_UINT32, BASE_HEX, 0, blurb);
2562
0
            } else if (el->isUnit.ssc) {
2563
0
                add_reg_info(&el->hf_id, name, abbrev, FT_UINT32, BASE_DEC, 0, blurb);
2564
0
            } else if (el->isUnit.version) {
2565
0
                add_reg_info(&el->hf_id, name, abbrev, FT_UINT16, BASE_DEC, 0, blurb);
2566
0
            } else if (el->scale && g_scaled) {
2567
0
                add_reg_info(&el->hf_id, name, abbrev, FT_DOUBLE, BASE_NONE,0, blurb);
2568
0
            } else
2569
0
                add_reg_info(&el->hf_id, name, abbrev, FT_UINT64, BASE_DEC, 0, blurb);
2570
0
            break;
2571
2572
0
        case TRDP_REAL32:
2573
0
        case TRDP_REAL64:
2574
0
            add_reg_info(&el->hf_id, name, abbrev, FT_DOUBLE, BASE_NONE, 0, blurb);
2575
0
            break;
2576
2577
        /*    case TRDP_TIMEDATE32 ... TRDP_TIMEDATE64:*/
2578
0
        case TRDP_TIMEDATE32:
2579
0
        case TRDP_TIMEDATE48:
2580
0
        case TRDP_TIMEDATE64:
2581
0
            add_reg_info(&el->hf_id, name, abbrev, g_time_raw ? FT_RELATIVE_TIME : FT_ABSOLUTE_TIME,
2582
0
                                                   g_time_raw ? 0 : (g_time_local ? ABSOLUTE_TIME_LOCAL : ABSOLUTE_TIME_UTC), 0, blurb);
2583
0
            break;
2584
2585
0
        case TRDP_UUID:
2586
0
            add_reg_info(&el->hf_id, name, abbrev, FT_GUID, BASE_NONE, 0, blurb);
2587
0
            break;
2588
2589
0
        default:
2590
0
            add_reg_info(&el->hf_id, name, abbrev, FT_BYTES, BASE_NONE, 0, blurb);
2591
2592
        /* as long as I do not track the hierarchy, do not recurse */
2593
        /* add_dataset_reg_info(el->linkedDS); */
2594
0
    }
2595
0
}
2596
2597
0
static void Dataset_add_reg_info(Dataset *ds) {
2598
0
    int *pett_id = &ds->ett_id;
2599
2600
0
    for (Element *el = ds->listOfElements; el; el = el->next) Element_add_reg_info(el, ds->type.name);
2601
2602
0
    if (ds->listOfElements)  wmem_array_append_one(trdp_build_dict.ett, pett_id);
2603
0
}
2604
2605
0
static void trdp_shutdown(void) {
2606
0
    TrdpDict_delete(pTrdpParser, proto_trdp);
2607
0
    pTrdpParser = NULL;
2608
0
}
2609
2610
2
static void register_trdp_fields(const char *prefix _U_) {
2611
2
    API_TRACE;
2612
2613
    /* List of header fields. */
2614
2
    static hf_register_info hf_base[] = {
2615
        /* All the general fields for the header */
2616
        // clang-format off
2617
2
        {&hf_trdp_sequencecounter, {"sequenceCounter", "trdp.sequencecounter", FT_UINT32, BASE_DEC,  NULL, 0x0, "", HFILL}},
2618
2
        {&hf_trdp_protocolversion, {"protocolVersion", "trdp.protocolversion", FT_BYTES,  BASE_NONE, NULL, 0x0, "", HFILL}},
2619
2
        {&hf_trdp_type,            {"msgtype", "trdp.type", FT_STRING, BASE_NONE, NULL, 0x0, "", HFILL}},
2620
2
        {&hf_trdp_comid,           {"comId", "trdp.comid", FT_UINT32, BASE_DEC,  NULL, 0x0, "", HFILL}},
2621
2
        {&hf_trdp_etb_topocount,   {"etbTopoCnt", "trdp.etbtopocnt", FT_UINT32, BASE_DEC,  NULL, 0x0, "", HFILL}},
2622
2
        {&hf_trdp_op_trn_topocount,{"opTrnTopoCnt", "trdp.optrntopocnt", FT_UINT32, BASE_DEC,  NULL, 0x0, "", HFILL}},
2623
2
        {&hf_trdp_datasetlength,   {"datasetLength", "trdp.datasetlength", FT_UINT32, BASE_DEC,  NULL, 0x0, "", HFILL}},
2624
2
        {&hf_trdp_padding,         {"padding", "trdp.padding", FT_BYTES,  BASE_NONE, NULL, 0x0, "", HFILL}},
2625
2626
        /* PD specific stuff */
2627
2
        {&hf_trdp_reserved,        {"reserved", "trdp.reserved", FT_UINT32, BASE_DEC,  NULL, 0x0, "", HFILL}},
2628
2
        {&hf_trdp_reply_comid,     {"replyComId", "trdp.replycomid", FT_UINT32, BASE_DEC,  NULL, 0x0, "", HFILL}}, /* only in PD request */
2629
2
        {&hf_trdp_reply_ipaddress, {"replyIpAddress", "trdp.replyip", FT_IPv4,   BASE_NONE, NULL, 0x0, "", HFILL}},
2630
2631
        /* MD specific stuff */
2632
2
        {&hf_trdp_replystatus,     {"replyStatus", "trdp.replystatus", FT_INT32,  BASE_DEC,  VALS(reply_status_names), 0x0, "", HFILL}},
2633
2
        {&hf_trdp_sessionid,       {"sessionUUID", "trdp.sessionid", FT_GUID,   BASE_NONE, NULL, 0x0, "", HFILL}},
2634
2
        {&hf_trdp_replytimeout,    {"replyTimeout", "trdp.replytimeout", FT_UINT32, BASE_DEC,  NULL, 0x0, "", HFILL}},
2635
2
        {&hf_trdp_sourceURI,       {"sourceUri", "trdp.sourceUri", FT_STRING, BASE_NONE, NULL, 0x0, "", HFILL}},
2636
2
        {&hf_trdp_destinationURI,  {"destinationURI", "trdp.destinationUri", FT_STRING, BASE_NONE, NULL, 0x0, "", HFILL}},
2637
2
        {&hf_trdp_userdata,        {"dataset", "trdp.rawdata", FT_BYTES,  BASE_NONE, NULL, 0x0, "", HFILL}},
2638
2639
2
        {&hf_trdp_fcs_head,        {"headerFcs", "trdp.fcshead", FT_UINT32, BASE_HEX,  NULL, 0x0, "", HFILL}},
2640
2
        {&hf_trdp_fcs_head_status, {"headerFcsStatus", "trdp.fcshead.status", FT_UINT8, BASE_NONE, VALS(proto_checksum_vals), 0x0, NULL, HFILL}},
2641
2
        {&hf_trdp_sc32_status,     {"safetyCodeStatus", "trdp.safetycode.status", FT_UINT8, BASE_NONE, VALS(proto_checksum_vals), 0x0, NULL, HFILL}},
2642
2643
        // clang-format on
2644
2
    };
2645
2646
    /* Setup protocol subtree array */
2647
2
    static int *ett_base[] = {
2648
2
        &ett_trdp,
2649
2
    };
2650
2651
    /* ------------------------------------------------------------------------
2652
     * load the XML dictionary
2653
     * ------------------------------------------------------------------------
2654
     */
2655
2656
2
    TrdpDict_delete(pTrdpParser, proto_trdp);
2657
2658
2
    if (trdp_build_dict.hf)  wmem_free(wmem_dic, trdp_build_dict.hf);
2659
2
    if (trdp_build_dict.ett) wmem_free(wmem_dic, trdp_build_dict.ett);
2660
2661
    //g_customTrdpDictionaryFolder
2662
2
    ws_info("TRDP custom dictionary is '%s' (proto=%d).", g_customTrdpDictionary, proto_trdp);
2663
2
    API_TRACE;
2664
2665
2
    GError *err = NULL;
2666
2
    char *basepath = g_basexml ? get_datafile_path("trdp", epan_get_environment_prefix()) : NULL;
2667
2
    const char* ld = (g_customTrdpDictionary && *g_customTrdpDictionary) ? g_customTrdpDictionary : g_customTrdpDictionaryFolder;
2668
2
    pTrdpParser = TrdpDict_new(basepath, ld, &err);
2669
2
    register_shutdown_routine(trdp_shutdown);
2670
2671
2
    API_TRACE;
2672
2
    if (err) {
2673
2
        report_failure("TRDP | XML input failed [%d]:\n%s", err->code, err->message);
2674
2
        g_error_free(err);
2675
2
    }
2676
2677
2
    g_free(basepath); /* is a g_malloc'ed string */
2678
2679
    /* ------------------------------------------------------------------------
2680
     * build the hf and ett dictionary entries
2681
     * ------------------------------------------------------------------------
2682
     */
2683
2684
2
    trdp_build_dict.hf =  wmem_array_new(wmem_dic, sizeof(hf_register_info));
2685
2
    trdp_build_dict.ett = wmem_array_new(wmem_dic, sizeof(int *));
2686
2687
2
    if (hf_trdp_type <= 0) {
2688
2
        proto_register_field_array(proto_trdp, hf_base, array_length(hf_base));
2689
2
        proto_register_subtree_array(ett_base, array_length(ett_base));
2690
2
    }
2691
2692
2
    if (pTrdpParser) {
2693
        /* arrays use the same hf */
2694
        /* don't care about comID linkage, as I really want to index all datasets,
2695
         * regardless of their hierarchy */
2696
0
        for (Dataset *ds = pTrdpParser->mTableDataset; ds; ds = ds->next) Dataset_add_reg_info(ds);
2697
0
    }
2698
2699
    /* Required function calls to register the header fields and subtrees used */
2700
2701
2
    proto_register_field_array(proto_trdp, (hf_register_info *)wmem_array_get_raw(trdp_build_dict.hf), wmem_array_get_count(trdp_build_dict.hf));
2702
2703
2
    proto_register_subtree_array((int **)wmem_array_get_raw(trdp_build_dict.ett), wmem_array_get_count(trdp_build_dict.ett));
2704
2
}
2705
2706
14
void proto_reg_handoff_trdp(void) {
2707
14
    static bool initialized = FALSE;
2708
14
    static unsigned int pd_port=0;
2709
14
    static unsigned int md_port=0;
2710
14
    API_TRACE;
2711
2712
14
    if (initialized == FALSE) {
2713
14
        initialized = TRUE;
2714
14
    } else {
2715
0
        if (pd_port != g_pd_port) {
2716
0
            dissector_delete_uint("udp.port", pd_port, trdp_handle);
2717
0
        }
2718
0
        if (md_port != g_md_port) {
2719
0
            dissector_delete_uint("udp.port", md_port, trdp_handle);
2720
0
            dissector_delete_uint("tcp.port", md_port, trdp_TCP_handle);
2721
0
        }
2722
0
    }
2723
14
    if (pd_port != g_pd_port) {
2724
14
        pd_port = g_pd_port;
2725
14
        dissector_add_uint_with_preference("udp.port", pd_port, trdp_handle);
2726
14
    }
2727
14
    if (md_port != g_md_port) {
2728
14
        md_port = g_md_port;
2729
14
        dissector_add_uint_with_preference("udp.port", md_port, trdp_handle);
2730
14
        dissector_add_uint_with_preference("tcp.port", md_port, trdp_TCP_handle);
2731
14
    }
2732
2733
    /* Reload header fields and dictionary but only, if it's been in use before */
2734
14
    if (hf_trdp_type > 0) register_trdp_fields(NULL);
2735
2736
14
    if (g_cstUUID) {
2737
14
        uint8_t x[16];
2738
14
        int i=0;
2739
14
        const char* s = g_cstUUID;
2740
14
        while(*s && i < 16) {
2741
0
            if (*s == '-') {
2742
0
                s++;
2743
0
            } else {
2744
0
                int8_t a = ws_xton(*s++);
2745
0
                if (a < 0) break;
2746
0
                int8_t b = ws_xton(*s++);
2747
0
                if (b < 0) break;
2748
0
                x[i++] = (uint8_t)a << 4 | b;
2749
0
            }
2750
0
        }
2751
2752
14
        if (i == 16) {
2753
0
            memcpy(cstUUID, x, sizeof(cstUUID));
2754
0
            invalid_cstUUID = false;
2755
0
            have_cstUUID = true;
2756
14
        } else {
2757
14
            invalid_cstUUID = !!g_cstUUID[0]; /* invalid, if the string is not empty */
2758
14
            have_cstUUID = false;
2759
14
        }
2760
14
    } else {
2761
0
        invalid_cstUUID = false;
2762
0
        have_cstUUID = false;
2763
0
    }
2764
14
}
2765
2766
14
void proto_register_trdp(void) {
2767
14
    module_t *trdp_module;
2768
2769
14
    enum_val_t *bitsetenumvals;
2770
14
    gsize bitset_offset = 0;
2771
14
    gsize bitset_types = 0;
2772
2773
14
    wmem_dic = wmem_allocator_new(WMEM_ALLOCATOR_SIMPLE);
2774
2775
28
    while (ElBasics[bitset_offset].id              != TRDP_BITSET8) bitset_offset++;
2776
56
    while (ElBasics[bitset_offset+bitset_types].id == TRDP_BITSET8) bitset_types++;
2777
2778
14
    bitsetenumvals = wmem_alloc0_array(wmem_epan_scope(), enum_val_t, bitset_types + 1);
2779
56
    for (gsize i = 0; i < bitset_types; i++) {
2780
42
        bitsetenumvals[i].description = ElBasics[bitset_offset+i].name;
2781
42
        bitsetenumvals[i].name = wmem_ascii_strdown(wmem_epan_scope(), ElBasics[bitset_offset+i].name, -1);
2782
42
        bitsetenumvals[i].value = (int)ElBasics[bitset_offset+i].subtype;
2783
42
    }
2784
2785
14
    enum_val_t *endianenumvals = wmem_alloc0_array(wmem_epan_scope(), enum_val_t, 2 + 1);
2786
14
    endianenumvals[0].description = "BE";
2787
14
    endianenumvals[0].name = "be";
2788
14
    endianenumvals[0].value = TRDP_ENDSUBTYPE_BIG;
2789
14
    endianenumvals[1].description = "LE (non-standard)";
2790
14
    endianenumvals[1].name = "le";
2791
14
    endianenumvals[1].value = TRDP_ENDSUBTYPE_LIT;
2792
2793
14
    enum_val_t *foldenumvals = wmem_alloc0_array(wmem_epan_scope(), enum_val_t, 3 + 1);
2794
14
    foldenumvals[0].description = "never";
2795
14
    foldenumvals[0].name = "never";
2796
14
    foldenumvals[0].value = TRDP_FOLD_NEVER;
2797
14
    foldenumvals[1].description = "XML config unit";
2798
14
    foldenumvals[1].name = "config";
2799
14
    foldenumvals[1].value = TRDP_FOLD_CONFIG;
2800
14
    foldenumvals[2].description = "always";
2801
14
    foldenumvals[2].name = "always";
2802
14
    foldenumvals[2].value = TRDP_FOLD_ALWAYS;
2803
2804
14
    API_TRACE;
2805
2806
    /* Register the protocol name and description */
2807
14
    proto_trdp      = proto_register_protocol(PROTO_NAME_TRDP, PROTO_TAG_TRDP, PROTO_FILTERNAME_TRDP);
2808
14
    trdp_handle     = register_dissector(PROTO_DISSECTORNAME_TRDP,    (dissector_t)dissect_trdp,     proto_trdp);
2809
14
    trdp_TCP_handle = register_dissector(PROTO_DISSECTORNAME_TRDPTCP, (dissector_t)dissect_trdp_tcp, proto_trdp);
2810
    /* Delay registration of com-id and dataset-id definitions */
2811
14
    proto_register_prefix("trdp", register_trdp_fields);
2812
2813
14
    trdp_module     = prefs_register_protocol(proto_trdp, proto_reg_handoff_trdp);
2814
2815
    /* Register the preference */
2816
14
    prefs_register_static_text_preference( trdp_module, "dissector_summary",
2817
14
        "Version 20260314",
2818
14
        NULL);
2819
2820
14
    prefs_register_bool_preference( trdp_module, "basexml",
2821
14
        "Load basic set of comIDs and dataset definitions from IEC 61375-2-3",
2822
14
        "When ticked, basic definitions of IEC 61375-2-3 are loaded. If that conflicts with your use or your definitions "
2823
14
        "- untick. If there's a bug or data missing, please file an issue.",
2824
14
        &g_basexml);
2825
2826
14
    prefs_set_preference_effect_fields(trdp_module, "basexml");
2827
2828
14
    prefs_register_filename_preference( trdp_module, "configfile",
2829
14
        "Custom TRDP configuration file",
2830
14
        "Custom TRDP configuration file",
2831
14
        &g_customTrdpDictionary, FALSE);
2832
2833
14
    prefs_set_preference_effect_fields(trdp_module, "configfile");
2834
2835
14
    prefs_register_directory_preference( trdp_module, "configfolder",
2836
14
        "Custom TRDP configuration file folder",
2837
14
        "Custom TRDP configuration file folder",
2838
14
        &g_customTrdpDictionaryFolder);
2839
2840
14
    prefs_set_preference_effect_fields(trdp_module, "configfolder");
2841
2842
14
    prefs_register_static_text_preference( trdp_module, "xml_summary",
2843
14
        "Please chose only either a file or a folder and leave the other field empty. Be sure, not to have conflicting versions of datasets or com-ids in that target folder - the file parser will be pesky.",
2844
14
        NULL);
2845
2846
14
    prefs_register_enum_preference( trdp_module, "bitset.subtype",
2847
14
        "Select default sub-type for TRDP-Element type 1",
2848
14
        "Type 1 can be interpreted differently, as BOOL, ANTIVALENT or BITSET. Select the fallback, if the element type is not given literally.",
2849
14
        &g_bitset_subtype, bitsetenumvals, FALSE);
2850
2851
14
    prefs_set_preference_effect_fields( trdp_module, "bitset.subtype");
2852
2853
14
    prefs_register_enum_preference( trdp_module, "numeric.subtype",
2854
14
        "Select default byte-order for TRDP-Element types (5-7,9-13)",
2855
14
        "Number types can be interpreted differently, as BE or LE (non-standard). Select the fallback, if the element type is not given literally (e.g., UINT16 vs UINT16_LE).",
2856
14
        &g_endian_subtype, endianenumvals, FALSE);
2857
2858
14
    prefs_set_preference_effect_fields( trdp_module, "numeric.subtype");
2859
2860
14
    prefs_register_enum_preference( trdp_module, "strings.le",
2861
14
        "Select default byte-order for TRDP-Element type 3 (UTF-16 strings).",
2862
14
        "Wide-character strings can be interpreted differently, as BE or LE (non-standard). Select the fallback, if the element type is not given literally (e.g., UTF16 vs UTF16_LE).",
2863
14
        &g_wchar_subtype, endianenumvals, FALSE);
2864
2865
14
    prefs_set_preference_effect_fields( trdp_module, "strings.le");
2866
2867
14
    prefs_register_enum_preference( trdp_module, "uuid.subtype",
2868
14
        "Select default byte-order for UUID fields.",
2869
14
        "UUID can be interpreted differently, as BE or LE (non-standard). Select the fallback, if the element type is not given literally (e.g., UUID vs UUID_LE). This does not affect the VDP-Trailer check.",
2870
14
        &g_uuid_subtype, endianenumvals, FALSE);
2871
2872
14
    prefs_set_preference_effect_fields( trdp_module, "uuid.subtype");
2873
2874
14
    prefs_register_enum_preference( trdp_module, "fold0",
2875
14
        "Hide a dataset if it is all zeros",
2876
14
        "If a dataset is all zeros (except array length values) it is hidden. For individual hiding, set the dataset referencing element's unit to \"fold0\".",
2877
14
        &g_fold0_mode, foldenumvals, FALSE);
2878
2879
14
    prefs_set_preference_effect_fields( trdp_module, "fold0");
2880
2881
14
    prefs_register_bool_preference( trdp_module, "time.local",
2882
14
        "Display time-types as local time, untick for UTC / no offsets.",
2883
14
        "Time types should be based on UTC. When ticked, Wireshark adds on local timezone offset. Untick if you like UTC to be displayed, or the source is not UTC.",
2884
14
        &g_time_local);
2885
2886
14
    prefs_register_bool_preference( trdp_module, "time.raw",
2887
14
        "Display time-types as raw seconds, not absolute time.",
2888
14
        "Time types should be absolute time since the UNIX-Epoch. When ticked, they are shown as seconds.",
2889
14
        &g_time_raw);
2890
2891
14
    prefs_register_bool_preference( trdp_module, "0strings",
2892
14
        "Variable-length CHAR8 and UTF16 arrays are 0-terminated. (non-standard)",
2893
14
        "When ticked, the length of a variable-length string (array-size=0) is calculated from searching for a terminator instead of using a previous length element.",
2894
14
        &g_0strings);
2895
2896
14
    prefs_register_bool_preference( trdp_module, "char8utf8",
2897
14
        "Interpret CHAR8 arrays as UTF-8.",
2898
14
        "When ticked, CHAR8 arrays are interpreted as UTF-8 string. If it fails, an exception is thrown. Untick if you need to see weird ASCII as C-escapes.",
2899
14
        &g_char8_is_utf8);
2900
2901
14
    prefs_register_bool_preference( trdp_module, "scaled",
2902
14
        "Use scaled value for filter.",
2903
14
        "When ticked, uses scaled values for filtering and display, otherwise the raw value.",
2904
14
        &g_scaled);
2905
2906
14
    prefs_register_string_preference( trdp_module, "sdtv2.consist_uuid",
2907
14
        "cstUUID used for VDP_TRAILER check",
2908
14
        "consist UUID for Safety Trailer calculation. Leave empty for consist-local. Insert only valid UUIDs-strings copied, "
2909
14
        "e.g., from some UUID-field in the format 00112233-4455-6677-8899-aabbccddeeff. Invalid values will be ignored w/o warning.",
2910
14
        &g_cstUUID);
2911
14
    prefs_set_preference_effect_fields( trdp_module, "sdtv2.consist_uuid");
2912
2913
14
    prefs_register_uint_preference( trdp_module, "pd.udp.port",
2914
14
        "PD message Port",
2915
14
        "UDP port for PD messages (Default port is " TRDP_DEFAULT_STR_PD_PORT ")",
2916
14
        10 /*base */, &g_pd_port);
2917
2918
14
    prefs_register_uint_preference( trdp_module, "md.udptcp.port",
2919
14
        "MD message Port",
2920
14
        "UDP and TCP port for MD messages (Default port is " TRDP_DEFAULT_STR_MD_PORT ")",
2921
14
        10 /*base */, &g_md_port);
2922
2923
    /* abandon legacy prefs */
2924
14
    prefs_register_obsolete_preference( trdp_module, "udp.port");
2925
14
    prefs_register_obsolete_preference( trdp_module, "tcp.port");
2926
14
    prefs_register_obsolete_preference( trdp_module, "sdtv2.sid");
2927
2928
    /* Register expert information */
2929
14
    expert_module_t *expert_trdp;
2930
14
    static ei_register_info ei[] = {
2931
14
        {&ei_trdp_type_unknown,     {"trdp.type_unknown",      PI_UNDECODED, PI_WARN, "TRDP type unknown", EXPFILL}},
2932
14
        {&ei_trdp_packet_small,     {"trdp.packet_size",       PI_UNDECODED, PI_WARN, "TRDP packet too small", EXPFILL}},
2933
14
        {&ei_trdp_userdata_empty,   {"trdp.userdata_empty",    PI_UNDECODED, PI_WARN, "TRDP user data is empty", EXPFILL}},
2934
14
        {&ei_trdp_proc_comid_zero,  {"trdp.process_comid_zero",PI_MALFORMED, PI_WARN, "Process packets must have com-id > 0", EXPFILL}},
2935
14
        {&ei_trdp_userdata_wrong,   {"trdp.userdata_wrong",    PI_UNDECODED, PI_WARN, "TRDP user data has wrong format", EXPFILL}},
2936
14
        {&ei_trdp_config_notparsed, {"trdp.config_unparsable", PI_UNDECODED, PI_WARN, "TRDP XML configuration cannot be parsed", EXPFILL}},
2937
14
        {&ei_trdp_padding_not_zero, {"trdp.padding_non_zero",  PI_MALFORMED, PI_WARN, "TRDP Padding not filled with zero", EXPFILL}},
2938
14
        {&ei_trdp_array_wrong,      {"trdp.array",             PI_PROTOCOL,  PI_WARN, "Dynamic array has unsupported datatype for length", EXPFILL}},
2939
14
        {&ei_trdp_faulty_antivalent,{"trdp.faulty_antivalent", PI_MALFORMED, PI_WARN, "Data contains faulty antivalent value.", EXPFILL}},
2940
14
        {&ei_trdp_reserved_not_zero,{"trdp.reserved_non_zero", PI_MALFORMED, PI_WARN, "Reserved attribute is not zero", EXPFILL}},
2941
14
        {&ei_trdp_header_checksum,  {"trdp.checksum_hrd.bad",  PI_CHECKSUM,  PI_WARN, "Header checksum error.", EXPFILL}},
2942
14
        {&ei_trdp_sdtv2_safetycode, {"trdp.sdtv2_safetycode.bad",PI_CHECKSUM,PI_WARN, "SDTv2 SafetyCode check error.", EXPFILL}},
2943
14
        {&ei_trdp_sdtv2_sequence,   {"trdp.sdtv2_sequence",    PI_SEQUENCE,  PI_WARN, "SDTv2 SafetySequenceCounter check error.", EXPFILL}},
2944
14
    };
2945
2946
14
    expert_trdp = expert_register_protocol(proto_trdp);
2947
14
    expert_register_field_array(expert_trdp, ei, array_length(ei));
2948
14
}