summaryrefslogtreecommitdiffstats
path: root/tools/eti2wireshark.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:34:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:34:10 +0000
commite4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc (patch)
tree68cb5ef9081156392f1dd62a00c6ccc1451b93df /tools/eti2wireshark.py
parentInitial commit. (diff)
downloadwireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.tar.xz
wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.zip
Adding upstream version 4.2.2.upstream/4.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/eti2wireshark.py')
-rwxr-xr-xtools/eti2wireshark.py1166
1 files changed, 1166 insertions, 0 deletions
diff --git a/tools/eti2wireshark.py b/tools/eti2wireshark.py
new file mode 100755
index 0000000..98fb291
--- /dev/null
+++ b/tools/eti2wireshark.py
@@ -0,0 +1,1166 @@
+#!/usr/bin/env python3
+
+# Generate Wireshark Dissectors for eletronic trading/market data
+# protocols such as ETI/EOBI.
+#
+# Targets Wireshark 3.5 or later.
+#
+# SPDX-FileCopyrightText: © 2021 Georg Sauthoff <mail@gms.tf>
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+
+import argparse
+import itertools
+import re
+import sys
+import xml.etree.ElementTree as ET
+
+
+# inlined from upstream's etimodel.py
+
+import itertools
+
+def get_max_sizes(st, dt):
+ h = {}
+ for name, e in dt.items():
+ v = e.get('size', '0')
+ h[name] = int(v)
+ for name, e in itertools.chain((i for i in st.items() if i[1].get('type') != 'Message'),
+ (i for i in st.items() if i[1].get('type') == 'Message')):
+ s = 0
+ for m in e:
+ x = h.get(m.get('type'), 0)
+ s += x * int(m.get('cardinality'))
+ h[name] = s
+ return h
+
+def get_min_sizes(st, dt):
+ h = {}
+ for name, e in dt.items():
+ v = e.get('size', '0')
+ if e.get('variableSize') is None:
+ h[name] = int(v)
+ else:
+ h[name] = 0
+ for name, e in itertools.chain((i for i in st.items() if i[1].get('type') != 'Message'),
+ (i for i in st.items() if i[1].get('type') == 'Message')):
+ s = 0
+ for m in e:
+ x = h.get(m.get('type'), 0)
+ s += x * int(m.get('minCardinality', '1'))
+ h[name] = s
+ return h
+
+# end # inlined from upstream's etimodel.py
+
+
+def get_used_types(st):
+ xs = set(y.get('type') for _, x in st.items() for y in x)
+ return xs
+
+def get_data_types(d):
+ r = d.getroot()
+ x = r.find('DataTypes')
+ h = {}
+ for e in x:
+ h[e.get('name')] = e
+ return h
+
+def get_structs(d):
+ r = d.getroot()
+ x = r.find('Structures')
+ h = {}
+ for e in x:
+ h[e.get('name')] = e
+ return h
+
+def get_templates(st):
+ ts = []
+ for k, v in st.items():
+ if v.get('type') == 'Message':
+ ts.append((int(v.get('numericID')), k))
+ ts.sort()
+ return ts
+
+
+def gen_header(proto, desc, o=sys.stdout):
+ if proto.startswith('eti') or proto.startswith('xti'):
+ ph = '#include "packet-tcp.h" // tcp_dissect_pdus()'
+ else:
+ ph = '#include "packet-udp.h" // udp_dissect_pdus()'
+ print(f'''// auto-generated by Georg Sauthoff's eti2wireshark.py
+
+/* packet-eti.c
+ * Routines for {proto.upper()} dissection
+ * Copyright 2021, Georg Sauthoff <mail@gms.tf>
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * The {desc} ({proto.upper()}) is an electronic trading protocol
+ * that is used by a few exchanges (Eurex, Xetra, ...).
+ *
+ * It's a Length-Tag based protocol consisting of mostly fix sized
+ * request/response messages.
+ *
+ * Links:
+ * https://en.wikipedia.org/wiki/List_of_electronic_trading_protocols#Europe
+ * https://github.com/gsauthof/python-eti#protocol-descriptions
+ * https://github.com/gsauthof/python-eti#protocol-introduction
+ *
+ */
+
+#include <config.h>
+
+
+#include <epan/packet.h> // Should be first Wireshark include (other than config.h)
+{ph}
+#include <epan/expert.h> // expert info
+
+#include <inttypes.h>
+#include <stdio.h> // snprintf()
+
+
+/* Prototypes */
+/* (Required to prevent [-Wmissing-prototypes] warnings */
+void proto_reg_handoff_{proto}(void);
+void proto_register_{proto}(void);
+''', file=o)
+
+
+def name2ident(name):
+ ll = True
+ xs = []
+ for i, c in enumerate(name):
+ if c.isupper():
+ if i > 0 and ll:
+ xs.append('_')
+ xs.append(c.lower())
+ ll = False
+ else:
+ xs.append(c)
+ ll = True
+ return ''.join(xs)
+
+def gen_enums(dt, ts, o=sys.stdout):
+ print('static const value_string template_id_vals[] = { // TemplateID', file=o)
+ min_tid, max_tid = ts[0][0], ts[-1][0]
+ xs = [None] * (max_tid - min_tid + 1)
+ for tid, name in ts:
+ xs[tid-min_tid] = name
+ for i, name in enumerate(xs):
+ if name is None:
+ print(f' {{ {min_tid + i}, "Unknown" }},', file=o)
+ else:
+ print(f' {{ {min_tid + i}, "{name}" }},', file=o)
+ print(''' { 0, NULL }
+};
+static value_string_ext template_id_vals_ext = VALUE_STRING_EXT_INIT(template_id_vals);''', file=o)
+ name2access = { 'TemplateID': '&template_id_vals_ext' }
+
+ dedup = {}
+ for name, e in dt.items():
+ vs = [ (x.get('value'), x.get('name')) for x in e.findall('ValidValue') ]
+ if not vs:
+ continue
+ if e.get('rootType') == 'String' and e.get('size') != '1':
+ continue
+
+ ident = name2ident(name)
+
+ nv = e.get('noValue')
+ ws = [ v[0] for v in vs ]
+ if nv not in ws:
+ if nv.startswith('0x0') and e.get('rootType') == 'String':
+ nv = '\0'
+ vs.append( (nv, 'NO_VALUE') )
+
+ if e.get('type') == 'int':
+ vs.sort(key = lambda x : int(x[0], 0))
+ else:
+ vs.sort(key = lambda x : ord(x[0]))
+ s = '-'.join(f'{v[0]}:{v[1]}' for v in vs)
+ x = dedup.get(s)
+ if x is None:
+ dedup[s] = name
+ else:
+ name2access[name] = name2access[x]
+ print(f'// {name} aliased by {x}', file=o)
+ continue
+
+ print(f'static const value_string {ident}_vals[] = {{ // {name}', file=o)
+ for i, v in enumerate(vs):
+ if e.get('rootType') == 'String':
+ k = f"'{v[0]}'" if ord(v[0]) != 0 else '0'
+ print(f''' {{ {k}, "{v[1]}" }},''', file=o)
+ else:
+ print(f' {{ {v[0]}, "{v[1]}" }},', file=o)
+ print(''' { 0, NULL }
+};''', file=o)
+
+ if len(vs) > 7:
+ print(f'static value_string_ext {ident}_vals_ext = VALUE_STRING_EXT_INIT({ident}_vals);', file=o)
+ name2access[name] = f'&{ident}_vals_ext'
+ else:
+ name2access[name] = f'VALS({ident}_vals)'
+
+ return name2access
+
+
+def get_fields(st, dt):
+ seen = {}
+ for name, e in st.items():
+ for m in e:
+ t = dt.get(m.get('type'))
+ if is_padding(t):
+ continue
+ if not (is_int(t) or is_fixed_string(t) or is_var_string(t)):
+ continue
+ name = m.get('name')
+ if name in seen:
+ if seen[name] != t:
+ raise RuntimeError(f'Mismatching type for: {name}')
+ else:
+ seen[name] = t
+ vs = list(seen.items())
+ vs.sort()
+ return vs
+
+def gen_field_handles(st, dt, proto, o=sys.stdout):
+ print(f'''static expert_field ei_{proto}_counter_overflow = EI_INIT;
+static expert_field ei_{proto}_invalid_template = EI_INIT;
+static expert_field ei_{proto}_invalid_length = EI_INIT;''', file=o)
+ if not proto.startswith('eobi'):
+ print(f'static expert_field ei_{proto}_unaligned = EI_INIT;', file=o)
+ print(f'''static expert_field ei_{proto}_missing = EI_INIT;
+static expert_field ei_{proto}_overused = EI_INIT;
+''', file=o)
+
+ vs = get_fields(st, dt)
+ s = ', '.join('-1' for i in range(len(vs)))
+ print(f'static int hf_{proto}[] = {{ {s} }};', file=o)
+ print(f'''static int hf_{proto}_dscp_exec_summary = -1;
+static int hf_{proto}_dscp_improved = -1;
+static int hf_{proto}_dscp_widened = -1;''', file=o)
+ print('enum Field_Handle_Index {', file=o)
+ for i, (name, _) in enumerate(vs):
+ c = ' ' if i == 0 else ','
+ print(f' {c} {name.upper()}_FH_IDX', file=o)
+ print('};', file=o)
+
+def type2ft(t):
+ if is_timestamp_ns(t):
+ return 'FT_ABSOLUTE_TIME'
+ if is_dscp(t):
+ return 'FT_UINT8'
+ if is_int(t):
+ if t.get('rootType') == 'String':
+ return 'FT_CHAR'
+ u = 'U' if is_unsigned(t) else ''
+ if t.get('size') is None:
+ raise RuntimeError(f'None size: {t.get("name")}')
+ size = int(t.get('size')) * 8
+ return f'FT_{u}INT{size}'
+ if is_fixed_string(t) or is_var_string(t):
+ # NB: technically, ETI fixed-strings are blank-padded,
+ # unless they are marked NO_VALUE, in that case
+ # the first byte is zero, followed by unspecified content.
+ # Also, some fixed-strings are zero-terminated, where again
+ # the bytes following the terminator are unspecified.
+ return 'FT_STRINGZTRUNC'
+ raise RuntimeError('unexpected type')
+
+def type2enc(t):
+ if is_timestamp_ns(t):
+ return 'ABSOLUTE_TIME_UTC'
+ if is_dscp(t):
+ return 'BASE_HEX'
+ if is_int(t):
+ if t.get('rootType') == 'String':
+ # NB: basically only used when enum and value is unknown
+ return 'BASE_HEX'
+ else:
+ return 'BASE_DEC'
+ if is_fixed_string(t) or is_var_string(t):
+ # previously 'STR_ASCII', which was removed upstream
+ # cf. 19dcb725b61e384f665ad4b955f3b78f63e626d9
+ return 'BASE_NONE'
+ raise RuntimeError('unexpected type')
+
+def gen_field_info(st, dt, n2enum, proto='eti', o=sys.stdout):
+ print(' static hf_register_info hf[] ={', file=o)
+ vs = get_fields(st, dt)
+ for i, (name, t) in enumerate(vs):
+ c = ' ' if i == 0 else ','
+ ft = type2ft(t)
+ enc = type2enc(t)
+ if is_enum(t) and not is_dscp(t):
+ vals = n2enum[t.get('name')]
+ if vals.startswith('&'):
+ extra_enc = '| BASE_EXT_STRING'
+ else:
+ extra_enc = ''
+ else:
+ vals = 'NULL'
+ extra_enc = ''
+ print(f''' {c} {{ &hf_{proto}[{name.upper()}_FH_IDX],
+ {{ "{name}", "{proto}.{name.lower()}",
+ {ft}, {enc}{extra_enc}, {vals}, 0x0,
+ NULL, HFILL }}
+ }}''', file=o)
+ print(f''' , {{ &hf_{proto}_dscp_exec_summary,
+ {{ "DSCP_ExecSummary", "{proto}.dscp_execsummary",
+ FT_BOOLEAN, 8, NULL, 0x10,
+ NULL, HFILL }}
+ }}
+ , {{ &hf_{proto}_dscp_improved,
+ {{ "DSCP_Improved", "{proto}.dscp_improved",
+ FT_BOOLEAN, 8, NULL, 0x20,
+ NULL, HFILL }}
+ }}
+ , {{ &hf_{proto}_dscp_widened,
+ {{ "DSCP_Widened", "{proto}.dscp_widened",
+ FT_BOOLEAN, 8, NULL, 0x40,
+ NULL, HFILL }}
+ }}''', file=o)
+ print(' };', file=o)
+
+
+def gen_subtree_handles(st, proto='eti', o=sys.stdout):
+ ns = [ name for name, e in st.items() if e.get('type') != 'Message' ]
+ ns.sort()
+ s = ', '.join('-1' for i in range(len(ns) + 1))
+ h = dict( (n, i) for i, n in enumerate(ns, 1) )
+ print(f'static gint ett_{proto}[] = {{ {s} }};', file=o)
+ print(f'static gint ett_{proto}_dscp = -1;', file=o)
+ return h
+
+
+def gen_subtree_array(st, proto='eti', o=sys.stdout):
+ n = sum(1 for name, e in st.items() if e.get('type') != 'Message')
+ n += 1
+ s = ', '.join(f'&ett_{proto}[{i}]' for i in range(n))
+ print(f' static gint * const ett[] = {{ {s}, &ett_{proto}_dscp }};', file=o)
+
+
+def gen_fields_table(st, dt, sh, o=sys.stdout):
+ name2off = {}
+ off = 0
+ names = []
+ for name, e in st.items():
+ if e.get('type') == 'Message':
+ continue
+ if name.endswith('Comp'):
+ s = name[:-4]
+ name2off[name] = off
+ off += len(s) + 1
+ names.append(s)
+ s = '\\0'.join(names)
+ print(f' static const char struct_names[] = "{s}";', file=o)
+
+ xs = [ x for x in st.items() if x[1].get('type') != 'Message' ]
+ xs += [ x for x in st.items() if x[1].get('type') == 'Message' ]
+ print(' static const struct ETI_Field fields[] = {', file=o)
+ i = 0
+ fields2idx = {}
+ for name, e in xs:
+ fields2idx[name] = i
+ print(f' // {name}@{i}', file=o)
+ counters = {}
+ cnt = 0
+ for m in e:
+ t = dt.get(m.get('type'))
+ c = ' ' if i == 0 else ','
+ typ = ''
+ size = int(t.get('size')) if t is not None else 0
+ rep = ''
+ fh = f'{m.get("name").upper()}_FH_IDX'
+ sub = ''
+ if is_padding(t):
+ print(f' {c} {{ ETI_PADDING, 0, {size}, 0, 0 }}', file=o)
+ elif is_fixed_point(t):
+ if size != 8:
+ raise RuntimeError('only supporting 8 byte fixed point')
+ fraction = int(t.get('precision'))
+ if fraction > 16:
+ raise RuntimeError('unusual high precisio in fixed point')
+ print(f' {c} {{ ETI_FIXED_POINT, {fraction}, {size}, {fh}, 0 }}', file=o)
+ elif is_timestamp_ns(t):
+ if size != 8:
+ raise RuntimeError('only supporting timestamps')
+ print(f' {c} {{ ETI_TIMESTAMP_NS, 0, {size}, {fh}, 0 }}', file=o)
+ elif is_dscp(t):
+ print(f' {c} {{ ETI_DSCP, 0, {size}, {fh}, 0 }}', file=o)
+ elif is_int(t):
+ u = 'U' if is_unsigned(t) else ''
+ if t.get('rootType') == 'String':
+ typ = 'ETI_CHAR'
+ else:
+ typ = f'ETI_{u}INT'
+ if is_enum(t):
+ typ += '_ENUM'
+ if t.get('type') == 'Counter':
+ counters[m.get('name')] = cnt
+ suf = f' // <- counter@{cnt}'
+ if cnt > 7:
+ raise RuntimeError(f'too many counters in message: {name}')
+ rep = cnt
+ cnt += 1
+ if typ != 'ETI_UINT':
+ raise RuntimeError('only unsigned counters supported')
+ if size > 2:
+ raise RuntimeError('only smaller counters supported')
+ typ = 'ETI_COUNTER'
+ ett_idx = t.get('maxValue')
+ else:
+ rep = 0
+ suf = ''
+ ett_idx = 0
+ print(f' {c} {{ {typ}, {rep}, {size}, {fh}, {ett_idx} }}{suf}', file=o)
+ elif is_fixed_string(t):
+ print(f' {c} {{ ETI_STRING, 0, {size}, {fh}, 0 }}', file=o)
+ elif is_var_string(t):
+ k = m.get('counter')
+ x = counters[k]
+ print(f' {c} {{ ETI_VAR_STRING, {x}, {size}, {fh}, 0 }}', file=o)
+ else:
+ a = m.get('type')
+ fields_idx = fields2idx[a]
+ k = m.get('counter')
+ if k:
+ counter_off = counters[k]
+ typ = 'ETI_VAR_STRUCT'
+ else:
+ counter_off = 0
+ typ = 'ETI_STRUCT'
+ names_off = name2off[m.get('type')]
+ ett_idx = sh[a]
+ print(f' {c} {{ {typ}, {counter_off}, {names_off}, {fields_idx}, {ett_idx} }} // {m.get("name")}', file=o)
+ i += 1
+ print(' , { ETI_EOF, 0, 0, 0, 0 }', file=o)
+ i += 1
+ print(' };', file=o)
+ return fields2idx
+
+def gen_template_table(min_templateid, n, ts, fields2idx, o=sys.stdout):
+ xs = [ '-1' ] * n
+ for tid, name in ts:
+ xs[tid - min_templateid] = f'{fields2idx[name]} /* {name} */'
+ s = '\n , '.join(xs)
+ print(f' static const int16_t tid2fidx[] = {{\n {s}\n }};', file=o)
+
+def gen_sizes_table(min_templateid, n, st, dt, ts, proto, o=sys.stdout):
+ is_eobi = proto.startswith('eobi')
+ xs = [ '0' if is_eobi else '{ 0, 0}' ] * n
+ min_s = get_min_sizes(st, dt)
+ max_s = get_max_sizes(st, dt)
+ if is_eobi:
+ for tid, name in ts:
+ xs[tid - min_templateid] = f'{max_s[name]} /* {name} */'
+ else:
+ for tid, name in ts:
+ xs[tid - min_templateid] = f'{{ {min_s[name]}, {max_s[name]} }} /* {name} */'
+ s = '\n , '.join(xs)
+ if is_eobi:
+ print(f' static const uint32_t tid2size[] = {{\n {s}\n }};', file=o)
+ else:
+ print(f' static const uint32_t tid2size[{n}][2] = {{\n {s}\n }};', file=o)
+
+
+# yes, usage attribute of single fields depends on the context
+# otherwise, we could just put the information into the fields table
+# Example: EOBI.PacketHeader.MessageHeader.MsgSeqNum is unused whereas
+# it's required in the EOBI ExecutionSummary and other messages
+def gen_usage_table(min_templateid, n, ts, ams, o=sys.stdout):
+ def map_usage(m):
+ x = m.get('usage')
+ if x == 'mandatory':
+ return 0
+ elif x == 'optional':
+ return 1
+ elif x == 'unused':
+ return 2
+ else:
+ raise RuntimeError(f'unknown usage value: {x}')
+
+ h = {}
+ i = 0
+ print(' static const unsigned char usages[] = {', file=o)
+ for am in ams:
+ name = am.get("name")
+ tid = int(am.get('numericID'))
+ print(f' // {name}', file=o)
+ h[tid] = i
+ for e in am:
+ if e.tag == 'Group':
+ print(f' //// {e.get("type")}', file=o)
+ for m in e:
+ if m.get('hidden') == 'true' or pad_re.match(m.get('name')):
+ continue
+ k = ' ' if i == 0 else ','
+ print(f' {k} {map_usage(m)} // {m.get("name")}#{i}', file=o)
+ i += 1
+ print(' ///', file=o)
+ else:
+ if e.get('hidden') == 'true' or pad_re.match(e.get('name')):
+ continue
+ k = ' ' if i == 0 else ','
+ print(f' {k} {map_usage(e)} // {e.get("name")}#{i}', file=o)
+ i += 1
+
+ # NB: the last element is a filler to simplify the out-of-bounds check
+ # (cf. the uidx DISSECTOR_ASSER_CMPUINIT() before the switch statement)
+ # when the ETI_EOF of the message whose usage information comes last
+ # is reached
+ print(f' , 0 // filler', file=o)
+ print(' };', file=o)
+ xs = [ '-1' ] * n
+ t2n = dict(ts)
+ for tid, uidx in h.items():
+ name = t2n[tid]
+ xs[tid - min_templateid] = f'{uidx} /* {name} */'
+ s = '\n , '.join(xs)
+ print(f' static const int16_t tid2uidx[] = {{\n {s}\n }};', file=o)
+
+
+def gen_dscp_table(proto, o=sys.stdout):
+ print(f''' static int * const dscp_bits[] = {{
+ &hf_{proto}_dscp_exec_summary,
+ &hf_{proto}_dscp_improved,
+ &hf_{proto}_dscp_widened,
+ NULL
+ }};''', file=o)
+
+
+def mk_int_case(size, signed, proto):
+ signed_str = 'i' if signed else ''
+ unsigned_str = '' if signed else 'u'
+ fmt_str = 'i' if signed else 'u'
+ if size == 2:
+ size_str = 's'
+ elif size == 4:
+ size_str = 'l'
+ elif size == 8:
+ size_str = '64'
+ type_str = f'g{unsigned_str}int{size * 8}'
+ no_value_str = f'INT{size * 8}_MIN' if signed else f'UINT{size * 8}_MAX'
+ pt_size = '64' if size == 8 else ''
+ if signed:
+ hex_str = '0x80' + '00' * (size - 1)
+ else:
+ hex_str = '0x' + 'ff' * size
+ if size == 1:
+ fn = f'tvb_get_g{unsigned_str}int8'
+ else:
+ fn = f'tvb_get_letoh{signed_str}{size_str}'
+ s = f'''case {size}:
+ {{
+ {type_str} x = {fn}(tvb, off);
+ if (x == {no_value_str}) {{
+ 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})");
+ if (!usages[uidx])
+ expert_add_info_format(pinfo, e, &ei_{proto}_missing, "required value is missing");
+ }} else {{
+ 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);
+ if (usages[uidx] == 2)
+ expert_add_info_format(pinfo, e, &ei_{proto}_overused, "unused value is set");
+ }}
+ }}
+ break;'''
+ return s
+
+
+def gen_dissect_structs(o=sys.stdout):
+ print('''
+enum ETI_Type {
+ ETI_EOF,
+ ETI_PADDING,
+ ETI_UINT,
+ ETI_INT,
+ ETI_UINT_ENUM,
+ ETI_INT_ENUM,
+ ETI_COUNTER,
+ ETI_FIXED_POINT,
+ ETI_TIMESTAMP_NS,
+ ETI_CHAR,
+ ETI_STRING,
+ ETI_VAR_STRING,
+ ETI_STRUCT,
+ ETI_VAR_STRUCT,
+ ETI_DSCP
+};
+
+struct ETI_Field {
+ uint8_t type;
+ uint8_t counter_off; // offset into counter array
+ // if ETI_COUNTER => storage
+ // if ETI_VAR_STRING or ETI_VAR_STRUCT => load
+ // to get length or repeat count
+ // if ETI_FIXED_POINT: #fractional digits
+ uint16_t size; // or offset into struct_names if ETI_STRUCT/ETI_VAR_STRUCT
+ uint16_t field_handle_idx; // or index into fields array if ETI_STRUCT/ETI_VAR_STRUT
+ uint16_t ett_idx; // index into ett array if ETI_STRUCT/ETI_VAR_STRUCT
+ // or max value if ETI_COUNTER
+};
+''', file=o)
+
+def gen_dissect_fn(st, dt, ts, sh, ams, proto, o=sys.stdout):
+ if proto.startswith('eti') or proto.startswith('xti'):
+ bl_fn = 'tvb_get_letohl'
+ template_off = 4
+ else:
+ bl_fn = 'tvb_get_letohs'
+ template_off = 2
+ print(f'''/* This method dissects fully reassembled messages */
+static int
+dissect_{proto}_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
+{{
+ col_set_str(pinfo->cinfo, COL_PROTOCOL, "{proto.upper()}");
+ col_clear(pinfo->cinfo, COL_INFO);
+ guint16 templateid = tvb_get_letohs(tvb, {template_off});
+ const char *template_str = val_to_str_ext(templateid, &template_id_vals_ext, "Unknown {proto.upper()} template: 0x%04x");
+ col_add_fstr(pinfo->cinfo, COL_INFO, "%s", template_str);
+
+ /* create display subtree for the protocol */
+ proto_item *ti = proto_tree_add_item(tree, proto_{proto}, tvb, 0, -1, ENC_NA);
+ guint32 bodylen= {bl_fn}(tvb, 0);
+ proto_item_append_text(ti, ", %s (%" PRIu16 "), BodyLen: %u", template_str, templateid, bodylen);
+ proto_tree *root = proto_item_add_subtree(ti, ett_{proto}[0]);
+''', file=o)
+
+ min_templateid = ts[0][0]
+ max_templateid = ts[-1][0]
+ n = max_templateid - min_templateid + 1
+
+ fields2idx = gen_fields_table(st, dt, sh, o)
+ gen_template_table(min_templateid, n, ts, fields2idx, o)
+ gen_sizes_table(min_templateid, n, st, dt, ts, proto, o)
+ gen_usage_table(min_templateid, n, ts, ams, o)
+ gen_dscp_table(proto, o)
+
+ print(f''' if (templateid < {min_templateid} || templateid > {max_templateid}) {{
+ proto_tree_add_expert_format(root, pinfo, &ei_{proto}_invalid_template, tvb, {template_off}, 4,
+ "Template ID out of range: %" PRIu16, templateid);
+ return tvb_captured_length(tvb);
+ }}
+ int fidx = tid2fidx[templateid - {min_templateid}];
+ if (fidx == -1) {{
+ proto_tree_add_expert_format(root, pinfo, &ei_{proto}_invalid_template, tvb, {template_off}, 4,
+ "Unallocated Template ID: %" PRIu16, templateid);
+ return tvb_captured_length(tvb);
+ }}''', file=o)
+
+ if proto.startswith('eobi'):
+ print(f''' if (bodylen != tid2size[templateid - {min_templateid}]) {{
+ proto_tree_add_expert_format(root, pinfo, &ei_{proto}_invalid_length, tvb, 0, {template_off},
+ "Unexpected BodyLen value of %" PRIu32 ", expected: %" PRIu32, bodylen, tid2size[templateid - {min_templateid}]);
+ }}''', file=o)
+ else:
+ print(f''' if (bodylen < tid2size[templateid - {min_templateid}][0] || bodylen > tid2size[templateid - {min_templateid}][1]) {{
+ if (tid2size[templateid - {min_templateid}][0] != tid2size[templateid - {min_templateid}][1])
+ proto_tree_add_expert_format(root, pinfo, &ei_{proto}_invalid_length, tvb, 0, {template_off},
+ "Unexpected BodyLen value of %" PRIu32 ", expected: %" PRIu32 "..%" PRIu32, bodylen, tid2size[templateid - {min_templateid}][0], tid2size[templateid - {min_templateid}][1]);
+ else
+ proto_tree_add_expert_format(root, pinfo, &ei_{proto}_invalid_length, tvb, 0, {template_off},
+ "Unexpected BodyLen value of %" PRIu32 ", expected: %" PRIu32, bodylen, tid2size[templateid - {min_templateid}][0]);
+ }}
+ if (bodylen % 8)
+ proto_tree_add_expert_format(root, pinfo, &ei_{proto}_unaligned, tvb, 0, {template_off},
+ "BodyLen value of %" PRIu32 " is not divisible by 8", bodylen);
+''', file=o)
+
+ print(f''' int uidx = tid2uidx[templateid - {min_templateid}];
+ DISSECTOR_ASSERT_CMPINT(uidx, >=, 0);
+ DISSECTOR_ASSERT_CMPUINT(((size_t)uidx), <, (sizeof usages / sizeof usages[0]));
+''', file=o)
+
+ print(f''' int old_fidx = 0;
+ int old_uidx = 0;
+ unsigned top = 1;
+ unsigned counter[8] = {{0}};
+ unsigned off = 0;
+ unsigned struct_off = 0;
+ unsigned repeats = 0;
+ proto_tree *t = root;
+ while (top) {{
+ DISSECTOR_ASSERT_CMPINT(fidx, >=, 0);
+ DISSECTOR_ASSERT_CMPUINT(((size_t)fidx), <, (sizeof fields / sizeof fields[0]));
+ DISSECTOR_ASSERT_CMPINT(uidx, >=, 0);
+ DISSECTOR_ASSERT_CMPUINT(((size_t)uidx), <, (sizeof usages / sizeof usages[0]));
+
+ switch (fields[fidx].type) {{
+ case ETI_EOF:
+ DISSECTOR_ASSERT_CMPUINT(top, >=, 1);
+ DISSECTOR_ASSERT_CMPUINT(top, <=, 2);
+ if (t != root)
+ proto_item_set_len(t, off - struct_off);
+ if (repeats) {{
+ --repeats;
+ fidx = fields[old_fidx].field_handle_idx;
+ uidx = old_uidx;
+ t = proto_tree_add_subtree(root, tvb, off, -1, ett_{proto}[fields[old_fidx].ett_idx], NULL, &struct_names[fields[old_fidx].size]);
+ struct_off = off;
+ }} else {{
+ fidx = old_fidx + 1;
+ t = root;
+ --top;
+ }}
+ break;
+ case ETI_VAR_STRUCT:
+ case ETI_STRUCT:
+ DISSECTOR_ASSERT_CMPUINT(fields[fidx].counter_off, <, sizeof counter / sizeof counter[0]);
+ repeats = fields[fidx].type == ETI_VAR_STRUCT ? counter[fields[fidx].counter_off] : 1;
+ if (repeats) {{
+ --repeats;
+ t = proto_tree_add_subtree(root, tvb, off, -1, ett_{proto}[fields[fidx].ett_idx], NULL, &struct_names[fields[fidx].size]);
+ struct_off = off;
+ old_fidx = fidx;
+ old_uidx = uidx;
+ fidx = fields[fidx].field_handle_idx;
+ DISSECTOR_ASSERT_CMPUINT(top, ==, 1);
+ ++top;
+ }} else {{
+ ++fidx;
+ }}
+ break;
+ case ETI_PADDING:
+ off += fields[fidx].size;
+ ++fidx;
+ break;
+ case ETI_CHAR:
+ proto_tree_add_item(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, ENC_ASCII);
+ off += fields[fidx].size;
+ ++fidx;
+ ++uidx;
+ break;
+ case ETI_STRING:
+ {{
+ guint8 c = tvb_get_guint8(tvb, off);
+ if (c)
+ proto_tree_add_item(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, ENC_ASCII);
+ else {{
+ proto_item *e = proto_tree_add_string(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, "NO_VALUE ('0x00...')");
+ if (!usages[uidx])
+ expert_add_info_format(pinfo, e, &ei_{proto}_missing, "required value is missing");
+ }}
+ }}
+ off += fields[fidx].size;
+ ++fidx;
+ ++uidx;
+ break;
+ case ETI_VAR_STRING:
+ DISSECTOR_ASSERT_CMPUINT(fields[fidx].counter_off, <, sizeof counter / sizeof counter[0]);
+ proto_tree_add_item(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, counter[fields[fidx].counter_off], ENC_ASCII);
+ off += counter[fields[fidx].counter_off];
+ ++fidx;
+ ++uidx;
+ break;
+ case ETI_COUNTER:
+ DISSECTOR_ASSERT_CMPUINT(fields[fidx].counter_off, <, sizeof counter / sizeof counter[0]);
+ DISSECTOR_ASSERT_CMPUINT(fields[fidx].size, <=, 2);
+ {{
+ switch (fields[fidx].size) {{
+ case 1:
+ {{
+ guint8 x = tvb_get_guint8(tvb, off);
+ if (x == UINT8_MAX) {{
+ proto_tree_add_uint_format_value(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, x, "NO_VALUE (0xff)");
+ counter[fields[fidx].counter_off] = 0;
+ }} else {{
+ 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);
+ if (x > fields[fidx].ett_idx) {{
+ counter[fields[fidx].counter_off] = fields[fidx].ett_idx;
+ expert_add_info_format(pinfo, e, &ei_{proto}_counter_overflow, "Counter overflow: %" PRIu8 " > %" PRIu16, x, fields[fidx].ett_idx);
+ }} else {{
+ counter[fields[fidx].counter_off] = x;
+ }}
+ }}
+ }}
+ break;
+ case 2:
+ {{
+ guint16 x = tvb_get_letohs(tvb, off);
+ if (x == UINT16_MAX) {{
+ proto_tree_add_uint_format_value(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, x, "NO_VALUE (0xffff)");
+ counter[fields[fidx].counter_off] = 0;
+ }} else {{
+ 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);
+ if (x > fields[fidx].ett_idx) {{
+ counter[fields[fidx].counter_off] = fields[fidx].ett_idx;
+ expert_add_info_format(pinfo, e, &ei_{proto}_counter_overflow, "Counter overflow: %" PRIu16 " > %" PRIu16, x, fields[fidx].ett_idx);
+ }} else {{
+ counter[fields[fidx].counter_off] = x;
+ }}
+ }}
+ }}
+ break;
+ }}
+ }}
+ off += fields[fidx].size;
+ ++fidx;
+ ++uidx;
+ break;
+ case ETI_UINT:
+ switch (fields[fidx].size) {{
+ {mk_int_case(1, False, proto)}
+ {mk_int_case(2, False, proto)}
+ {mk_int_case(4, False, proto)}
+ {mk_int_case(8, False, proto)}
+ }}
+ off += fields[fidx].size;
+ ++fidx;
+ ++uidx;
+ break;
+ case ETI_INT:
+ switch (fields[fidx].size) {{
+ {mk_int_case(1, True, proto)}
+ {mk_int_case(2, True, proto)}
+ {mk_int_case(4, True, proto)}
+ {mk_int_case(8, True, proto)}
+ }}
+ off += fields[fidx].size;
+ ++fidx;
+ ++uidx;
+ break;
+ case ETI_UINT_ENUM:
+ case ETI_INT_ENUM:
+ proto_tree_add_item(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, ENC_LITTLE_ENDIAN);
+ off += fields[fidx].size;
+ ++fidx;
+ ++uidx;
+ break;
+ case ETI_FIXED_POINT:
+ DISSECTOR_ASSERT_CMPUINT(fields[fidx].size, ==, 8);
+ DISSECTOR_ASSERT_CMPUINT(fields[fidx].counter_off, >, 0);
+ DISSECTOR_ASSERT_CMPUINT(fields[fidx].counter_off, <=, 16);
+ {{
+ gint64 x = tvb_get_letohi64(tvb, off);
+ if (x == INT64_MIN) {{
+ 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)");
+ if (!usages[uidx])
+ expert_add_info_format(pinfo, e, &ei_{proto}_missing, "required value is missing");
+ }} else {{
+ unsigned slack = fields[fidx].counter_off + 1;
+ if (x < 0)
+ slack += 1;
+ char s[21];
+ int n = snprintf(s, sizeof s, "%0*" PRIi64, slack, x);
+ DISSECTOR_ASSERT_CMPUINT(n, >, 0);
+ unsigned k = n - fields[fidx].counter_off;
+ 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);
+ }}
+ }}
+ off += fields[fidx].size;
+ ++fidx;
+ ++uidx;
+ break;
+ case ETI_TIMESTAMP_NS:
+ DISSECTOR_ASSERT_CMPUINT(fields[fidx].size, ==, 8);
+ proto_tree_add_item(t, hf_{proto}[fields[fidx].field_handle_idx], tvb, off, fields[fidx].size, ENC_LITTLE_ENDIAN | ENC_TIME_NSECS);
+ off += fields[fidx].size;
+ ++fidx;
+ ++uidx;
+ break;
+ case ETI_DSCP:
+ DISSECTOR_ASSERT_CMPUINT(fields[fidx].size, ==, 1);
+ proto_tree_add_bitmask(t, tvb, off, hf_{proto}[fields[fidx].field_handle_idx], ett_{proto}_dscp, dscp_bits, ENC_LITTLE_ENDIAN);
+ off += fields[fidx].size;
+ ++fidx;
+ ++uidx;
+ break;
+ }}
+ }}
+''', file=o)
+
+ print(''' return tvb_captured_length(tvb);
+}
+''', file=o)
+
+ print(f'''/* determine PDU length of protocol {proto.upper()} */
+static guint
+get_{proto}_message_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_)
+{{
+ return (guint){bl_fn}(tvb, offset);
+}}
+''', file=o)
+
+ if proto.startswith('eobi'):
+ print(f'''static int
+dissect_{proto}(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
+ void *data)
+{{
+ return udp_dissect_pdus(tvb, pinfo, tree, 4, NULL,
+ get_{proto}_message_len, dissect_{proto}_message, data);
+}}
+''', file=o)
+ else:
+ print(f'''static int
+dissect_{proto}(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
+ void *data)
+{{
+ tcp_dissect_pdus(tvb, pinfo, tree, TRUE, 4 /* bytes to read for bodylen */,
+ get_{proto}_message_len, dissect_{proto}_message, data);
+ return tvb_captured_length(tvb);
+}}
+''', file=o)
+
+def gen_register_fn(st, dt, n2enum, proto, desc, o=sys.stdout):
+ print(f'''void
+proto_register_{proto}(void)
+{{''', file=o)
+ gen_field_info(st, dt, n2enum, proto, o)
+
+ print(f''' static ei_register_info ei[] = {{
+ {{
+ &ei_{proto}_counter_overflow,
+ {{ "{proto}.counter_overflow", PI_PROTOCOL, PI_WARN, "Counter Overflow", EXPFILL }}
+ }},
+ {{
+ &ei_{proto}_invalid_template,
+ {{ "{proto}.invalid_template", PI_PROTOCOL, PI_ERROR, "Invalid Template ID", EXPFILL }}
+ }},
+ {{
+ &ei_{proto}_invalid_length,
+ {{ "{proto}.invalid_length", PI_PROTOCOL, PI_ERROR, "Invalid Body Length", EXPFILL }}
+ }},''', file=o)
+ if not proto.startswith('eobi'):
+ print(f''' {{
+ &ei_{proto}_unaligned,
+ {{ "{proto}.unaligned", PI_PROTOCOL, PI_ERROR, "A Body Length not divisible by 8 leads to unaligned followup messages", EXPFILL }}
+ }},''', file=o)
+ print(f''' {{
+ &ei_{proto}_missing,
+ {{ "{proto}.missing", PI_PROTOCOL, PI_WARN, "A required value is missing", EXPFILL }}
+ }},
+ {{
+ &ei_{proto}_overused,
+ {{ "{proto}.overused", PI_PROTOCOL, PI_WARN, "An unused value is set", EXPFILL }}
+ }}
+ }};''', file=o)
+
+ print(f''' proto_{proto} = proto_register_protocol("{desc}",
+ "{proto.upper()}", "{proto}");''', file=o)
+
+ print(f''' expert_module_t *expert_{proto} = expert_register_protocol(proto_{proto});
+ expert_register_field_array(expert_{proto}, ei, array_length(ei));''', file=o)
+
+ print(f' proto_register_field_array(proto_{proto}, hf, array_length(hf));',
+ file=o)
+ gen_subtree_array(st, proto, o)
+ print(' proto_register_subtree_array(ett, array_length(ett));', file=o)
+ if proto.startswith('eobi'):
+ print(f' proto_disable_by_default(proto_{proto});', file=o)
+ print('}\n', file=o)
+
+
+def gen_handoff_fn(proto, o=sys.stdout):
+ print(f'''void
+proto_reg_handoff_{proto}(void)
+{{
+ dissector_handle_t {proto}_handle = create_dissector_handle(dissect_{proto},
+ proto_{proto});
+
+ // cf. N7 Network Access Guide, e.g.
+ // https://www.xetra.com/xetra-en/technology/t7/system-documentation/release10-0/Release-10.0-2692700?frag=2692724
+ // https://www.xetra.com/resource/blob/2762078/388b727972b5122945eedf0e63c36920/data/N7-Network-Access-Guide-v2.0.59.pdf
+
+''', file=o)
+ if proto.startswith('eti'):
+ print(f''' // NB: can only be called once for a port/handle pair ...
+ // dissector_add_uint_with_preference("tcp.port", 19006 /* LF PROD */, eti_handle);
+
+ dissector_add_uint("tcp.port", 19006 /* LF PROD */, {proto}_handle);
+ dissector_add_uint("tcp.port", 19043 /* PS PROD */, {proto}_handle);
+ dissector_add_uint("tcp.port", 19506 /* LF SIMU */, {proto}_handle);
+ dissector_add_uint("tcp.port", 19543 /* PS SIMU */, {proto}_handle);''', file=o)
+ elif proto.startswith('xti'):
+ print(f''' // NB: unfortunately, Cash-ETI shares the same ports as Derivatives-ETI ...
+ // We thus can't really add a well-know port for XTI.
+ // Use Wireshark's `Decode As...` or tshark's `-d tcp.port=19043,xti` feature
+ // to switch from ETI to XTI dissection.
+ dissector_add_uint_with_preference("tcp.port", 19042 /* dummy */, {proto}_handle);''', file=o)
+ else:
+ print(f''' static const int ports[] = {{
+ 59000, // Snapshot EUREX US-allowed PROD
+ 59001, // Incremental EUREX US-allowed PROD
+ 59032, // Snapshot EUREX US-restricted PROD
+ 59033, // Incremental EUREX US-restricted PROD
+ 59500, // Snapshot EUREX US-allowed SIMU
+ 59501, // Incremental EUREX US-allowed SIMU
+ 59532, // Snapshot EUREX US-restricted SIMU
+ 59533, // Incremental EUREX US-restricted SIMU
+
+ 57000, // Snapshot FX US-allowed PROD
+ 57001, // Incremental FX US-allowed PROD
+ 57032, // Snapshot FX US-restricted PROD
+ 57033, // Incremental FX US-restricted PROD
+ 57500, // Snapshot FX US-allowed SIMU
+ 57501, // Incremental FX US-allowed SIMU
+ 57532, // Snapshot FX US-restricted SIMU
+ 57533, // Incremental FX US-restricted SIMU
+
+ 59000, // Snapshot Xetra PROD
+ 59001, // Incremental Xetra PROD
+ 59500, // Snapshot Xetra SIMU
+ 59501, // Incremental Xetra SIMU
+
+ 56000, // Snapshot Boerse Frankfurt PROD
+ 56001, // Incremental Boerse Frankfurt PROD
+ 56500, // Snapshot Boerse Frankfurt SIMU
+ 56501 // Incremental Boerse Frankfurt SIMU
+ }};
+ for (unsigned i = 0; i < sizeof ports / sizeof ports[0]; ++i)
+ dissector_add_uint("udp.port", ports[i], {proto}_handle);''', file=o)
+ print('}', file=o)
+
+def is_int(t):
+ if t is not None:
+ r = t.get('rootType')
+ return r in ('int', 'floatDecimal') or (r == 'String' and t.get('size') == '1')
+ return False
+
+def is_enum(t):
+ if t is not None:
+ r = t.get('rootType')
+ if r == 'int' or (r == 'String' and t.get('size') == '1'):
+ return t.find('ValidValue') is not None
+ return False
+
+def is_fixed_point(t):
+ return t is not None and t.get('rootType') == 'floatDecimal'
+
+def is_timestamp_ns(t):
+ return t is not None and t.get('type') == 'UTCTimestamp'
+
+def is_dscp(t):
+ return t is not None and t.get('name') == 'DSCP'
+
+pad_re = re.compile('Pad[1-9]')
+
+def is_padding(t):
+ if t is not None:
+ return t.get('rootType') == 'String' and pad_re.match(t.get('name'))
+ return False
+
+def is_fixed_string(t):
+ if t is not None:
+ return t.get('rootType') in ('String', 'data') and not t.get('variableSize')
+ return False
+
+def is_var_string(t):
+ if t is not None:
+ return t.get('rootType') in ('String', 'data') and t.get('variableSize') is not None
+ return False
+
+def is_unsigned(t):
+ v = t.get('minValue')
+ return v is not None and not v.startswith('-')
+
+def is_counter(t):
+ return t.get('type') == 'Counter'
+
+def type_to_fmt(t):
+ if is_padding(t):
+ return f'{t.get("size")}x'
+ elif is_int(t):
+ n = int(t.get('size'))
+ if n == 1:
+ return 'B'
+ else:
+ if n == 2:
+ c = 'h'
+ elif n == 4:
+ c = 'i'
+ elif n == 8:
+ c = 'q'
+ else:
+ raise ValueError(f'unknown int size {n}')
+ if is_unsigned(t):
+ c = c.upper()
+ return c
+ elif is_fixed_string(t):
+ return f'{t.get("size")}s'
+ else:
+ return '?'
+
+def pp_int_type(t):
+ if not is_int(t):
+ return None
+ s = 'i'
+ if is_unsigned(t):
+ s = 'u'
+ n = int(t.get('size'))
+ s += str(n)
+ return s
+
+def is_elementary(t):
+ return t is not None and t.get('counter') is None
+
+def group_members(e, dt):
+ xs = []
+ ms = []
+ for m in e:
+ t = dt.get(m.get('type'))
+ if is_elementary(t):
+ ms.append(m)
+ else:
+ if ms:
+ xs.append(ms)
+ ms = []
+ xs.append([m])
+ if ms:
+ xs.append(ms)
+ return xs
+
+
+
+def parse_args():
+ p = argparse.ArgumentParser(description='Generate Wireshark Dissector for ETI/EOBI style protocol specifictions')
+ p.add_argument('filename', help='protocol description XML file')
+ p.add_argument('--proto', default='eti',
+ help='short protocol name (default: %(default)s)')
+ p.add_argument('--desc', '-d',
+ default='Enhanced Trading Interface',
+ help='protocol description (default: %(default)s)')
+ p.add_argument('--output', '-o', default='-',
+ help='output filename (default: stdout)')
+ args = p.parse_args()
+ return args
+
+def main():
+ args = parse_args()
+ filename = args.filename
+ d = ET.parse(filename)
+ o = sys.stdout if args.output == '-' else open(args.output, 'w')
+ proto = args.proto
+
+ version = (d.getroot().get('version'), d.getroot().get('subVersion'))
+ desc = f'{args.desc} {version[0]}'
+
+ dt = get_data_types(d)
+ st = get_structs(d)
+ used = get_used_types(st)
+ for k in list(dt.keys()):
+ if k not in used:
+ del dt[k]
+ ts = get_templates(st)
+ ams = d.getroot().find('ApplicationMessages')
+
+ gen_header(proto, desc, o)
+ print(f'static int proto_{proto} = -1;', file=o)
+ gen_field_handles(st, dt, proto, o)
+ n2enum = gen_enums(dt, ts, o)
+ gen_dissect_structs(o)
+ sh = gen_subtree_handles(st, proto, o)
+ gen_dissect_fn(st, dt, ts, sh, ams, proto, o)
+ gen_register_fn(st, dt, n2enum, proto, desc, o)
+ gen_handoff_fn(proto, o)
+
+
+if __name__ == '__main__':
+ sys.exit(main())