/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 | } |