summaryrefslogtreecommitdiffstats
path: root/tools/asterix
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tools/asterix/README.md51
-rw-r--r--tools/asterix/packet-asterix-template.c867
-rwxr-xr-xtools/asterix/update-specs.py829
3 files changed, 1747 insertions, 0 deletions
diff --git a/tools/asterix/README.md b/tools/asterix/README.md
new file mode 100644
index 00000000..d7b2101f
--- /dev/null
+++ b/tools/asterix/README.md
@@ -0,0 +1,51 @@
+# Asterix parser generator
+
+*Asterix* is a set of standards, where each standard is defined
+as so called *asterix category*.
+In addition, each *asterix category* is potentially released
+in number of editions. There is no guarantie about backward
+compatibility between the editions.
+
+The structured version of asterix specifications is maintained
+in a separate project:
+<https://zoranbosnjak.github.io/asterix-specs/specs.html>
+
+The purpose of this directory is to convert from structured
+specifications (json format) to the `epan/dissectors/packet-asterix.c` file,
+which is the actual asterix parser for this project.
+
+It is important **NOT** to edit `epan/dissectors/packet-asterix.c` file
+manually, since this file is automatically generated.
+
+## Manual update procedure
+
+To sync with the upstream asterix specifications, run:
+
+```bash
+# show current upstream git revision (for reference)
+export ASTERIX_SPECS_REV=$(./tools/asterix/update-specs.py --reference)
+echo $ASTERIX_SPECS_REV
+
+# update asterix decoder
+./tools/asterix/update-specs.py > epan/dissectors/packet-asterix.c
+git add epan/dissectors/packet-asterix.c
+
+# inspect change, rebuild project, test...
+
+# commit change, with reference to upstream version
+git commit -m "asterix: Sync with asterix-specs #$ASTERIX_SPECS_REV"
+```
+
+## Automatic update procedure
+
+To integrate asterix updates to a periodic (GitLab CI) job, use `--update` option.
+For example:
+
+```
+...
+# Asterix categories.
+- ./tools/asterix/update-specs.py --update || echo "asterix failed." >> commit-message.txt
+- COMMIT_FILES+=("epan/dissectors/packet-asterix.c")
+...
+```
+
diff --git a/tools/asterix/packet-asterix-template.c b/tools/asterix/packet-asterix-template.c
new file mode 100644
index 00000000..e655cfd7
--- /dev/null
+++ b/tools/asterix/packet-asterix-template.c
@@ -0,0 +1,867 @@
+/*
+
+Notice:
+
+
+This file is auto generated, do not edit!
+See tools/asterix/README.md for details.
+
+
+Data source:
+---{gitrev}---
+
+
+*/
+
+/* packet-asterix.c
+ * Routines for ASTERIX decoding
+ * By Marko Hrastovec <marko.hrastovec@sloveniacontrol.si>
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * ASTERIX (All-purpose structured EUROCONTROL surveillances
+ * information exchange) is a protocol related to air traffic control.
+ *
+ * The specifications can be downloaded from
+ * http://www.eurocontrol.int/services/asterix
+ */
+
+#include <config.h>
+
+#include <wsutil/bits_ctz.h>
+
+#include <epan/packet.h>
+#include <epan/prefs.h>
+#include <epan/proto_data.h>
+
+void proto_register_asterix(void);
+void proto_reg_handoff_asterix(void);
+
+#define PROTO_TAG_ASTERIX "ASTERIX"
+#define ASTERIX_PORT 8600
+
+#define MAX_DISSECT_STR 1024
+#define MAX_BUFFER 256
+
+static int proto_asterix = -1;
+
+static int hf_asterix_category = -1;
+static int hf_asterix_length = -1;
+static int hf_asterix_message = -1;
+static int hf_asterix_fspec = -1;
+static int hf_re_field_len = -1;
+static int hf_spare = -1;
+static int hf_counter = -1;
+static int hf_XXX_FX = -1;
+
+static int ett_asterix = -1;
+static int ett_asterix_category = -1;
+static int ett_asterix_length = -1;
+static int ett_asterix_message = -1;
+static int ett_asterix_subtree = -1;
+
+static dissector_handle_t asterix_handle;
+/* The following defines tell us how to decode the length of
+ * fields and how to construct their display structure */
+#define FIXED 1
+#define REPETITIVE 2
+#define FX 3
+/*#define FX_1 4*/
+/*#define RE 5*/
+#define COMPOUND 6
+/*#define SP 7*/
+/*#define FX_UAP 8*/
+#define EXP 9 /* Explicit (RE or SP) */
+
+/* The following defines tell us how to
+ * decode and display individual fields. */
+#define FIELD_PART_INT 0
+#define FIELD_PART_UINT 1
+#define FIELD_PART_FLOAT 2
+#define FIELD_PART_UFLOAT 3
+#define FIELD_PART_SQUAWK 4
+#define FIELD_PART_CALLSIGN 5
+#define FIELD_PART_ASCII 6
+#define FIELD_PART_FX 7
+#define FIELD_PART_HEX 8
+#define FIELD_PART_IAS_IM 9
+#define FIELD_PART_IAS_ASPD 10
+
+typedef struct FieldPart_s FieldPart;
+struct FieldPart_s {
+ uint16_t bit_length; /* length of field in bits */
+ double scaling_factor; /* scaling factor of the field (for instance: 1/128) */
+ uint8_t type; /* Pre-defined type for proper presentation */
+ int *hf; /* Pointer to hf representing this kind of data */
+ const char *format_string; /* format string for showing float values */
+};
+
+DIAG_OFF_PEDANTIC
+typedef struct AsterixField_s AsterixField;
+struct AsterixField_s {
+ uint8_t type; /* type of field */
+ unsigned length; /* fixed length */
+ unsigned repetition_counter_size; /* size of repetition counter, length of one item is in length */
+ unsigned header_length; /* the size is in first header_length bytes of the field */
+ int *hf; /* pointer to Wireshark hf_register_info */
+ const FieldPart **part; /* Look declaration and description of FieldPart above. */
+ const AsterixField *field[]; /* subfields */
+};
+DIAG_ON_PEDANTIC
+
+static void dissect_asterix_packet (tvbuff_t *, packet_info *pinfo, proto_tree *);
+static void dissect_asterix_data_block (tvbuff_t *tvb, packet_info *pinfo, unsigned, proto_tree *, uint8_t, int);
+static int dissect_asterix_fields (tvbuff_t *, packet_info *pinfo, unsigned, proto_tree *, uint8_t, const AsterixField *[]);
+
+static void asterix_build_subtree (tvbuff_t *, packet_info *pinfo, unsigned, proto_tree *, const AsterixField *);
+static void twos_complement (int64_t *, int);
+static uint8_t asterix_bit (uint8_t, uint8_t);
+static unsigned asterix_fspec_len (tvbuff_t *, unsigned);
+static uint8_t asterix_field_exists (tvbuff_t *, unsigned, int);
+static uint8_t asterix_get_active_uap (tvbuff_t *, unsigned, uint8_t);
+static int asterix_field_length (tvbuff_t *, unsigned, const AsterixField *);
+static int asterix_field_offset (tvbuff_t *, unsigned, const AsterixField *[], int);
+static int asterix_message_length (tvbuff_t *, unsigned, uint8_t, uint8_t);
+
+static const char AISCode[] = { ' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', ' ', ' ', ' ', ' ',
+ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ', ' ', ' ', ' ', ' ', ' ' };
+
+static const value_string valstr_XXX_FX[] = {
+ { 0, "End of data item" },
+ { 1, "Extension into next extent" },
+ { 0, NULL }
+};
+static const FieldPart IXXX_FX = { 1, 1.0, FIELD_PART_FX, &hf_XXX_FX, NULL };
+static const FieldPart IXXX_1bit_spare = { 1, 1.0, FIELD_PART_UINT, NULL, NULL };
+static const FieldPart IXXX_2bit_spare = { 2, 1.0, FIELD_PART_UINT, NULL, NULL };
+static const FieldPart IXXX_3bit_spare = { 3, 1.0, FIELD_PART_UINT, NULL, NULL };
+static const FieldPart IXXX_4bit_spare = { 4, 1.0, FIELD_PART_UINT, NULL, NULL };
+static const FieldPart IXXX_5bit_spare = { 5, 1.0, FIELD_PART_UINT, NULL, NULL };
+static const FieldPart IXXX_6bit_spare = { 6, 1.0, FIELD_PART_UINT, NULL, NULL };
+static const FieldPart IXXX_7bit_spare = { 7, 1.0, FIELD_PART_UINT, NULL, NULL };
+
+/* Spare Item */
+DIAG_OFF_PEDANTIC
+static const AsterixField IX_SPARE = { FIXED, 0, 0, 0, &hf_spare, NULL, { NULL } };
+
+/* insert1 */
+---{insert1}---
+/* insert1 */
+
+/* settings which category version to use for each ASTERIX category */
+static int global_categories_version[] = {
+ 0, /* 000 */
+ 0, /* 001 */
+ 0, /* 002 */
+ 0, /* 003 */
+ 0, /* 004 */
+ 0, /* 005 */
+ 0, /* 006 */
+ 0, /* 007 */
+ 0, /* 008 */
+ 0, /* 009 */
+ 0, /* 010 */
+ 0, /* 011 */
+ 0, /* 012 */
+ 0, /* 013 */
+ 0, /* 014 */
+ 0, /* 015 */
+ 0, /* 016 */
+ 0, /* 017 */
+ 0, /* 018 */
+ 0, /* 019 */
+ 0, /* 020 */
+ 0, /* 021 */
+ 0, /* 022 */
+ 0, /* 023 */
+ 0, /* 024 */
+ 0, /* 025 */
+ 0, /* 026 */
+ 0, /* 027 */
+ 0, /* 028 */
+ 0, /* 029 */
+ 0, /* 030 */
+ 0, /* 031 */
+ 0, /* 032 */
+ 0, /* 033 */
+ 0, /* 034 */
+ 0, /* 035 */
+ 0, /* 036 */
+ 0, /* 037 */
+ 0, /* 038 */
+ 0, /* 039 */
+ 0, /* 040 */
+ 0, /* 041 */
+ 0, /* 042 */
+ 0, /* 043 */
+ 0, /* 044 */
+ 0, /* 045 */
+ 0, /* 046 */
+ 0, /* 047 */
+ 0, /* 048 */
+ 0, /* 049 */
+ 0, /* 050 */
+ 0, /* 051 */
+ 0, /* 052 */
+ 0, /* 053 */
+ 0, /* 054 */
+ 0, /* 055 */
+ 0, /* 056 */
+ 0, /* 057 */
+ 0, /* 058 */
+ 0, /* 059 */
+ 0, /* 060 */
+ 0, /* 061 */
+ 0, /* 062 */
+ 0, /* 063 */
+ 0, /* 064 */
+ 0, /* 065 */
+ 0, /* 066 */
+ 0, /* 067 */
+ 0, /* 068 */
+ 0, /* 069 */
+ 0, /* 070 */
+ 0, /* 071 */
+ 0, /* 072 */
+ 0, /* 073 */
+ 0, /* 074 */
+ 0, /* 075 */
+ 0, /* 076 */
+ 0, /* 077 */
+ 0, /* 078 */
+ 0, /* 079 */
+ 0, /* 080 */
+ 0, /* 081 */
+ 0, /* 082 */
+ 0, /* 083 */
+ 0, /* 084 */
+ 0, /* 085 */
+ 0, /* 086 */
+ 0, /* 087 */
+ 0, /* 088 */
+ 0, /* 089 */
+ 0, /* 090 */
+ 0, /* 091 */
+ 0, /* 092 */
+ 0, /* 093 */
+ 0, /* 094 */
+ 0, /* 095 */
+ 0, /* 096 */
+ 0, /* 097 */
+ 0, /* 098 */
+ 0, /* 099 */
+ 0, /* 100 */
+ 0, /* 101 */
+ 0, /* 102 */
+ 0, /* 103 */
+ 0, /* 104 */
+ 0, /* 105 */
+ 0, /* 106 */
+ 0, /* 107 */
+ 0, /* 108 */
+ 0, /* 109 */
+ 0, /* 110 */
+ 0, /* 111 */
+ 0, /* 112 */
+ 0, /* 113 */
+ 0, /* 114 */
+ 0, /* 115 */
+ 0, /* 116 */
+ 0, /* 117 */
+ 0, /* 118 */
+ 0, /* 119 */
+ 0, /* 120 */
+ 0, /* 121 */
+ 0, /* 122 */
+ 0, /* 123 */
+ 0, /* 124 */
+ 0, /* 125 */
+ 0, /* 126 */
+ 0, /* 127 */
+ 0, /* 128 */
+ 0, /* 129 */
+ 0, /* 130 */
+ 0, /* 131 */
+ 0, /* 132 */
+ 0, /* 133 */
+ 0, /* 134 */
+ 0, /* 135 */
+ 0, /* 136 */
+ 0, /* 137 */
+ 0, /* 138 */
+ 0, /* 139 */
+ 0, /* 140 */
+ 0, /* 141 */
+ 0, /* 142 */
+ 0, /* 143 */
+ 0, /* 144 */
+ 0, /* 145 */
+ 0, /* 146 */
+ 0, /* 147 */
+ 0, /* 148 */
+ 0, /* 149 */
+ 0, /* 150 */
+ 0, /* 151 */
+ 0, /* 152 */
+ 0, /* 153 */
+ 0, /* 154 */
+ 0, /* 155 */
+ 0, /* 156 */
+ 0, /* 157 */
+ 0, /* 158 */
+ 0, /* 159 */
+ 0, /* 160 */
+ 0, /* 161 */
+ 0, /* 162 */
+ 0, /* 163 */
+ 0, /* 164 */
+ 0, /* 165 */
+ 0, /* 166 */
+ 0, /* 167 */
+ 0, /* 168 */
+ 0, /* 169 */
+ 0, /* 170 */
+ 0, /* 171 */
+ 0, /* 172 */
+ 0, /* 173 */
+ 0, /* 174 */
+ 0, /* 175 */
+ 0, /* 176 */
+ 0, /* 177 */
+ 0, /* 178 */
+ 0, /* 179 */
+ 0, /* 180 */
+ 0, /* 181 */
+ 0, /* 182 */
+ 0, /* 183 */
+ 0, /* 184 */
+ 0, /* 185 */
+ 0, /* 186 */
+ 0, /* 187 */
+ 0, /* 188 */
+ 0, /* 189 */
+ 0, /* 190 */
+ 0, /* 191 */
+ 0, /* 192 */
+ 0, /* 193 */
+ 0, /* 194 */
+ 0, /* 195 */
+ 0, /* 196 */
+ 0, /* 197 */
+ 0, /* 198 */
+ 0, /* 199 */
+ 0, /* 200 */
+ 0, /* 201 */
+ 0, /* 202 */
+ 0, /* 203 */
+ 0, /* 204 */
+ 0, /* 205 */
+ 0, /* 206 */
+ 0, /* 207 */
+ 0, /* 208 */
+ 0, /* 209 */
+ 0, /* 210 */
+ 0, /* 211 */
+ 0, /* 212 */
+ 0, /* 213 */
+ 0, /* 214 */
+ 0, /* 215 */
+ 0, /* 216 */
+ 0, /* 217 */
+ 0, /* 218 */
+ 0, /* 219 */
+ 0, /* 220 */
+ 0, /* 221 */
+ 0, /* 222 */
+ 0, /* 223 */
+ 0, /* 224 */
+ 0, /* 225 */
+ 0, /* 226 */
+ 0, /* 227 */
+ 0, /* 228 */
+ 0, /* 229 */
+ 0, /* 230 */
+ 0, /* 231 */
+ 0, /* 232 */
+ 0, /* 233 */
+ 0, /* 234 */
+ 0, /* 235 */
+ 0, /* 236 */
+ 0, /* 237 */
+ 0, /* 238 */
+ 0, /* 239 */
+ 0, /* 240 */
+ 0, /* 241 */
+ 0, /* 242 */
+ 0, /* 243 */
+ 0, /* 244 */
+ 0, /* 245 */
+ 0, /* 246 */
+ 0, /* 247 */
+ 0, /* 248 */
+ 0, /* 249 */
+ 0, /* 250 */
+ 0, /* 251 */
+ 0, /* 252 */
+ 0, /* 253 */
+ 0, /* 254 */
+ 0 /* 255 */
+};
+
+static int dissect_asterix (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
+{
+ col_set_str (pinfo->cinfo, COL_PROTOCOL, "ASTERIX");
+ col_clear (pinfo->cinfo, COL_INFO);
+
+ if (tree) { /* we are being asked for details */
+ dissect_asterix_packet (tvb, pinfo, tree);
+ }
+
+ return tvb_captured_length(tvb);
+}
+
+static void dissect_asterix_packet (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+ unsigned i;
+ uint8_t category;
+ uint16_t length;
+ proto_item *asterix_packet_item;
+ proto_tree *asterix_packet_tree;
+
+ for (i = 0; i < tvb_reported_length (tvb); i += length + 3) {
+ /* all ASTERIX messages have the same structure:
+ *
+ * header:
+ *
+ * 1 byte category even though a category is referenced as I019,
+ * this is just stored as decimal 19 (i.e. 0x13)
+ * 2 bytes length the total length of this ASTERIX message, the
+ * length includes the size of the header.
+ *
+ * Note that the there was a structural change at
+ * one point that changes whether multiple
+ * records can occur after the header or not
+ * (each category specifies this explicitly. All
+ * of the currently supported categories can have
+ * multiple records so this implementation just
+ * assumes that is always the case)
+ *
+ * record (multiple records can exists):
+ *
+ * n bytes FSPEC the field specifier is a bit mask where the
+ * lowest bit of each byte is called the FX bit.
+ * When the FX bit is set this indicates that
+ * the FSPEC extends into the next byte.
+ * Any other bit indicates the presence of the
+ * field that owns that bit (as per the User
+ * Application Profile (UAP)).
+ * X bytes Field Y X is as per the specification for field Y.
+ * etc.
+ *
+ * The User Application Profile (UAP) is simply a mapping from the
+ * FSPEC to fields. Each category has its own UAP.
+ */
+ category = tvb_get_guint8 (tvb, i);
+ length = (tvb_get_guint8 (tvb, i + 1) << 8) + tvb_get_guint8 (tvb, i + 2) - 3; /* -3 for category and length */
+
+ asterix_packet_item = proto_tree_add_item (tree, proto_asterix, tvb, i, length + 3, ENC_NA);
+ proto_item_append_text (asterix_packet_item, ", Category %03d", category);
+ asterix_packet_tree = proto_item_add_subtree (asterix_packet_item, ett_asterix);
+ proto_tree_add_item (asterix_packet_tree, hf_asterix_category, tvb, i, 1, ENC_BIG_ENDIAN);
+ proto_tree_add_item (asterix_packet_tree, hf_asterix_length, tvb, i + 1, 2, ENC_BIG_ENDIAN);
+
+ dissect_asterix_data_block (tvb, pinfo, i + 3, asterix_packet_tree, category, length);
+ }
+}
+
+static void dissect_asterix_data_block (tvbuff_t *tvb, packet_info *pinfo, unsigned offset, proto_tree *tree, uint8_t category, int length)
+{
+ uint8_t active_uap;
+ int fspec_len, inner_offset, size, counter;
+ proto_item *asterix_message_item = NULL;
+ proto_tree *asterix_message_tree = NULL;
+
+ for (counter = 1, inner_offset = 0; inner_offset < length; counter++) {
+
+ /* This loop handles parsing of each ASTERIX record */
+
+ active_uap = asterix_get_active_uap (tvb, offset + inner_offset, category);
+ size = asterix_message_length (tvb, offset + inner_offset, category, active_uap);
+ if (size > 0) {
+ asterix_message_item = proto_tree_add_item (tree, hf_asterix_message, tvb, offset + inner_offset, size, ENC_NA);
+ proto_item_append_text (asterix_message_item, ", #%02d, length: %d", counter, size);
+ asterix_message_tree = proto_item_add_subtree (asterix_message_item, ett_asterix_message);
+ fspec_len = asterix_fspec_len (tvb, offset + inner_offset);
+ /*show_fspec (tvb, asterix_message_tree, offset + inner_offset, fspec_len);*/
+ proto_tree_add_item (asterix_message_tree, hf_asterix_fspec, tvb, offset + inner_offset, fspec_len, ENC_NA);
+
+ size = dissect_asterix_fields (tvb, pinfo, offset + inner_offset, asterix_message_tree, category, categories[category][global_categories_version[category]][active_uap]);
+
+ inner_offset += size + fspec_len;
+ }
+ else {
+ inner_offset = length;
+ }
+ }
+}
+
+static int dissect_asterix_fields (tvbuff_t *tvb, packet_info *pinfo, unsigned offset, proto_tree *tree, uint8_t category, const AsterixField *current_uap[])
+{
+ unsigned i, j, size, start, len, inner_offset, fspec_len;
+ uint64_t counter;
+ proto_item *asterix_field_item = NULL;
+ proto_tree *asterix_field_tree = NULL;
+ proto_item *asterix_field_item2 = NULL;
+ proto_tree *asterix_field_tree2 = NULL;
+
+ if (current_uap == NULL)
+ return 0;
+
+ for (i = 0, size = 0; current_uap[i] != NULL; i++) {
+ start = asterix_field_offset (tvb, offset, current_uap, i);
+ if (start > 0) {
+ len = asterix_field_length (tvb, offset + start, current_uap[i]);
+ size += len;
+ switch(current_uap[i]->type) {
+ case COMPOUND:
+ asterix_field_item = proto_tree_add_item (tree, *current_uap[i]->hf, tvb, offset + start, len, ENC_NA);
+ asterix_field_tree = proto_item_add_subtree (asterix_field_item, ett_asterix_subtree);
+ fspec_len = asterix_fspec_len (tvb, offset + start);
+ proto_tree_add_item (asterix_field_tree, hf_asterix_fspec, tvb, offset + start, fspec_len, ENC_NA);
+ dissect_asterix_fields (tvb, pinfo, offset + start, asterix_field_tree, category, (const AsterixField **)current_uap[i]->field);
+ break;
+ case REPETITIVE:
+ asterix_field_item = proto_tree_add_item (tree, *current_uap[i]->hf, tvb, offset + start, len, ENC_NA);
+ asterix_field_tree = proto_item_add_subtree (asterix_field_item, ett_asterix_subtree);
+ for (j = 0, counter = 0; j < current_uap[i]->repetition_counter_size; j++) {
+ counter = (counter << 8) + tvb_get_guint8 (tvb, offset + start + j);
+ }
+ proto_tree_add_item (asterix_field_tree, hf_counter, tvb, offset + start, current_uap[i]->repetition_counter_size, ENC_BIG_ENDIAN);
+ for (j = 0, inner_offset = 0; j < counter; j++, inner_offset += current_uap[i]->length) {
+ asterix_field_item2 = proto_tree_add_item (asterix_field_tree, *current_uap[i]->hf, tvb, offset + start + current_uap[i]->repetition_counter_size + inner_offset, current_uap[i]->length, ENC_NA);
+ asterix_field_tree2 = proto_item_add_subtree (asterix_field_item2, ett_asterix_subtree);
+ asterix_build_subtree (tvb, pinfo, offset + start + current_uap[i]->repetition_counter_size + inner_offset, asterix_field_tree2, current_uap[i]);
+ }
+ break;
+ /* currently not generated from asterix-spec*/
+ /*case EXP:
+ asterix_field_item = proto_tree_add_item (tree, *current_uap[i]->hf, tvb, offset + start, len, ENC_NA);
+ asterix_field_tree = proto_item_add_subtree (asterix_field_item, ett_asterix_subtree);
+ proto_tree_add_item (asterix_field_tree, hf_re_field_len, tvb, offset + start, 1, ENC_BIG_ENDIAN);
+ start++;
+ fspec_len = asterix_fspec_len (tvb, offset + start);
+ proto_tree_add_item (asterix_field_tree, hf_asterix_fspec, tvb, offset + start, fspec_len, ENC_NA);
+ dissect_asterix_fields (tvb, pinfo, offset + start, asterix_field_tree, category, (const AsterixField **)current_uap[i]->field);
+ break;*/
+ default: /* FIXED, FX, FX_1, FX_UAP */
+ asterix_field_item = proto_tree_add_item (tree, *current_uap[i]->hf, tvb, offset + start, len, ENC_NA);
+ asterix_field_tree = proto_item_add_subtree (asterix_field_item, ett_asterix_subtree);
+ asterix_build_subtree (tvb, pinfo, offset + start, asterix_field_tree, current_uap[i]);
+ break;
+ }
+ }
+ }
+ return size;
+}
+
+static void asterix_build_subtree (tvbuff_t *tvb, packet_info *pinfo, unsigned offset, proto_tree *parent, const AsterixField *field)
+{
+ header_field_info* hfi;
+ int bytes_in_type, byte_offset_of_mask;
+ int i, inner_offset, offset_in_tvb, length_in_tvb;
+ uint8_t go_on;
+ int64_t value;
+ char *str_buffer = NULL;
+ double scaling_factor = 1.0;
+ uint8_t *air_speed_im_bit;
+ if (field->part != NULL) {
+ for (i = 0, inner_offset = 0, go_on = 1; go_on && field->part[i] != NULL; i++) {
+ value = tvb_get_bits64 (tvb, offset * 8 + inner_offset, field->part[i]->bit_length, ENC_BIG_ENDIAN);
+ if (field->part[i]->hf != NULL) {
+ offset_in_tvb = offset + inner_offset / 8;
+ length_in_tvb = (inner_offset % 8 + field->part[i]->bit_length + 7) / 8;
+ switch (field->part[i]->type) {
+ case FIELD_PART_FX:
+ if (!value) go_on = 0;
+ /* Fall through */
+ case FIELD_PART_INT:
+ case FIELD_PART_UINT:
+ case FIELD_PART_HEX:
+ case FIELD_PART_ASCII:
+ case FIELD_PART_SQUAWK:
+ hfi = proto_registrar_get_nth (*field->part[i]->hf);
+ if (hfi->bitmask)
+ {
+ // for a small bit field to decode correctly with
+ // a mask that belongs to a large(r) one we need to
+ // re-adjust offset_in_tvb and length_in_tvb to
+ // correctly align with the given hf mask.
+ //
+ // E.g. the following would not decode correctly:
+ // { &hf_020_050_V, ... FT_UINT16, ... 0x8000, ...
+ // instead one would have to use
+ // { &hf_020_050_V, ... FT_UINT8, ... 0x80, ...
+ //
+ bytes_in_type = ftype_wire_size(hfi->type);
+ if (bytes_in_type > 1)
+ {
+ byte_offset_of_mask = bytes_in_type - (ws_ilog2 (hfi->bitmask) + 8)/8;
+ if (byte_offset_of_mask >= 0)
+ {
+ offset_in_tvb -= byte_offset_of_mask;
+ length_in_tvb = bytes_in_type;
+ }
+ }
+ }
+ proto_tree_add_item (parent, *field->part[i]->hf, tvb, offset_in_tvb, length_in_tvb, ENC_BIG_ENDIAN);
+ break;
+ case FIELD_PART_FLOAT:
+ twos_complement (&value, field->part[i]->bit_length);
+ /* Fall through */
+ case FIELD_PART_UFLOAT:
+ scaling_factor = field->part[i]->scaling_factor;
+ if (field->part[i]->format_string != NULL)
+ proto_tree_add_double_format_value (parent, *field->part[i]->hf, tvb, offset_in_tvb, length_in_tvb, value * scaling_factor, field->part[i]->format_string, value * scaling_factor);
+ else
+ proto_tree_add_double (parent, *field->part[i]->hf, tvb, offset_in_tvb, length_in_tvb, value * scaling_factor);
+ break;
+ case FIELD_PART_CALLSIGN:
+ str_buffer = wmem_strdup_printf(
+ pinfo->pool,
+ "%c%c%c%c%c%c%c%c",
+ AISCode[(value >> 42) & 63],
+ AISCode[(value >> 36) & 63],
+ AISCode[(value >> 30) & 63],
+ AISCode[(value >> 24) & 63],
+ AISCode[(value >> 18) & 63],
+ AISCode[(value >> 12) & 63],
+ AISCode[(value >> 6) & 63],
+ AISCode[value & 63]);
+ proto_tree_add_string (parent, *field->part[i]->hf, tvb, offset_in_tvb, length_in_tvb, str_buffer);
+ break;
+ case FIELD_PART_IAS_IM:
+ /* special processing for I021/150 and I062/380#4 because Air Speed depends on IM subfield */
+ air_speed_im_bit = wmem_new (pinfo->pool, uint8_t);
+ *air_speed_im_bit = (tvb_get_guint8 (tvb, offset_in_tvb) & 0x80) >> 7;
+ /* Save IM info for the packet. key = 21150. */
+ p_add_proto_data (pinfo->pool, pinfo, proto_asterix, 21150, air_speed_im_bit);
+ proto_tree_add_item (parent, *field->part[i]->hf, tvb, offset_in_tvb, length_in_tvb, ENC_BIG_ENDIAN);
+ break;
+ case FIELD_PART_IAS_ASPD:
+ /* special processing for I021/150 and I062/380#4 because Air Speed depends on IM subfield */
+ air_speed_im_bit = (uint8_t *)p_get_proto_data (pinfo->pool, pinfo, proto_asterix, 21150);
+ if (!air_speed_im_bit || *air_speed_im_bit == 0)
+ scaling_factor = 1.0/16384.0;
+ else
+ scaling_factor = 0.001;
+ proto_tree_add_double (parent, *field->part[i]->hf, tvb, offset_in_tvb, length_in_tvb, value * scaling_factor);
+ break;
+ }
+ }
+ inner_offset += field->part[i]->bit_length;
+ }
+ } /* if not null */
+}
+
+static uint8_t asterix_bit (uint8_t b, uint8_t bitNo)
+{
+ return bitNo < 8 && (b & (0x80 >> bitNo)) > 0;
+}
+
+/* Function makes int64_t two's complement.
+ * Only the bit_len bit are set in int64_t. All more significant
+ * bits need to be set to have proper two's complement.
+ * If the number is negative, all other bits must be set to 1.
+ * If the number is positive, all other bits must remain 0. */
+static void twos_complement (int64_t *v, int bit_len)
+{
+ if (*v & (G_GUINT64_CONSTANT(1) << (bit_len - 1))) {
+ *v |= (G_GUINT64_CONSTANT(0xffffffffffffffff) << bit_len);
+ }
+}
+
+static unsigned asterix_fspec_len (tvbuff_t *tvb, unsigned offset)
+{
+ unsigned i;
+ unsigned max_length = tvb_reported_length (tvb) - offset;
+ for (i = 0; (tvb_get_guint8 (tvb, offset + i) & 1) && i < max_length; i++);
+ return i + 1;
+}
+
+static uint8_t asterix_field_exists (tvbuff_t *tvb, unsigned offset, int bitIndex)
+{
+ uint8_t bitNo, i;
+ bitNo = bitIndex + bitIndex / 7;
+ for (i = 0; i < bitNo / 8; i++) {
+ if (!(tvb_get_guint8 (tvb, offset + i) & 1)) return 0;
+ }
+ return asterix_bit (tvb_get_guint8 (tvb, offset + i), bitNo % 8);
+}
+
+static int asterix_field_length (tvbuff_t *tvb, unsigned offset, const AsterixField *field)
+{
+ unsigned size;
+ uint64_t count;
+ uint8_t i;
+
+ size = 0;
+ switch(field->type) {
+ case FIXED:
+ size = field->length;
+ break;
+ case REPETITIVE:
+ for (i = 0, count = 0; i < field->repetition_counter_size && i < sizeof (count); i++)
+ count = (count << 8) + tvb_get_guint8 (tvb, offset + i);
+ size = (unsigned)(field->repetition_counter_size + count * field->length);
+ break;
+ case FX:
+ for (size = field->length + field->header_length; tvb_get_guint8 (tvb, offset + size - 1) & 1; size += field->length);
+ break;
+ case EXP:
+ for (i = 0, size = 0; i < field->header_length; i++) {
+ size = (size << 8) + tvb_get_guint8 (tvb, offset + i);
+ }
+ break;
+ case COMPOUND:
+ /* FSPEC */
+ for (size = 0; tvb_get_guint8 (tvb, offset + size) & 1; size++);
+ size++;
+
+ for (i = 0; field->field[i] != NULL; i++) {
+ if (asterix_field_exists (tvb, offset, i))
+ size += asterix_field_length (tvb, offset + size, field->field[i]);
+ }
+ break;
+ }
+ return size;
+}
+
+/* This works for category 001. For other it may require changes. */
+static uint8_t asterix_get_active_uap (tvbuff_t *tvb, unsigned offset, uint8_t category)
+{
+ int i, inner_offset;
+ AsterixField **current_uap;
+
+ if ((category == 1) && (categories[category] != NULL)) { /* if category is supported */
+ if (categories[category][global_categories_version[category]][1] != NULL) { /* if exists another uap */
+ current_uap = (AsterixField **)categories[category][global_categories_version[category]][0];
+ if (current_uap != NULL) {
+ inner_offset = asterix_fspec_len (tvb, offset);
+ for (i = 0; current_uap[i] != NULL; i++) {
+ if (asterix_field_exists (tvb, offset, i)) {
+ if (i == 1) { /* uap selector (I001/020) is always at index '1' */
+ return tvb_get_guint8 (tvb, offset + inner_offset) >> 7;
+ }
+ inner_offset += asterix_field_length (tvb, offset + inner_offset, current_uap[i]);
+ }
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+static int asterix_field_offset (tvbuff_t *tvb, unsigned offset, const AsterixField *current_uap[], int field_index)
+{
+ int i, inner_offset;
+ inner_offset = 0;
+ if (asterix_field_exists (tvb, offset, field_index)) {
+ inner_offset = asterix_fspec_len (tvb, offset);
+ for (i = 0; i < field_index; i++) {
+ if (asterix_field_exists (tvb, offset, i))
+ inner_offset += asterix_field_length (tvb, offset + inner_offset, current_uap[i]);
+ }
+ }
+ return inner_offset;
+}
+
+static int asterix_message_length (tvbuff_t *tvb, unsigned offset, uint8_t category, uint8_t active_uap)
+{
+ int i, size;
+ AsterixField **current_uap;
+
+ if (categories[category] != NULL) { /* if category is supported */
+ current_uap = (AsterixField **)categories[category][global_categories_version[category]][active_uap];
+ if (current_uap != NULL) {
+ size = asterix_fspec_len (tvb, offset);
+ for (i = 0; current_uap[i] != NULL; i++) {
+ if (asterix_field_exists (tvb, offset, i)) {
+ size += asterix_field_length (tvb, offset + size, current_uap[i]);
+ }
+ }
+ return size;
+ }
+ }
+ return 0;
+}
+
+void proto_register_asterix (void)
+{
+ static hf_register_info hf[] = {
+ { &hf_asterix_category, { "Category", "asterix.category", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } },
+ { &hf_asterix_length, { "Length", "asterix.length", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } },
+ { &hf_asterix_message, { "Asterix message", "asterix.message", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } },
+ { &hf_asterix_fspec, { "FSPEC", "asterix.fspec", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } },
+ { &hf_re_field_len, { "RE LEN", "asterix.re_field_len", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } },
+ { &hf_spare, { "Spare", "asterix.spare", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } },
+ { &hf_counter, { "Counter", "asterix.counter", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } },
+ { &hf_XXX_FX, { "FX", "asterix.FX", FT_UINT8, BASE_DEC, VALS (valstr_XXX_FX), 0x01, "Extension into next extent", HFILL } },
+/* insert2 */
+---{insert2}---
+/* insert2 */
+ };
+
+ /* Setup protocol subtree array */
+ static int *ett[] = {
+ &ett_asterix,
+ &ett_asterix_category,
+ &ett_asterix_length,
+ &ett_asterix_message,
+ &ett_asterix_subtree
+ };
+
+ module_t *asterix_prefs_module;
+
+ proto_asterix = proto_register_protocol (
+ "ASTERIX packet", /* name */
+ "ASTERIX", /* short name */
+ "asterix" /* abbrev */
+ );
+
+ proto_register_field_array (proto_asterix, hf, array_length (hf));
+ proto_register_subtree_array (ett, array_length (ett));
+
+ asterix_handle = register_dissector ("asterix", dissect_asterix, proto_asterix);
+
+ asterix_prefs_module = prefs_register_protocol (proto_asterix, NULL);
+
+/* insert3 */
+---{insert3}---
+/* insert3 */
+}
+
+void proto_reg_handoff_asterix (void)
+{
+ dissector_add_uint_with_preference("udp.port", ASTERIX_PORT, asterix_handle);
+}
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * vi: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */
diff --git a/tools/asterix/update-specs.py b/tools/asterix/update-specs.py
new file mode 100755
index 00000000..7af735dc
--- /dev/null
+++ b/tools/asterix/update-specs.py
@@ -0,0 +1,829 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# By Zoran BoĆĄnjak <zoran.bosnjak@sloveniacontrol.si>
+#
+# Use asterix specifications in JSON format,
+# to generate C/C++ structures, suitable for wireshark.
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+
+import argparse
+
+import urllib.request
+import json
+from copy import copy, deepcopy
+from itertools import chain, repeat, takewhile
+from functools import reduce
+import os
+import sys
+import re
+
+# Path to default upstream repository
+upstream_repo = 'https://zoranbosnjak.github.io/asterix-specs'
+dissector_file = 'epan/dissectors/packet-asterix.c'
+
+class Offset(object):
+ """Keep track of number of added bits.
+ It's like integer, except when offsets are added together,
+ a 'modulo 8' is applied, such that offset is always between [0,7].
+ """
+
+ def __init__(self):
+ self.current = 0
+
+ def __add__(self, other):
+ self.current = (self.current + other) % 8
+ return self
+
+ @property
+ def get(self):
+ return self.current
+
+class Context(object):
+ """Support class to be used as a context manager.
+ The 'tell' method is used to output (print) some data.
+ All output is first collected to a buffer, then rendered
+ using a template file.
+ """
+ def __init__(self):
+ self.buffer = {}
+ self.offset = Offset()
+ self.inside_repetitive = False
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, exc_traceback):
+ pass
+
+ def tell(self, channel, s):
+ """Append string 's' to an output channel."""
+ lines = self.buffer.get(channel, [])
+ lines.append(s)
+ self.buffer[channel] = lines
+
+ def reset_offset(self):
+ self.offset = Offset()
+
+def get_number(value):
+ """Get Natural/Real/Rational number as an object."""
+ class Integer(object):
+ def __init__(self, val):
+ self.val = val
+ def __str__(self):
+ return '{}'.format(self.val)
+ def __float__(self):
+ return float(self.val)
+
+ class Ratio(object):
+ def __init__(self, a, b):
+ self.a = a
+ self.b = b
+ def __str__(self):
+ return '{}/{}'.format(self.a, self.b)
+ def __float__(self):
+ return float(self.a) / float(self.b)
+
+ class Real(object):
+ def __init__(self, val):
+ self.val = val
+ def __str__(self):
+ return '{0:f}'.format(self.val).rstrip('0')
+ def __float__(self):
+ return float(self.val)
+
+ t = value['type']
+ val = value['value']
+
+ if t == 'Integer':
+ return Integer(int(val))
+ if t == 'Ratio':
+ x, y = val['numerator'], val['denominator']
+ return Ratio(x, y)
+ if t == 'Real':
+ return Real(float(val))
+ raise Exception('unexpected value type {}'.format(t))
+
+def replace_string(s, mapping):
+ """Helper function to replace each entry from the mapping."""
+ for (key,val) in mapping.items():
+ s = s.replace(key, val)
+ return s
+
+def safe_string(s):
+ """String replacement table."""
+ return replace_string(s, {
+ # from C reference manual
+ chr(92): r"\\", # Backslash character.
+ '?': r"\?", # Question mark character.
+ "'": r"\'", # Single quotation mark.
+ '"': r'\"', # Double quotation mark.
+ "\a": "", # Audible alert.
+ "\b": "", # Backspace character.
+ "\e": "", # <ESC> character. (This is a GNU extension.)
+ "\f": "", # Form feed.
+ "\n": "", # Newline character.
+ "\r": "", # Carriage return.
+ "\t": " ", # Horizontal tab.
+ "\v": "", # Vertical tab.
+ })
+
+def get_scaling(content):
+ """Get scaling factor from the content."""
+ k = content.get('scaling')
+ if k is None:
+ return None
+ k = get_number(k)
+
+ fract = content['fractionalBits']
+
+ if fract > 0:
+ scale = format(float(k) / (pow(2, fract)), '.29f')
+ scale = scale.rstrip('0')
+ else:
+ scale = format(float(k))
+ return scale
+
+def get_fieldpart(content):
+ """Get FIELD_PART* from the content."""
+ t = content['type']
+ if t == 'Raw': return 'FIELD_PART_HEX'
+ elif t == 'Table': return 'FIELD_PART_UINT'
+ elif t == 'String':
+ var = content['variation']
+ if var == 'StringAscii': return 'FIELD_PART_ASCII'
+ elif var == 'StringICAO': return 'FIELD_PART_CALLSIGN'
+ elif var == 'StringOctal': return 'FIELD_PART_SQUAWK'
+ else:
+ raise Exception('unexpected string variation: {}'.format(var))
+ elif t == 'Integer':
+ if content['signed']:
+ return 'FIELD_PART_INT'
+ else:
+ return 'FIELD_PART_UINT'
+ elif t == 'Quantity':
+ if content['signed']:
+ return 'FIELD_PART_FLOAT'
+ else:
+ return 'FIELD_PART_UFLOAT'
+ elif t == 'Bds':
+ return 'FIELD_PART_HEX'
+ else:
+ raise Exception('unexpected content type: {}'.format(t))
+
+def download_url(path):
+ """Download url and return content as a string."""
+ with urllib.request.urlopen(upstream_repo + path) as url:
+ return url.read()
+
+def read_file(path):
+ """Read file content, return string."""
+ with open(path) as f:
+ return f.read()
+
+def load_jsons(paths):
+ """Load json files from either URL or from local disk."""
+
+ # load from url
+ if paths == []:
+ manifest = download_url('/manifest.json').decode()
+ listing = []
+ for spec in json.loads(manifest):
+ cat = spec['category']
+ for edition in spec['cats']:
+ listing.append('/specs/cat{}/cats/cat{}/definition.json'.format(cat, edition))
+ for edition in spec['refs']:
+ listing.append('/specs/cat{}/refs/ref{}/definition.json'.format(cat, edition))
+ return [download_url(i).decode() for i in listing]
+
+ # load from disk
+ else:
+ listing = []
+ for path in paths:
+ if os.path.isdir(path):
+ for root, dirs, files in os.walk(path):
+ for i in files:
+ (a,b) = os.path.splitext(i)
+ if (a,b) != ('definition', '.json'):
+ continue
+ listing.append(os.path.join(root, i))
+ elif os.path.isfile(path):
+ listing.append(path)
+ else:
+ raise Exception('unexpected path type: {}'.path)
+ return [read_file(f) for f in listing]
+
+def load_gitrev(paths):
+ """Read git revision reference."""
+
+ # load from url
+ if paths == []:
+ gitrev = download_url('/gitrev.txt').decode().strip()
+ return [upstream_repo, 'git revision: {}'.format(gitrev)]
+
+ # load from disk
+ else:
+ return ['(local disk)']
+
+def get_ft(ref, n, content, offset):
+ """Get FT... from the content."""
+ a = offset.get
+
+ # bruto bit size (next multiple of 8)
+ (m, b) = divmod(a+n, 8)
+ m = m if b == 0 else m + 1
+ m *= 8
+
+ mask = '0x00'
+ if a != 0 or b != 0:
+ bits = chain(repeat(0, a), repeat(1, n), repeat(0, m-n-a))
+ mask = 0
+ for (a,b) in zip(bits, reversed(range(m))):
+ mask += a*pow(2,b)
+ mask = hex(mask)
+ # prefix mask with zeros '0x000...', to adjust mask size
+ assert mask[0:2] == '0x'
+ mask = mask[2:]
+ required_mask_size = (m//8)*2
+ add_some = required_mask_size - len(mask)
+ mask = '0x' + '0'*add_some + mask
+
+ t = content['type']
+
+ if t == 'Raw':
+ if n > 64: # very long items
+ assert (n % 8) == 0, "very long items require byte alignment"
+ return 'FT_NONE, BASE_NONE, NULL, 0x00'
+
+ if (n % 8): # not byte aligned
+ base = 'DEC'
+ else: # byte aligned
+ if n >= 32: # long items
+ base = 'HEX'
+ else: # short items
+ base = 'HEX_DEC'
+ return 'FT_UINT{}, BASE_{}, NULL, {}'.format(m, base, mask)
+ elif t == 'Table':
+ return 'FT_UINT{}, BASE_DEC, VALS (valstr_{}), {}'.format(m, ref, mask)
+ elif t == 'String':
+ var = content['variation']
+ if var == 'StringAscii':
+ return 'FT_STRING, BASE_NONE, NULL, {}'.format(mask)
+ elif var == 'StringICAO':
+ return 'FT_STRING, BASE_NONE, NULL, {}'.format(mask)
+ elif var == 'StringOctal':
+ return 'FT_UINT{}, BASE_OCT, NULL, {}'.format(m, mask)
+ else:
+ raise Exception('unexpected string variation: {}'.format(var))
+ elif t == 'Integer':
+ signed = content['signed']
+ if signed:
+ return 'FT_INT{}, BASE_DEC, NULL, {}'.format(m, mask)
+ else:
+ return 'FT_UINT{}, BASE_DEC, NULL, {}'.format(m, mask)
+ elif t == 'Quantity':
+ return 'FT_DOUBLE, BASE_NONE, NULL, 0x00'
+ elif t == 'Bds':
+ return 'FT_UINT{}, BASE_DEC, NULL, {}'.format(m, mask)
+ else:
+ raise Exception('unexpected content type: {}'.format(t))
+
+def reference(cat, edition, path):
+ """Create reference string."""
+ name = '_'.join(path)
+ if edition is None:
+ return('{:03d}_{}'.format(cat, name))
+ return('{:03d}_V{}_{}_{}'.format(cat, edition['major'], edition['minor'], name))
+
+def get_content(rule):
+ t = rule['type']
+ # Most cases are 'ContextFree', use as specified.
+ if t == 'ContextFree':
+ return rule['content']
+ # Handle 'Dependent' contents as 'Raw'.
+ elif t == 'Dependent':
+ return {'type': "Raw"}
+ else:
+ raise Exception('unexpected type: {}'.format(t))
+
+def get_bit_size(item):
+ """Return bit size of a (spare) item."""
+ if item['spare']:
+ return item['length']
+ else:
+ return item['variation']['size']
+
+def get_description(item, content=None):
+ """Return item description."""
+ name = item['name'] if not is_generated(item) else None
+ title = item.get('title')
+ if content is not None and content.get('unit'):
+ unit = '[{}]'.format(safe_string(content['unit']))
+ else:
+ unit = None
+
+ parts = filter(lambda x: bool(x), [name, title, unit])
+ if not parts:
+ return ''
+ return reduce(lambda a,b: a + ', ' + b, parts)
+
+def generate_group(item, variation=None):
+ """Generate group-item from element-item."""
+ level2 = copy(item)
+ level2['name'] = 'VALUE'
+ level2['is_generated'] = True
+ if variation is None:
+ level1 = copy(item)
+ level1['variation'] = {
+ 'type': 'Group',
+ 'items': [level2],
+ }
+ else:
+ level2['variation'] = variation['variation']
+ level1 = {
+ 'type': "Group",
+ 'items': [level2],
+ }
+ return level1
+
+def is_generated(item):
+ return item.get('is_generated') is not None
+
+def ungroup(item):
+ """Convert group of items of known size to element"""
+ n = sum([get_bit_size(i) for i in item['variation']['items']])
+ result = copy(item)
+ result['variation'] = {
+ 'rule': {
+ 'content': {'type': 'Raw'},
+ 'type': 'ContextFree',
+ },
+ 'size': n,
+ 'type': 'Element',
+ }
+ return result
+
+def part1(ctx, get_ref, catalogue):
+ """Generate components in order
+ - static int hf_...
+ - FiledPart
+ - FieldPart[]
+ - AsterixField
+ """
+
+ tell = lambda s: ctx.tell('insert1', s)
+ tell_pr = lambda s: ctx.tell('insert2', s)
+
+ ctx.reset_offset()
+
+ def handle_item(path, item):
+ """Handle 'spare' or regular 'item'.
+ This function is used recursively, depending on the item structure.
+ """
+
+ def handle_variation(path, variation):
+ """Handle 'Element, Group...' variations.
+ This function is used recursively, depending on the item structure."""
+
+ t = variation['type']
+
+ ref = get_ref(path)
+
+ def part_of(item):
+ if item['spare']:
+ return '&IXXX_{}bit_spare'.format(item['length'])
+ return '&I{}_{}'.format(ref, item['name'])
+
+ if t == 'Element':
+ tell('static int hf_{} = -1;'.format(ref))
+ n = variation['size']
+ content = get_content(variation['rule'])
+ scaling = get_scaling(content)
+ scaling = scaling if scaling is not None else 1.0
+ fp = get_fieldpart(content)
+
+ if content['type'] == 'Table':
+ tell('static const value_string valstr_{}[] = {}'.format(ref, '{'))
+ for (a,b) in content['values']:
+ tell(' {} {}, "{}" {},'.format('{', a, safe_string(b), '}'))
+ tell(' {} 0, NULL {}'.format('{', '}'))
+ tell('};')
+
+ tell('static const FieldPart I{} = {} {}, {}, {}, &hf_{}, NULL {};'.format(ref, '{', n, scaling, fp, ref, '}'))
+ description = get_description(item, content)
+
+ ft = get_ft(ref, n, content, ctx.offset)
+ tell_pr(' {} &hf_{}, {} "{}", "asterix.{}", {}, NULL, HFILL {} {},'.format('{', ref, '{', description, ref, ft, '}', '}'))
+
+ ctx.offset += n
+
+ elif t == 'Group':
+ ctx.reset_offset()
+
+ description = get_description(item)
+ tell_pr(' {} &hf_{}, {} "{}", "asterix.{}", FT_NONE, BASE_NONE, NULL, 0x00, NULL, HFILL {} {},'.format('{', ref, '{', description, ref, '}', '}'))
+
+ tell('static int hf_{} = -1;'.format(ref))
+ for i in variation['items']:
+ handle_item(path, i)
+
+ # FieldPart[]
+ tell('static const FieldPart *I{}_PARTS[] = {}'.format(ref,'{'))
+ for i in variation['items']:
+ tell(' {},'.format(part_of(i)))
+ tell(' NULL')
+ tell('};')
+
+ # AsterixField
+ bit_size = sum([get_bit_size(i) for i in variation['items']])
+ byte_size = bit_size // 8
+ parts = 'I{}_PARTS'.format(ref)
+ comp = '{ NULL }'
+ if not ctx.inside_repetitive:
+ tell('static const AsterixField I{} = {} FIXED, {}, 0, 0, &hf_{}, {}, {} {};'.format
+ (ref, '{', byte_size, ref, parts, comp, '}'))
+
+ elif t == 'Extended':
+ ctx.reset_offset()
+
+ description = get_description(item)
+ tell_pr(' {} &hf_{}, {} "{}", "asterix.{}", FT_NONE, BASE_NONE, NULL, 0x00, NULL, HFILL {} {},'.format('{', ref, '{', description, ref, '}', '}'))
+ tell('static int hf_{} = -1;'.format(ref))
+
+ items = []
+ for i in variation['items']:
+ if i is None:
+ items.append(i)
+ continue
+ if i.get('variation') is not None:
+ if i['variation']['type'] == 'Group':
+ i = ungroup(i)
+ items.append(i)
+
+ for i in items:
+ if i is None:
+ ctx.offset += 1
+ else:
+ handle_item(path, i)
+
+ tell('static const FieldPart *I{}_PARTS[] = {}'.format(ref,'{'))
+ for i in items:
+ if i is None:
+ tell(' &IXXX_FX,')
+ else:
+ tell(' {},'.format(part_of(i)))
+
+ tell(' NULL')
+ tell('};')
+
+ # AsterixField
+ first_part = list(takewhile(lambda x: x is not None, items))
+ n = (sum([get_bit_size(i) for i in first_part]) + 1) // 8
+ parts = 'I{}_PARTS'.format(ref)
+ comp = '{ NULL }'
+ tell('static const AsterixField I{} = {} FX, {}, 0, {}, &hf_{}, {}, {} {};'.format
+ (ref, '{', n, 0, ref, parts, comp, '}'))
+
+ elif t == 'Repetitive':
+ ctx.reset_offset()
+ ctx.inside_repetitive = True
+
+ # Group is required below this item.
+ if variation['variation']['type'] == 'Element':
+ subvar = generate_group(item, variation)
+ else:
+ subvar = variation['variation']
+ handle_variation(path, subvar)
+
+ # AsterixField
+ bit_size = sum([get_bit_size(i) for i in subvar['items']])
+ byte_size = bit_size // 8
+ rep = variation['rep']['size'] // 8
+ parts = 'I{}_PARTS'.format(ref)
+ comp = '{ NULL }'
+ tell('static const AsterixField I{} = {} REPETITIVE, {}, {}, 0, &hf_{}, {}, {} {};'.format
+ (ref, '{', byte_size, rep, ref, parts, comp, '}'))
+ ctx.inside_repetitive = False
+
+ elif t == 'Explicit':
+ ctx.reset_offset()
+ tell('static int hf_{} = -1;'.format(ref))
+ description = get_description(item)
+ tell_pr(' {} &hf_{}, {} "{}", "asterix.{}", FT_NONE, BASE_NONE, NULL, 0x00, NULL, HFILL {} {},'.format('{', ref, '{', description, ref, '}', '}'))
+ tell('static const AsterixField I{} = {} EXP, 0, 0, 1, &hf_{}, NULL, {} NULL {} {};'.format(ref, '{', ref, '{', '}', '}'))
+
+ elif t == 'Compound':
+ ctx.reset_offset()
+ tell('static int hf_{} = -1;'.format(ref))
+ description = get_description(item)
+ tell_pr(' {} &hf_{}, {} "{}", "asterix.{}", FT_NONE, BASE_NONE, NULL, 0x00, NULL, HFILL {} {},'.format('{', ref, '{', description, ref, '}', '}'))
+ comp = '{'
+ for i in variation['items']:
+ if i is None:
+ comp += ' &IX_SPARE,'
+ continue
+ # Group is required below this item.
+ if i['variation']['type'] == 'Element':
+ subitem = generate_group(i)
+ else:
+ subitem = i
+ comp += ' &I{}_{},'.format(ref, subitem['name'])
+ handle_item(path, subitem)
+ comp += ' NULL }'
+
+ # AsterixField
+ tell('static const AsterixField I{} = {} COMPOUND, 0, 0, 0, &hf_{}, NULL, {} {};'.format
+ (ref, '{', ref, comp, '}'))
+
+ else:
+ raise Exception('unexpected variation type: {}'.format(t))
+
+ if item['spare']:
+ ctx.offset += item['length']
+ return
+
+ # Group is required on the first level.
+ if path == [] and item['variation']['type'] == 'Element':
+ variation = generate_group(item)['variation']
+ else:
+ variation = item['variation']
+ handle_variation(path + [item['name']], variation)
+
+ for item in catalogue:
+ # adjust 'repetitive fx' item
+ if item['variation']['type'] == 'Repetitive' and item['variation']['rep']['type'] == 'Fx':
+ var = item['variation']['variation'].copy()
+ if var['type'] != 'Element':
+ raise Exception("Expecting 'Element'")
+ item = item.copy()
+ item['variation'] = {
+ 'type': 'Extended',
+ 'items': [{
+ 'definition': None,
+ 'description': None,
+ 'name': 'Subitem',
+ 'remark': None,
+ 'spare': False,
+ 'title': 'Subitem',
+ 'variation': var,
+ }, None]
+ }
+ handle_item([], item)
+ tell('')
+
+def part2(ctx, ref, uap):
+ """Generate UAPs"""
+
+ tell = lambda s: ctx.tell('insert1', s)
+ tell('DIAG_OFF_PEDANTIC')
+
+ ut = uap['type']
+ if ut == 'uap':
+ variations = [{'name': 'uap', 'items': uap['items']}]
+ elif ut == 'uaps':
+ variations = uap['variations']
+ else:
+ raise Exception('unexpected uap type {}'.format(ut))
+
+ for var in variations:
+ tell('static const AsterixField *I{}_{}[] = {}'.format(ref, var['name'], '{'))
+ for i in var['items']:
+ if i is None:
+ tell(' &IX_SPARE,')
+ else:
+ tell(' &I{}_{},'.format(ref, i))
+ tell(' NULL')
+ tell('};')
+
+ tell('static const AsterixField **I{}[] = {}'.format(ref, '{'))
+ for var in variations:
+ tell(' I{}_{},'.format(ref, var['name']))
+ tell(' NULL')
+ tell('};')
+ tell('DIAG_ON_PEDANTIC')
+ tell('')
+
+def part3(ctx, specs):
+ """Generate
+ - static const AsterixField ***...
+ - static const enum_val_t ..._versions[]...
+ """
+ tell = lambda s: ctx.tell('insert1', s)
+ def fmt_edition(cat, edition):
+ return 'I{:03d}_V{}_{}'.format(cat, edition['major'], edition['minor'])
+
+ cats = set([spec['number'] for spec in specs])
+ for cat in sorted(cats):
+ lst = [spec for spec in specs if spec['number'] == cat]
+ editions = sorted([val['edition'] for val in lst], key = lambda x: (x['major'], x['minor']), reverse=True)
+ editions_fmt = [fmt_edition(cat, edition) for edition in editions]
+ editions_str = ', '.join(['I{:03d}'.format(cat)] + editions_fmt)
+ tell('DIAG_OFF_PEDANTIC')
+ tell('static const AsterixField ***I{:03d}all[] = {} {} {};'.format(cat, '{', editions_str, '}'))
+ tell('DIAG_ON_PEDANTIC')
+ tell('')
+
+ tell('static const enum_val_t I{:03d}_versions[] = {}'.format(cat, '{'))
+ edition = editions[0]
+ a = edition['major']
+ b = edition['minor']
+ tell(' {} "I{:03d}", "Version {}.{} (latest)", 0 {},'.format('{', cat, a, b, '}'))
+ for ix, edition in enumerate(editions, start=1):
+ a = edition['major']
+ b = edition['minor']
+ tell(' {} "I{:03d}_v{}_{}", "Version {}.{}", {} {},'.format('{', cat, a, b, a, b, ix, '}'))
+ tell(' { NULL, NULL, 0 }')
+ tell('};')
+ tell('')
+
+def part4(ctx, cats):
+ """Generate
+ - static const AsterixField ****categories[]...
+ - prefs_register_enum_preference ...
+ """
+ tell = lambda s: ctx.tell('insert1', s)
+ tell_pr = lambda s: ctx.tell('insert3', s)
+
+ tell('static const AsterixField ****categories[] = {')
+ for i in range(0, 256):
+ val = 'I{:03d}all'.format(i) if i in cats else 'NULL'
+ tell(' {}, /* {:03d} */'.format(val, i))
+ tell(' NULL')
+ tell('};')
+
+ for cat in sorted(cats):
+ tell_pr(' prefs_register_enum_preference (asterix_prefs_module, "i{:03d}_version", "I{:03d} version", "Select the CAT{:03d} version", &global_categories_version[{}], I{:03d}_versions, false);'.format(cat, cat, cat, cat, cat))
+
+class Output(object):
+ """Output context manager. Write either to stdout or to a dissector
+ file directly, depending on 'update' argument"""
+ def __init__(self, update):
+ self.update = update
+ self.f = None
+
+ def __enter__(self):
+ if self.update:
+ self.f = open(dissector_file, 'w')
+ return self
+
+ def __exit__(self, exc_type, exc_value, exc_traceback):
+ if self.f is not None:
+ self.f.close()
+
+ def dump(self, line):
+ if self.f is None:
+ print(line)
+ else:
+ self.f.write(line+'\n')
+
+def remove_rfs(spec):
+ """Remove RFS item. It's present in specs, but not used."""
+ catalogue = [] # create new catalogue without RFS
+ rfs_items = []
+ for i in spec['catalogue']:
+ if i['variation']['type'] == 'Rfs':
+ rfs_items.append(i['name'])
+ else:
+ catalogue.append(i)
+ if not rfs_items:
+ return spec
+ spec2 = copy(spec)
+ spec2['catalogue'] = catalogue
+ # remove RFS from UAP(s)
+ uap = deepcopy(spec['uap'])
+ ut = uap['type']
+ if ut == 'uap':
+ items = [None if i in rfs_items else i for i in uap['items']]
+ if items[-1] is None: items = items[:-1]
+ uap['items'] = items
+ elif ut == 'uaps':
+ variations = []
+ for var in uap['variations']:
+ items = [None if i in rfs_items else i for i in var['items']]
+ if items[-1] is None: items = items[:-1]
+ var['items'] = items
+ variations.append(var)
+ uap['variations'] = variations
+ else:
+ raise Exception('unexpected uap type {}'.format(ut))
+ spec2['uap'] = uap
+ return spec2
+
+def is_valid(spec):
+ """Check spec"""
+ def check_item(item):
+ if item['spare']:
+ return True
+ return check_variation(item['variation'])
+ def check_variation(variation):
+ t = variation['type']
+ if t == 'Element':
+ return True
+ elif t == 'Group':
+ return all([check_item(i) for i in variation['items']])
+ elif t == 'Extended':
+ trailing_fx = variation['items'][-1] == None
+ if not trailing_fx:
+ return False
+ return all([check_item(i) for i in variation['items'] if i is not None])
+ elif t == 'Repetitive':
+ return check_variation(variation['variation'])
+ elif t == 'Explicit':
+ return True
+ elif t == 'Compound':
+ items = [i for i in variation['items'] if i is not None]
+ return all([check_item(i) for i in items])
+ else:
+ raise Exception('unexpected variation type {}'.format(t))
+ return all([check_item(i) for i in spec['catalogue']])
+
+def main():
+ parser = argparse.ArgumentParser(description='Process asterix specs files.')
+ parser.add_argument('paths', metavar='PATH', nargs='*',
+ help='json spec file(s), use upstream repository in no input is given')
+ parser.add_argument('--reference', action='store_true',
+ help='print upstream reference and exit')
+ parser.add_argument("--update", action="store_true",
+ help="Update %s as needed instead of writing to stdout" % dissector_file)
+ args = parser.parse_args()
+
+ if args.reference:
+ gitrev_short = download_url('/gitrev.txt').decode().strip()[0:10]
+ print(gitrev_short)
+ sys.exit(0)
+
+ # read and json-decode input files
+ jsons = load_jsons(args.paths)
+ jsons = [json.loads(i) for i in jsons]
+ jsons = sorted(jsons, key = lambda x: (x['number'], x['edition']['major'], x['edition']['minor']))
+ jsons = [spec for spec in jsons if spec['type'] == 'Basic']
+ jsons = [remove_rfs(spec) for spec in jsons]
+ jsons = [spec for spec in jsons if is_valid(spec)]
+
+ cats = list(set([x['number'] for x in jsons]))
+ latest_editions = {cat: sorted(
+ filter(lambda x: x['number'] == cat, jsons),
+ key = lambda x: (x['edition']['major'], x['edition']['minor']), reverse=True)[0]['edition']
+ for cat in cats}
+
+ # regular expression for template rendering
+ ins = re.compile(r'---\{([A-Za-z0-9_]*)\}---')
+
+ gitrev = load_gitrev(args.paths)
+ with Context() as ctx:
+ for i in gitrev:
+ ctx.tell('gitrev', i)
+
+ # generate parts into the context buffer
+ for spec in jsons:
+ is_latest = spec['edition'] == latest_editions[spec['number']]
+
+ ctx.tell('insert1', '/* Category {:03d}, edition {}.{} */'.format(spec['number'], spec['edition']['major'], spec['edition']['minor']))
+
+ # handle part1
+ get_ref = lambda path: reference(spec['number'], spec['edition'], path)
+ part1(ctx, get_ref, spec['catalogue'])
+ if is_latest:
+ ctx.tell('insert1', '/* Category {:03d}, edition {}.{} (latest) */'.format(spec['number'], spec['edition']['major'], spec['edition']['minor']))
+ get_ref = lambda path: reference(spec['number'], None, path)
+ part1(ctx, get_ref, spec['catalogue'])
+
+ # handle part2
+ cat = spec['number']
+ edition = spec['edition']
+ ref = '{:03d}_V{}_{}'.format(cat, edition['major'], edition['minor'])
+ part2(ctx, ref, spec['uap'])
+ if is_latest:
+ ref = '{:03d}'.format(cat)
+ part2(ctx, ref, spec['uap'])
+
+ part3(ctx, jsons)
+ part4(ctx, set([spec['number'] for spec in jsons]))
+
+ # use context buffer to render template
+ script_path = os.path.dirname(os.path.realpath(__file__))
+ with open(os.path.join(script_path, 'packet-asterix-template.c')) as f:
+ template_lines = f.readlines()
+
+ # All input is collected and rendered.
+ # It's safe to update the disector.
+
+ # copy each line of the template to required output,
+ # if the 'insertion' is found in the template,
+ # replace it with the buffer content
+ with Output(args.update) as out:
+ for line in template_lines:
+ line = line.rstrip()
+
+ insertion = ins.match(line)
+ if insertion is None:
+ out.dump(line)
+ else:
+ segment = insertion.group(1)
+ [out.dump(i) for i in ctx.buffer[segment]]
+
+if __name__ == '__main__':
+ main()
+