diff options
Diffstat (limited to '')
-rw-r--r-- | tools/asterix/README.md | 51 | ||||
-rw-r--r-- | tools/asterix/packet-asterix-template.c | 867 | ||||
-rwxr-xr-x | tools/asterix/update-specs.py | 829 |
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() + |