3 # Generate Wireshark Dissectors for electronic trading/market data
4 # protocols such as ETI/EOBI.
6 # Targets Wireshark 3.5 or later.
8 # SPDX-FileCopyrightText: © 2021 Georg Sauthoff <mail@gms.tf>
9 # SPDX-License-Identifier: GPL-2.0-or-later
15 import xml
.etree
.ElementTree
as ET
18 # inlined from upstream's etimodel.py
22 def get_max_sizes(st
, dt
):
24 for name
, e
in dt
.items():
25 v
= e
.get('size', '0')
27 for name
, e
in itertools
.chain((i
for i
in st
.items() if i
[1].get('type') != 'Message'),
28 (i
for i
in st
.items() if i
[1].get('type') == 'Message')):
31 x
= h
.get(m
.get('type'), 0)
32 s
+= x
* int(m
.get('cardinality'))
36 def get_min_sizes(st
, dt
):
38 for name
, e
in dt
.items():
39 v
= e
.get('size', '0')
40 if e
.get('variableSize') is None:
44 for name
, e
in itertools
.chain((i
for i
in st
.items() if i
[1].get('type') != 'Message'),
45 (i
for i
in st
.items() if i
[1].get('type') == 'Message')):
48 x
= h
.get(m
.get('type'), 0)
49 s
+= x
* int(m
.get('minCardinality', '1'))
53 # end # inlined from upstream's etimodel.py
56 def get_used_types(st
):
57 xs
= set(y
.get('type') for _
, x
in st
.items() for y
in x
)
60 def get_data_types(d
):
62 x
= r
.find('DataTypes')
70 x
= r
.find('Structures')
76 def get_templates(st
):
78 for k
, v
in st
.items():
79 if v
.get('type') == 'Message':
80 ts
.append((int(v
.get('numericID')), k
))
85 def gen_header(proto
, desc
, o
=sys
.stdout
):
86 if proto
.startswith('eti') or proto
.startswith('xti'):
87 ph
= '#include "packet-tcp.h" // tcp_dissect_pdus()'
89 ph
= '#include "packet-udp.h" // udp_dissect_pdus()'
90 print(f
'''// auto-generated by Georg Sauthoff's eti2wireshark.py
93 * Routines for {proto.upper()} dissection
94 * Copyright 2021, Georg Sauthoff <mail@gms.tf>
96 * Wireshark - Network traffic analyzer
97 * By Gerald Combs <gerald@wireshark.org>
98 * Copyright 1998 Gerald Combs
100 * SPDX-License-Identifier: GPL-2.0-or-later
104 * The {desc} ({proto.upper()}) is an electronic trading protocol
105 * that is used by a few exchanges (Eurex, Xetra, ...).
107 * It's a Length-Tag based protocol consisting of mostly fix sized
108 * request/response messages.
111 * https://en.wikipedia.org/wiki/List_of_electronic_trading_protocols#Europe
112 * https://github.com/gsauthof/python-eti#protocol-descriptions
113 * https://github.com/gsauthof/python-eti#protocol-introduction
120 #include <epan/packet.h> // Should be first Wireshark include (other than config.h)
122 #include <epan/expert.h> // expert info
124 #include <inttypes.h>
125 #include <stdio.h> // snprintf()
129 /* (Required to prevent [-Wmissing-prototypes] warnings */
130 void proto_reg_handoff_{proto}(void);
131 void proto_register_{proto}(void);
133 static dissector_handle_t {proto}_handle;
135 static int proto_{proto};
139 def name2ident(name
):
142 for i
, c
in enumerate(name
):
153 def gen_enums(dt
, ts
, o
=sys
.stdout
):
154 print('static const value_string template_id_vals[] = { // TemplateID', file=o
)
155 min_tid
, max_tid
= ts
[0][0], ts
[-1][0]
156 xs
= [None] * (max_tid
- min_tid
+ 1)
158 xs
[tid
-min_tid
] = name
159 for i
, name
in enumerate(xs
):
161 print(f
' {{ {min_tid + i}, "Unknown" }},', file=o
)
163 print(f
' {{ {min_tid + i}, "{name}" }},', file=o
)
164 print(''' { 0, NULL }
166 static value_string_ext template_id_vals_ext = VALUE_STRING_EXT_INIT(template_id_vals);''', file=o
)
167 name2access
= { 'TemplateID': '&template_id_vals_ext' }
170 for name
, e
in dt
.items():
171 vs
= [ (x
.get('value'), x
.get('name')) for x
in e
.findall('ValidValue') ]
174 if e
.get('rootType') == 'String' and e
.get('size') != '1':
177 ident
= name2ident(name
)
179 nv
= e
.get('noValue')
180 ws
= [ v
[0] for v
in vs
]
182 if nv
.startswith('0x0') and e
.get('rootType') == 'String':
184 vs
.append( (nv
, 'NO_VALUE') )
186 if e
.get('type') == 'int':
187 vs
.sort(key
= lambda x
: int(x
[0], 0))
189 vs
.sort(key
= lambda x
: ord(x
[0]))
190 s
= '-'.join(f
'{v[0]}:{v[1]}' for v
in vs
)
195 name2access
[name
] = name2access
[x
]
196 print(f
'// {name} aliased by {x}', file=o
)
199 print(f
'static const value_string {ident}_vals[] = {{ // {name}', file=o
)
200 for i
, v
in enumerate(vs
):
201 if e
.get('rootType') == 'String':
202 k
= f
"'{v[0]}'" if ord(v
[0]) != 0 else '0'
203 print(f
''' {{ {k}, "{v[1]}" }},''', file=o
)
205 print(f
' {{ {v[0]}, "{v[1]}" }},', file=o
)
206 print(''' { 0, NULL }
210 print(f
'static value_string_ext {ident}_vals_ext = VALUE_STRING_EXT_INIT({ident}_vals);', file=o
)
211 name2access
[name
] = f
'&{ident}_vals_ext'
213 name2access
[name
] = f
'VALS({ident}_vals)'
218 def get_fields(st
, dt
):
220 for name
, e
in st
.items():
222 t
= dt
.get(m
.get('type'))
225 if not (is_int(t
) or is_fixed_string(t
) or is_var_string(t
)):
230 raise RuntimeError(f
'Mismatching type for: {name}')
233 vs
= list(seen
.items())
237 def gen_field_handles(st
, dt
, proto
, o
=sys
.stdout
):
238 print(f
'''static expert_field ei_{proto}_counter_overflow;
239 static expert_field ei_{proto}_invalid_template;
240 static expert_field ei_{proto}_invalid_length;''', file=o
)
241 if not proto
.startswith('eobi'):
242 print(f
'static expert_field ei_{proto}_unaligned;', file=o
)
243 print(f
'''static expert_field ei_{proto}_missing;
244 static expert_field ei_{proto}_overused;
247 vs
= get_fields(st
, dt
)
248 print(f
'static int hf_{proto}[{len(vs)}];', file=o
)
249 print(f
'''static int hf_{proto}_dscp_exec_summary;
250 static int hf_{proto}_dscp_improved;
251 static int hf_{proto}_dscp_widened;''', file=o
)
252 print('enum Field_Handle_Index {', file=o
)
253 for i
, (name
, _
) in enumerate(vs
):
254 c
= ' ' if i
== 0 else ','
255 print(f
' {c} {name.upper()}_FH_IDX', file=o
)
259 if is_timestamp_ns(t
):
260 return 'FT_ABSOLUTE_TIME'
264 if t
.get('rootType') == 'String':
266 u
= 'U' if is_unsigned(t
) else ''
267 if t
.get('size') is None:
268 raise RuntimeError(f
'None size: {t.get("name")}')
269 size
= int(t
.get('size')) * 8
270 return f
'FT_{u}INT{size}'
271 if is_fixed_string(t
) or is_var_string(t
):
272 # NB: technically, ETI fixed-strings are blank-padded,
273 # unless they are marked NO_VALUE, in that case
274 # the first byte is zero, followed by unspecified content.
275 # Also, some fixed-strings are zero-terminated, where again
276 # the bytes following the terminator are unspecified.
277 return 'FT_STRINGZTRUNC'
278 raise RuntimeError('unexpected type')
281 if is_timestamp_ns(t
):
282 return 'ABSOLUTE_TIME_UTC'
286 if t
.get('rootType') == 'String':
287 # NB: basically only used when enum and value is unknown
291 if is_fixed_string(t
) or is_var_string(t
):
292 # previously 'STR_ASCII', which was removed upstream
293 # cf. 19dcb725b61e384f665ad4b955f3b78f63e626d9
295 raise RuntimeError('unexpected type')
297 def gen_field_info(st
, dt
, n2enum
, proto
='eti', o
=sys
.stdout
):
298 print(' static hf_register_info hf[] ={', file=o
)
299 vs
= get_fields(st
, dt
)
300 for i
, (name
, t
) in enumerate(vs
):
301 c
= ' ' if i
== 0 else ','
304 if is_enum(t
) and not is_dscp(t
):
305 vals
= n2enum
[t
.get('name')]
306 if vals
.startswith('&'):
307 extra_enc
= '| BASE_EXT_STRING'
313 print(f
''' {c} {{ &hf_{proto}[{name.upper()}_FH_IDX],
314 {{ "{name}", "{proto}.{name.lower()}",
315 {ft}, {enc}{extra_enc}, {vals}, 0x0,
318 print(f
''' , {{ &hf_{proto}_dscp_exec_summary,
319 {{ "DSCP_ExecSummary", "{proto}.dscp_execsummary",
320 FT_BOOLEAN, 8, NULL, 0x10,
323 , {{ &hf_{proto}_dscp_improved,
324 {{ "DSCP_Improved", "{proto}.dscp_improved",
325 FT_BOOLEAN, 8, NULL, 0x20,
328 , {{ &hf_{proto}_dscp_widened,
329 {{ "DSCP_Widened", "{proto}.dscp_widened",
330 FT_BOOLEAN, 8, NULL, 0x40,
336 def gen_subtree_handles(st
, proto
='eti', o
=sys
.stdout
):
337 ns
= [ name
for name
, e
in st
.items() if e
.get('type') != 'Message' ]
339 h
= dict( (n
, i
) for i
, n
in enumerate(ns
, 1) )
340 print(f
'static int ett_{proto}[{len(ns) + 1}];', file=o
)
341 print(f
'static int ett_{proto}_dscp;', file=o
)
345 def gen_subtree_array(st
, proto
='eti', o
=sys
.stdout
):
346 n
= sum(1 for name
, e
in st
.items() if e
.get('type') != 'Message')
348 s
= ', '.join(f
'&ett_{proto}[{i}]' for i
in range(n
))
349 print(f
' static int * const ett[] = {{ {s}, &ett_{proto}_dscp }};', file=o
)
352 def gen_fields_table(st
, dt
, sh
, o
=sys
.stdout
):
356 for name
, e
in st
.items():
357 if e
.get('type') == 'Message':
359 if name
.endswith('Comp'):
364 s
= '\\0'.join(names
)
365 print(f
' static const char struct_names[] = "{s}";', file=o
)
367 xs
= [ x
for x
in st
.items() if x
[1].get('type') != 'Message' ]
368 xs
+= [ x
for x
in st
.items() if x
[1].get('type') == 'Message' ]
369 print(' static const struct ETI_Field fields[] = {', file=o
)
374 print(f
' // {name}@{i}', file=o
)
378 t
= dt
.get(m
.get('type'))
379 c
= ' ' if i
== 0 else ','
381 size
= int(t
.get('size')) if t
is not None else 0
383 fh
= f
'{m.get("name").upper()}_FH_IDX'
385 print(f
' {c} {{ ETI_PADDING, 0, {size}, 0, 0 }}', file=o
)
386 elif is_fixed_point(t
):
388 raise RuntimeError('only supporting 8 byte fixed point')
389 fraction
= int(t
.get('precision'))
391 raise RuntimeError('unusual high precisio in fixed point')
392 print(f
' {c} {{ ETI_FIXED_POINT, {fraction}, {size}, {fh}, 0 }}', file=o
)
393 elif is_timestamp_ns(t
):
395 raise RuntimeError('only supporting timestamps')
396 print(f
' {c} {{ ETI_TIMESTAMP_NS, 0, {size}, {fh}, 0 }}', file=o
)
398 print(f
' {c} {{ ETI_DSCP, 0, {size}, {fh}, 0 }}', file=o
)
400 u
= 'U' if is_unsigned(t
) else ''
401 if t
.get('rootType') == 'String':
407 if t
.get('type') == 'Counter':
408 counters
[m
.get('name')] = cnt
409 suf
= f
' // <- counter@{cnt}'
411 raise RuntimeError(f
'too many counters in message: {name}')
414 if typ
!= 'ETI_UINT':
415 raise RuntimeError('only unsigned counters supported')
417 raise RuntimeError('only smaller counters supported')
419 ett_idx
= t
.get('maxValue')
424 print(f
' {c} {{ {typ}, {rep}, {size}, {fh}, {ett_idx} }}{suf}', file=o
)
425 elif is_fixed_string(t
):
426 print(f
' {c} {{ ETI_STRING, 0, {size}, {fh}, 0 }}', file=o
)
427 elif is_var_string(t
):
430 print(f
' {c} {{ ETI_VAR_STRING, {x}, {size}, {fh}, 0 }}', file=o
)
433 fields_idx
= fields2idx
[a
]
436 counter_off
= counters
[k
]
437 typ
= 'ETI_VAR_STRUCT'
441 names_off
= name2off
[m
.get('type')]
443 print(f
' {c} {{ {typ}, {counter_off}, {names_off}, {fields_idx}, {ett_idx} }} // {m.get("name")}', file=o
)
445 print(' , { ETI_EOF, 0, 0, 0, 0 }', file=o
)
450 def gen_template_table(min_templateid
, n
, ts
, fields2idx
, o
=sys
.stdout
):
453 xs
[tid
- min_templateid
] = f
'{fields2idx[name]} /* {name} */'
455 print(f
' static const int16_t tid2fidx[] = {{\n {s}\n }};', file=o
)
457 def gen_sizes_table(min_templateid
, n
, st
, dt
, ts
, proto
, o
=sys
.stdout
):
458 is_eobi
= proto
.startswith('eobi')
459 xs
= [ '0' if is_eobi
else '{ 0, 0}' ] * n
460 min_s
= get_min_sizes(st
, dt
)
461 max_s
= get_max_sizes(st
, dt
)
464 xs
[tid
- min_templateid
] = f
'{max_s[name]} /* {name} */'
467 xs
[tid
- min_templateid
] = f
'{{ {min_s[name]}, {max_s[name]} }} /* {name} */'
470 print(f
' static const uint32_t tid2size[] = {{\n {s}\n }};', file=o
)
472 print(f
' static const uint32_t tid2size[{n}][2] = {{\n {s}\n }};', file=o
)
475 # yes, usage attribute of single fields depends on the context
476 # otherwise, we could just put the information into the fields table
477 # Example: EOBI.PacketHeader.MessageHeader.MsgSeqNum is unused whereas
478 # it's required in the EOBI ExecutionSummary and other messages
479 def gen_usage_table(min_templateid
, n
, ts
, ams
, o
=sys
.stdout
):
484 elif x
== 'optional':
489 raise RuntimeError(f
'unknown usage value: {x}')
493 print(' static const unsigned char usages[] = {', file=o
)
495 name
= am
.get("name")
496 tid
= int(am
.get('numericID'))
497 print(f
' // {name}', file=o
)
501 print(f
' //// {e.get("type")}', file=o
)
503 if m
.get('hidden') == 'true' or pad_re
.match(m
.get('name')):
505 k
= ' ' if i
== 0 else ','
506 print(f
' {k} {map_usage(m)} // {m.get("name")}#{i}', file=o
)
508 print(' ///', file=o
)
510 if e
.get('hidden') == 'true' or pad_re
.match(e
.get('name')):
512 k
= ' ' if i
== 0 else ','
513 print(f
' {k} {map_usage(e)} // {e.get("name")}#{i}', file=o
)
516 # NB: the last element is a filler to simplify the out-of-bounds check
517 # (cf. the uidx DISSECTOR_ASSER_CMPUINIT() before the switch statement)
518 # when the ETI_EOF of the message whose usage information comes last
520 print(' , 0 // filler', file=o
)
524 for tid
, uidx
in h
.items():
526 xs
[tid
- min_templateid
] = f
'{uidx} /* {name} */'
528 print(f
' static const int16_t tid2uidx[] = {{\n {s}\n }};', file=o
)
531 def gen_dscp_table(proto
, o
=sys
.stdout
):
532 print(f
''' static int * const dscp_bits[] = {{
533 &hf_{proto}_dscp_exec_summary,
534 &hf_{proto}_dscp_improved,
535 &hf_{proto}_dscp_widened,
540 def mk_int_case(size
, signed
, proto
):
541 signed_str
= 'i' if signed
else ''
542 unsigned_str
= '' if signed
else 'u'
543 fmt_str
= 'i' if signed
else 'u'
550 type_str
= f
'g{unsigned_str}int{size * 8}'
551 no_value_str
= f
'INT{size * 8}_MIN' if signed
else f
'UINT{size * 8}_MAX'
552 pt_size
= '64' if size
== 8 else ''
554 hex_str
= '0x80' + '00' * (size
- 1)
556 hex_str
= '0x' + 'ff' * size
558 fn
= f
'tvb_get_g{unsigned_str}int8'
560 fn
= f
'tvb_get_letoh{signed_str}{size_str}'
563 {type_str} x = {fn}(tvb, off);
564 if (x == {no_value_str}) {{
565 proto_item *e = proto_tree_add_{unsigned_str}int{pt_size}_format_value(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, x, "NO_VALUE ({hex_str})");
567 expert_add_info_format(pinfo, e, &ei_{proto}_missing, "required value is missing");
569 proto_item *e = proto_tree_add_{unsigned_str}int{pt_size}_format_value(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, x, "%" PRI{fmt_str}{size * 8}, x);
570 if (usages[uidx] == 2)
571 expert_add_info_format(pinfo, e, &ei_{proto}_overused, "unused value is set");
578 def gen_dissect_structs(o
=sys
.stdout
):
600 uint8_t counter_off; // offset into counter array
601 // if ETI_COUNTER => storage
602 // if ETI_VAR_STRING or ETI_VAR_STRUCT => load
603 // to get length or repeat count
604 // if ETI_FIXED_POINT: #fractional digits
605 uint16_t size; // or offset into struct_names if ETI_STRUCT/ETI_VAR_STRUCT
606 uint16_t field_handle_idx; // or index into fields array if ETI_STRUCT/ETI_VAR_STRUT
607 uint16_t ett_idx; // index into ett array if ETI_STRUCT/ETI_VAR_STRUCT
608 // or max value if ETI_COUNTER
612 def gen_dissect_fn(st
, dt
, ts
, sh
, ams
, proto
, o
=sys
.stdout
):
613 if proto
.startswith('eti') or proto
.startswith('xti'):
614 bl_fn
= 'tvb_get_letohl'
617 bl_fn
= 'tvb_get_letohs'
619 print(f
'''/* This method dissects fully reassembled messages */
621 dissect_{proto}_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
623 col_set_str(pinfo->cinfo, COL_PROTOCOL, "{proto.upper()}");
624 col_clear(pinfo->cinfo, COL_INFO);
625 uint16_t templateid = tvb_get_letohs(tvb, {template_off});
626 const char *template_str = val_to_str_ext(templateid, &template_id_vals_ext, "Unknown {proto.upper()} template: 0x%04x");
627 col_add_str(pinfo->cinfo, COL_INFO, template_str);
629 /* create display subtree for the protocol */
630 proto_item *ti = proto_tree_add_item(tree, proto_{proto}, tvb, 0, -1, ENC_NA);
631 uint32_t bodylen= {bl_fn}(tvb, 0);
632 proto_item_append_text(ti, ", %s (%" PRIu16 "), BodyLen: %u", template_str, templateid, bodylen);
633 proto_tree *root = proto_item_add_subtree(ti, ett_{proto}[0]);
636 min_templateid
= ts
[0][0]
637 max_templateid
= ts
[-1][0]
638 n
= max_templateid
- min_templateid
+ 1
640 fields2idx
= gen_fields_table(st
, dt
, sh
, o
)
641 gen_template_table(min_templateid
, n
, ts
, fields2idx
, o
)
642 gen_sizes_table(min_templateid
, n
, st
, dt
, ts
, proto
, o
)
643 gen_usage_table(min_templateid
, n
, ts
, ams
, o
)
644 gen_dscp_table(proto
, o
)
646 print(f
''' if (templateid < {min_templateid} || templateid > {max_templateid}) {{
647 proto_tree_add_expert_format(root, pinfo, &ei_{proto}_invalid_template, tvb, {template_off}, 4,
648 "Template ID out of range: %" PRIu16, templateid);
649 return tvb_captured_length(tvb);
651 int fidx = tid2fidx[templateid - {min_templateid}];
653 proto_tree_add_expert_format(root, pinfo, &ei_{proto}_invalid_template, tvb, {template_off}, 4,
654 "Unallocated Template ID: %" PRIu16, templateid);
655 return tvb_captured_length(tvb);
658 if proto
.startswith('eobi'):
659 print(f
''' if (bodylen != tid2size[templateid - {min_templateid}]) {{
660 proto_tree_add_expert_format(root, pinfo, &ei_{proto}_invalid_length, tvb, 0, {template_off},
661 "Unexpected BodyLen value of %" PRIu32 ", expected: %" PRIu32, bodylen, tid2size[templateid - {min_templateid}]);
664 print(f
''' if (bodylen < tid2size[templateid - {min_templateid}][0] || bodylen > tid2size[templateid - {min_templateid}][1]) {{
665 if (tid2size[templateid - {min_templateid}][0] != tid2size[templateid - {min_templateid}][1])
666 proto_tree_add_expert_format(root, pinfo, &ei_{proto}_invalid_length, tvb, 0, {template_off},
667 "Unexpected BodyLen value of %" PRIu32 ", expected: %" PRIu32 "..%" PRIu32, bodylen, tid2size[templateid - {min_templateid}][0], tid2size[templateid - {min_templateid}][1]);
669 proto_tree_add_expert_format(root, pinfo, &ei_{proto}_invalid_length, tvb, 0, {template_off},
670 "Unexpected BodyLen value of %" PRIu32 ", expected: %" PRIu32, bodylen, tid2size[templateid - {min_templateid}][0]);
673 proto_tree_add_expert_format(root, pinfo, &ei_{proto}_unaligned, tvb, 0, {template_off},
674 "BodyLen value of %" PRIu32 " is not divisible by 8", bodylen);
677 print(f
''' int uidx = tid2uidx[templateid - {min_templateid}];
678 DISSECTOR_ASSERT_CMPINT(uidx, >=, 0);
679 DISSECTOR_ASSERT_CMPUINT(((size_t)uidx), <, array_length(usages));
682 print(f
''' int old_fidx = 0;
685 unsigned counter[8] = {{0}};
687 unsigned struct_off = 0;
688 unsigned repeats = 0;
689 proto_tree *t = root;
691 DISSECTOR_ASSERT_CMPINT(fidx, >=, 0);
692 DISSECTOR_ASSERT_CMPUINT(((size_t)fidx), <, array_length(fields));
693 DISSECTOR_ASSERT_CMPINT(uidx, >=, 0);
694 DISSECTOR_ASSERT_CMPUINT(((size_t)uidx), <, array_length(usages));
696 switch (fields[fidx].type) {{
698 DISSECTOR_ASSERT_CMPUINT(top, >=, 1);
699 DISSECTOR_ASSERT_CMPUINT(top, <=, 2);
701 proto_item_set_len(t, off - struct_off);
704 fidx = fields[old_fidx].field_handle_idx;
706 t = proto_tree_add_subtree(root, tvb, off, -1, ett_{proto}[fields[old_fidx].ett_idx], NULL, &struct_names[fields[old_fidx].size]);
716 DISSECTOR_ASSERT_CMPUINT(fields[fidx].counter_off, <, array_length(counter));
717 repeats = fields[fidx].type == ETI_VAR_STRUCT ? counter[fields[fidx].counter_off] : 1;
720 t = proto_tree_add_subtree(root, tvb, off, -1, ett_{proto}[fields[fidx].ett_idx], NULL, &struct_names[fields[fidx].size]);
724 fidx = fields[fidx].field_handle_idx;
725 DISSECTOR_ASSERT_CMPUINT(top, ==, 1);
732 off += fields[fidx].size;
736 proto_tree_add_item(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, ENC_ASCII);
737 off += fields[fidx].size;
743 uint8_t c = tvb_get_uint8(tvb, off);
745 proto_tree_add_item(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, ENC_ASCII);
747 proto_item *e = proto_tree_add_string(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, "NO_VALUE ('0x00...')");
749 expert_add_info_format(pinfo, e, &ei_{proto}_missing, "required value is missing");
752 off += fields[fidx].size;
757 DISSECTOR_ASSERT_CMPUINT(fields[fidx].counter_off, <, array_length(counter));
758 proto_tree_add_item(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, counter[fields[fidx].counter_off], ENC_ASCII);
759 off += counter[fields[fidx].counter_off];
764 DISSECTOR_ASSERT_CMPUINT(fields[fidx].counter_off, <, array_length(counter));
765 DISSECTOR_ASSERT_CMPUINT(fields[fidx].size, <=, 2);
767 switch (fields[fidx].size) {{
770 uint8_t x = tvb_get_uint8(tvb, off);
771 if (x == UINT8_MAX) {{
772 proto_tree_add_uint_format_value(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, x, "NO_VALUE (0xff)");
773 counter[fields[fidx].counter_off] = 0;
775 proto_item *e = proto_tree_add_uint_format_value(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, x, "%" PRIu8, x);
776 if (x > fields[fidx].ett_idx) {{
777 counter[fields[fidx].counter_off] = fields[fidx].ett_idx;
778 expert_add_info_format(pinfo, e, &ei_{proto}_counter_overflow, "Counter overflow: %" PRIu8 " > %" PRIu16, x, fields[fidx].ett_idx);
780 counter[fields[fidx].counter_off] = x;
787 uint16_t x = tvb_get_letohs(tvb, off);
788 if (x == UINT16_MAX) {{
789 proto_tree_add_uint_format_value(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, x, "NO_VALUE (0xffff)");
790 counter[fields[fidx].counter_off] = 0;
792 proto_item *e = proto_tree_add_uint_format_value(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, x, "%" PRIu16, x);
793 if (x > fields[fidx].ett_idx) {{
794 counter[fields[fidx].counter_off] = fields[fidx].ett_idx;
795 expert_add_info_format(pinfo, e, &ei_{proto}_counter_overflow, "Counter overflow: %" PRIu16 " > %" PRIu16, x, fields[fidx].ett_idx);
797 counter[fields[fidx].counter_off] = x;
804 off += fields[fidx].size;
809 switch (fields[fidx].size) {{
810 {mk_int_case(1, False, proto)}
811 {mk_int_case(2, False, proto)}
812 {mk_int_case(4, False, proto)}
813 {mk_int_case(8, False, proto)}
815 off += fields[fidx].size;
820 switch (fields[fidx].size) {{
821 {mk_int_case(1, True, proto)}
822 {mk_int_case(2, True, proto)}
823 {mk_int_case(4, True, proto)}
824 {mk_int_case(8, True, proto)}
826 off += fields[fidx].size;
832 proto_tree_add_item(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, ENC_LITTLE_ENDIAN);
833 off += fields[fidx].size;
837 case ETI_FIXED_POINT:
838 DISSECTOR_ASSERT_CMPUINT(fields[fidx].size, ==, 8);
839 DISSECTOR_ASSERT_CMPUINT(fields[fidx].counter_off, >, 0);
840 DISSECTOR_ASSERT_CMPUINT(fields[fidx].counter_off, <=, 16);
842 int64_t x = tvb_get_letohi64(tvb, off);
843 if (x == INT64_MIN) {{
844 proto_item *e = proto_tree_add_int64_format_value(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, x, "NO_VALUE (0x8000000000000000)");
846 expert_add_info_format(pinfo, e, &ei_{proto}_missing, "required value is missing");
848 unsigned slack = fields[fidx].counter_off + 1;
852 int n = snprintf(s, sizeof s, "%0*" PRIi64, slack, x);
853 DISSECTOR_ASSERT_CMPUINT(n, >, 0);
854 unsigned k = n - fields[fidx].counter_off;
855 proto_tree_add_int64_format_value(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, x, "%.*s.%s", k, s, s + k);
858 off += fields[fidx].size;
862 case ETI_TIMESTAMP_NS:
863 DISSECTOR_ASSERT_CMPUINT(fields[fidx].size, ==, 8);
864 proto_tree_add_item(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, ENC_LITTLE_ENDIAN | ENC_TIME_NSECS);
865 off += fields[fidx].size;
870 DISSECTOR_ASSERT_CMPUINT(fields[fidx].size, ==, 1);
871 proto_tree_add_bitmask(t, tvb, off, hf_{proto}[fields[fidx].field_handle_idx], ett_{proto}_dscp, dscp_bits, ENC_LITTLE_ENDIAN);
872 off += fields[fidx].size;
880 print(''' return tvb_captured_length(tvb);
884 print(f
'''/* determine PDU length of protocol {proto.upper()} */
886 get_{proto}_message_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_)
888 return (unsigned){bl_fn}(tvb, offset);
892 if proto
.startswith('eobi'):
894 dissect_{proto}(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
897 return udp_dissect_pdus(tvb, pinfo, tree, 4, NULL,
898 get_{proto}_message_len, dissect_{proto}_message, data);
903 dissect_{proto}(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
906 tcp_dissect_pdus(tvb, pinfo, tree, true, 4 /* bytes to read for bodylen */,
907 get_{proto}_message_len, dissect_{proto}_message, data);
908 return tvb_captured_length(tvb);
912 def gen_register_fn(st
, dt
, n2enum
, proto
, desc
, o
=sys
.stdout
):
914 proto_register_{proto}(void)
916 gen_field_info(st
, dt
, n2enum
, proto
, o
)
918 print(f
''' static ei_register_info ei[] = {{
920 &ei_{proto}_counter_overflow,
921 {{ "{proto}.counter_overflow", PI_PROTOCOL, PI_WARN, "Counter Overflow", EXPFILL }}
924 &ei_{proto}_invalid_template,
925 {{ "{proto}.invalid_template", PI_PROTOCOL, PI_ERROR, "Invalid Template ID", EXPFILL }}
928 &ei_{proto}_invalid_length,
929 {{ "{proto}.invalid_length", PI_PROTOCOL, PI_ERROR, "Invalid Body Length", EXPFILL }}
931 if not proto
.startswith('eobi'):
933 &ei_{proto}_unaligned,
934 {{ "{proto}.unaligned", PI_PROTOCOL, PI_ERROR, "A Body Length not divisible by 8 leads to unaligned followup messages", EXPFILL }}
938 {{ "{proto}.missing", PI_PROTOCOL, PI_WARN, "A required value is missing", EXPFILL }}
941 &ei_{proto}_overused,
942 {{ "{proto}.overused", PI_PROTOCOL, PI_WARN, "An unused value is set", EXPFILL }}
946 print(f
''' proto_{proto} = proto_register_protocol("{desc}",
947 "{proto.upper()}", "{proto}");''', file=o
)
949 print(f
''' expert_module_t *expert_{proto} = expert_register_protocol(proto_{proto});
950 expert_register_field_array(expert_{proto}, ei, array_length(ei));''', file=o
)
952 print(f
' proto_register_field_array(proto_{proto}, hf, array_length(hf));',
954 gen_subtree_array(st
, proto
, o
)
955 print(' proto_register_subtree_array(ett, array_length(ett));', file=o
)
956 if proto
.startswith('eobi'):
957 print(f
' proto_disable_by_default(proto_{proto});', file=o
)
959 print(f
'\n {proto}_handle = register_dissector("{proto}", dissect_{proto}, proto_{proto});', file=o
)
963 def gen_handoff_fn(proto
, o
=sys
.stdout
):
965 proto_reg_handoff_{proto}(void)
967 // cf. N7 Network Access Guide, e.g.
968 // https://www.xetra.com/xetra-en/technology/t7/system-documentation/release10-0/Release-10.0-2692700?frag=2692724
969 // https://www.xetra.com/resource/blob/2762078/388b727972b5122945eedf0e63c36920/data/N7-Network-Access-Guide-v2.0.59.pdf
972 if proto
.startswith('eti'):
973 print(f
''' // NB: can only be called once for a port/handle pair ...
974 // dissector_add_uint_with_preference("tcp.port", 19006 /* LF PROD */, eti_handle);
976 dissector_add_uint("tcp.port", 19006 /* LF PROD */, {proto}_handle);
977 dissector_add_uint("tcp.port", 19043 /* PS PROD */, {proto}_handle);
978 dissector_add_uint("tcp.port", 19506 /* LF SIMU */, {proto}_handle);
979 dissector_add_uint("tcp.port", 19543 /* PS SIMU */, {proto}_handle);''', file=o
)
980 elif proto
.startswith('xti'):
981 print(f
''' // NB: unfortunately, Cash-ETI shares the same ports as Derivatives-ETI ...
982 // We thus can't really add a well-know port for XTI.
983 // Use Wireshark's `Decode As...` or tshark's `-d tcp.port=19043,xti` feature
984 // to switch from ETI to XTI dissection.
985 dissector_add_uint_with_preference("tcp.port", 19042 /* dummy */, {proto}_handle);''', file=o
)
987 print(f
''' static const int ports[] = {{
988 59000, // Snapshot EUREX US-allowed PROD
989 59001, // Incremental EUREX US-allowed PROD
990 59032, // Snapshot EUREX US-restricted PROD
991 59033, // Incremental EUREX US-restricted PROD
992 59500, // Snapshot EUREX US-allowed SIMU
993 59501, // Incremental EUREX US-allowed SIMU
994 59532, // Snapshot EUREX US-restricted SIMU
995 59533, // Incremental EUREX US-restricted SIMU
997 57000, // Snapshot FX US-allowed PROD
998 57001, // Incremental FX US-allowed PROD
999 57032, // Snapshot FX US-restricted PROD
1000 57033, // Incremental FX US-restricted PROD
1001 57500, // Snapshot FX US-allowed SIMU
1002 57501, // Incremental FX US-allowed SIMU
1003 57532, // Snapshot FX US-restricted SIMU
1004 57533, // Incremental FX US-restricted SIMU
1006 59000, // Snapshot Xetra PROD
1007 59001, // Incremental Xetra PROD
1008 59500, // Snapshot Xetra SIMU
1009 59501, // Incremental Xetra SIMU
1011 56000, // Snapshot Boerse Frankfurt PROD
1012 56001, // Incremental Boerse Frankfurt PROD
1013 56500, // Snapshot Boerse Frankfurt SIMU
1014 56501 // Incremental Boerse Frankfurt SIMU
1016 for (unsigned i = 0; i < array_length(ports); ++i)
1017 dissector_add_uint("udp.port", ports[i], {proto}_handle);''', file=o
)
1022 r
= t
.get('rootType')
1023 return r
in ('int', 'floatDecimal') or (r
== 'String' and t
.get('size') == '1')
1028 r
= t
.get('rootType')
1029 if r
== 'int' or (r
== 'String' and t
.get('size') == '1'):
1030 return t
.find('ValidValue') is not None
1033 def is_fixed_point(t
):
1034 return t
is not None and t
.get('rootType') == 'floatDecimal'
1036 def is_timestamp_ns(t
):
1037 return t
is not None and t
.get('type') == 'UTCTimestamp'
1040 return t
is not None and t
.get('name') == 'DSCP'
1042 pad_re
= re
.compile('Pad[1-9]')
1046 return t
.get('rootType') == 'String' and pad_re
.match(t
.get('name'))
1049 def is_fixed_string(t
):
1051 return t
.get('rootType') in ('String', 'data') and not t
.get('variableSize')
1054 def is_var_string(t
):
1056 return t
.get('rootType') in ('String', 'data') and t
.get('variableSize') is not None
1060 v
= t
.get('minValue')
1061 return v
is not None and not v
.startswith('-')
1064 return t
.get('type') == 'Counter'
1068 return f
'{t.get("size")}x'
1070 n
= int(t
.get('size'))
1081 raise ValueError(f
'unknown int size {n}')
1085 elif is_fixed_string(t
):
1086 return f
'{t.get("size")}s'
1096 n
= int(t
.get('size'))
1100 def is_elementary(t
):
1101 return t
is not None and t
.get('counter') is None
1103 def group_members(e
, dt
):
1107 t
= dt
.get(m
.get('type'))
1108 if is_elementary(t
):
1122 p
= argparse
.ArgumentParser(description
='Generate Wireshark Dissector for ETI/EOBI style protocol specifications')
1123 p
.add_argument('filename', help='protocol description XML file')
1124 p
.add_argument('--proto', default
='eti',
1125 help='short protocol name (default: %(default)s)')
1126 p
.add_argument('--desc', '-d',
1127 default
='Enhanced Trading Interface',
1128 help='protocol description (default: %(default)s)')
1129 p
.add_argument('--output', '-o', default
='-',
1130 help='output filename (default: stdout)')
1131 args
= p
.parse_args()
1136 filename
= args
.filename
1137 d
= ET
.parse(filename
)
1138 o
= sys
.stdout
if args
.output
== '-' else open(args
.output
, 'w')
1141 version
= (d
.getroot().get('version'), d
.getroot().get('subVersion'))
1142 desc
= f
'{args.desc} {version[0]}'
1144 dt
= get_data_types(d
)
1146 used
= get_used_types(st
)
1147 for k
in list(dt
.keys()):
1150 ts
= get_templates(st
)
1151 ams
= d
.getroot().find('ApplicationMessages')
1153 gen_header(proto
, desc
, o
)
1154 gen_field_handles(st
, dt
, proto
, o
)
1155 n2enum
= gen_enums(dt
, ts
, o
)
1156 gen_dissect_structs(o
)
1157 sh
= gen_subtree_handles(st
, proto
, o
)
1158 gen_dissect_fn(st
, dt
, ts
, sh
, ams
, proto
, o
)
1159 gen_register_fn(st
, dt
, n2enum
, proto
, desc
, o
)
1160 gen_handoff_fn(proto
, o
)
1163 if __name__
== '__main__':