Replace JSON-GLib by custom JSON dumper library
The (optional) JSON-GLib library adds dependencies on GObject, GIO. For statically linked oss-fuzz builds it also adds libffi and more. To avoid these dependencies, replace JSON-GLib by some custom code. This allows `tshark -G elastic-mapping` to be enabled by default without extra deps. API design goals of the new JSON dumper library: - Small interface without a lot of abstraction. - Avoid memory allocations if possible (currently none, but maybe json_puts_string will be replaced to improve UTF-8 support). - Do not implement parsing, this is currently handled by jsmn. Methods to open/close array/objects and to set members are inspired by the JsonGlib interface. The interfaces to write values is inspired by the sharkd code (json_puts_string is also borrowed from that). The only observed differences in the tshark output: - JSON-GLib ignores duplicates, json_dumper does not and may produce duplicates and currently print two "ip.opt.sec_prot_auth_unassigned". - JSON-GLib adds a space before a colon (unimportant formatting detail). - (Not observed, but UTF-8 strings will be wrong like bug 14948.) A test was added to catch changes in the tshark output. I also fuzzed json_dumper with libFuzzer + UBSAN/ASAN and fixed an off-by-one error. Change-Id: I0c85b18777b04d1e0f613a3d59935ec59be87ff4 Link: https://www.wireshark.org/lists/wireshark-dev/201811/msg00052.html Reviewed-on: https://code.wireshark.org/review/30732 Petri-Dish: Peter Wu <peter@lekensteyn.nl> Tested-by: Petri Dish Buildbot Reviewed-by: Anders Broman <a.broman58@gmail.com>
This commit is contained in:
parent
fb9c6905ef
commit
656cc19fc7
8
debian/libwsutil0.symbols
vendored
8
debian/libwsutil0.symbols
vendored
@ -101,6 +101,14 @@ libwsutil.so.0 libwsutil0 #MINVER#
|
|||||||
isprint_string@Base 1.10.0
|
isprint_string@Base 1.10.0
|
||||||
isprint_utf8_string@Base 2.6.1
|
isprint_utf8_string@Base 2.6.1
|
||||||
json_decode_string_inplace@Base 2.9.0
|
json_decode_string_inplace@Base 2.9.0
|
||||||
|
json_dumper_begin_array@Base 2.9.0
|
||||||
|
json_dumper_begin_object@Base 2.9.0
|
||||||
|
json_dumper_end_array@Base 2.9.0
|
||||||
|
json_dumper_end_object@Base 2.9.0
|
||||||
|
json_dumper_finish@Base 2.9.0
|
||||||
|
json_dumper_set_member_name@Base 2.9.0
|
||||||
|
json_dumper_value_anyf@Base 2.9.0
|
||||||
|
json_dumper_value_string@Base 2.9.0
|
||||||
json_parse@Base 2.9.0
|
json_parse@Base 2.9.0
|
||||||
json_validate@Base 2.9.0
|
json_validate@Base 2.9.0
|
||||||
linear2alaw@Base 1.12.0~rc1
|
linear2alaw@Base 1.12.0~rc1
|
||||||
|
126
epan/proto.c
126
epan/proto.c
@ -20,6 +20,7 @@
|
|||||||
#include <wsutil/bits_count_ones.h>
|
#include <wsutil/bits_count_ones.h>
|
||||||
#include <wsutil/sign_ext.h>
|
#include <wsutil/sign_ext.h>
|
||||||
#include <wsutil/utf8_entities.h>
|
#include <wsutil/utf8_entities.h>
|
||||||
|
#include <wsutil/json_dumper.h>
|
||||||
|
|
||||||
#include <ftypes/ftypes-int.h>
|
#include <ftypes/ftypes-int.h>
|
||||||
|
|
||||||
@ -47,10 +48,6 @@
|
|||||||
#include <wsutil/ws_printf.h> /* ws_debug_printf */
|
#include <wsutil/ws_printf.h> /* ws_debug_printf */
|
||||||
#include <wsutil/crash_info.h>
|
#include <wsutil/crash_info.h>
|
||||||
|
|
||||||
#ifdef HAVE_JSONGLIB
|
|
||||||
#include <json-glib/json-glib.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Ptvcursor limits */
|
/* Ptvcursor limits */
|
||||||
#define SUBTREE_ONCE_ALLOCATION_NUMBER 8
|
#define SUBTREE_ONCE_ALLOCATION_NUMBER 8
|
||||||
#define SUBTREE_MAX_LEVELS 256
|
#define SUBTREE_MAX_LEVELS 256
|
||||||
@ -10263,21 +10260,17 @@ proto_registrar_dump_fieldcount(void)
|
|||||||
return (gpa_hfinfo.allocated_len > PROTO_PRE_ALLOC_HF_FIELDS_MEM);
|
return (gpa_hfinfo.allocated_len > PROTO_PRE_ALLOC_HF_FIELDS_MEM);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_JSONGLIB
|
static void
|
||||||
|
elastic_add_base_mapping(json_dumper *dumper)
|
||||||
static JsonBuilder*
|
|
||||||
elastic_add_base_mapping(JsonBuilder* builder)
|
|
||||||
{
|
{
|
||||||
json_builder_set_member_name(builder, "template");
|
json_dumper_set_member_name(dumper, "template");
|
||||||
json_builder_add_string_value(builder, "packets-*");
|
json_dumper_value_string(dumper, "packets-*");
|
||||||
|
|
||||||
json_builder_set_member_name(builder, "settings");
|
json_dumper_set_member_name(dumper, "settings");
|
||||||
json_builder_begin_object(builder);
|
json_dumper_begin_object(dumper);
|
||||||
json_builder_set_member_name(builder, "index.mapping.total_fields.limit");
|
json_dumper_set_member_name(dumper, "index.mapping.total_fields.limit");
|
||||||
json_builder_add_int_value(builder, 1000000);
|
json_dumper_value_anyf(dumper, "%d", 1000000);
|
||||||
json_builder_end_object(builder);
|
json_dumper_end_object(dumper);
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gchar* ws_type_to_elastic(guint type _U_)
|
gchar* ws_type_to_elastic(guint type _U_)
|
||||||
@ -10358,14 +10351,9 @@ proto_registrar_dump_elastic(const gchar* filter)
|
|||||||
{
|
{
|
||||||
header_field_info *hfinfo;
|
header_field_info *hfinfo;
|
||||||
header_field_info *parent_hfinfo;
|
header_field_info *parent_hfinfo;
|
||||||
JsonGenerator* generator;
|
|
||||||
JsonBuilder* builder;
|
|
||||||
JsonNode* root;
|
|
||||||
gsize length;
|
|
||||||
guint i;
|
guint i;
|
||||||
gboolean open_object = TRUE;
|
gboolean open_object = TRUE;
|
||||||
const char* prev_proto = NULL;
|
const char* prev_proto = NULL;
|
||||||
gchar* data;
|
|
||||||
gchar* str;
|
gchar* str;
|
||||||
gchar** protos = NULL;
|
gchar** protos = NULL;
|
||||||
gchar* proto;
|
gchar* proto;
|
||||||
@ -10382,30 +10370,33 @@ proto_registrar_dump_elastic(const gchar* filter)
|
|||||||
* n.label -> where n is the indentation level and label the name of the object
|
* n.label -> where n is the indentation level and label the name of the object
|
||||||
*/
|
*/
|
||||||
|
|
||||||
builder = json_builder_new();
|
json_dumper dumper = {
|
||||||
json_builder_begin_object(builder); // 1.root
|
.output_file = stdout,
|
||||||
builder = elastic_add_base_mapping(builder);
|
.flags = JSON_DUMPER_FLAGS_PRETTY_PRINT,
|
||||||
|
};
|
||||||
|
json_dumper_begin_object(&dumper); // 1.root
|
||||||
|
elastic_add_base_mapping(&dumper);
|
||||||
|
|
||||||
json_builder_set_member_name(builder, "mappings");
|
json_dumper_set_member_name(&dumper, "mappings");
|
||||||
json_builder_begin_object(builder); // 2.mappings
|
json_dumper_begin_object(&dumper); // 2.mappings
|
||||||
json_builder_set_member_name(builder, "pcap_file");
|
json_dumper_set_member_name(&dumper, "pcap_file");
|
||||||
|
|
||||||
json_builder_begin_object(builder); // 3.pcap_file
|
json_dumper_begin_object(&dumper); // 3.pcap_file
|
||||||
json_builder_set_member_name(builder, "dynamic");
|
json_dumper_set_member_name(&dumper, "dynamic");
|
||||||
json_builder_add_boolean_value(builder, FALSE);
|
json_dumper_value_anyf(&dumper, "false");
|
||||||
|
|
||||||
json_builder_set_member_name(builder, "properties");
|
json_dumper_set_member_name(&dumper, "properties");
|
||||||
json_builder_begin_object(builder); // 4.properties
|
json_dumper_begin_object(&dumper); // 4.properties
|
||||||
json_builder_set_member_name(builder, "timestamp");
|
json_dumper_set_member_name(&dumper, "timestamp");
|
||||||
json_builder_begin_object(builder); // 5.timestamp
|
json_dumper_begin_object(&dumper); // 5.timestamp
|
||||||
json_builder_set_member_name(builder, "type");
|
json_dumper_set_member_name(&dumper, "type");
|
||||||
json_builder_add_string_value(builder, "date");
|
json_dumper_value_string(&dumper, "date");
|
||||||
json_builder_end_object(builder); // 5.timestamp
|
json_dumper_end_object(&dumper); // 5.timestamp
|
||||||
|
|
||||||
json_builder_set_member_name(builder, "layers");
|
json_dumper_set_member_name(&dumper, "layers");
|
||||||
json_builder_begin_object(builder); // 5.layers
|
json_dumper_begin_object(&dumper); // 5.layers
|
||||||
json_builder_set_member_name(builder, "properties");
|
json_dumper_set_member_name(&dumper, "properties");
|
||||||
json_builder_begin_object(builder); // 6.properties
|
json_dumper_begin_object(&dumper); // 6.properties
|
||||||
|
|
||||||
for (i = 0; i < gpa_hfinfo.len; i++) {
|
for (i = 0; i < gpa_hfinfo.len; i++) {
|
||||||
if (gpa_hfinfo.hfi[i] == NULL)
|
if (gpa_hfinfo.hfi[i] == NULL)
|
||||||
@ -10444,55 +10435,46 @@ proto_registrar_dump_elastic(const gchar* filter)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (prev_proto && g_strcmp0(parent_hfinfo->abbrev, prev_proto)) {
|
if (prev_proto && g_strcmp0(parent_hfinfo->abbrev, prev_proto)) {
|
||||||
json_builder_end_object(builder); // 8.properties
|
json_dumper_end_object(&dumper); // 8.properties
|
||||||
json_builder_end_object(builder); // 7.parent_hfinfo->abbrev
|
json_dumper_end_object(&dumper); // 7.parent_hfinfo->abbrev
|
||||||
open_object = TRUE;
|
open_object = TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
prev_proto = parent_hfinfo->abbrev;
|
prev_proto = parent_hfinfo->abbrev;
|
||||||
|
|
||||||
if (open_object) {
|
if (open_object) {
|
||||||
json_builder_set_member_name(builder, parent_hfinfo->abbrev);
|
json_dumper_set_member_name(&dumper, parent_hfinfo->abbrev);
|
||||||
json_builder_begin_object(builder); // 7.parent_hfinfo->abbrev
|
json_dumper_begin_object(&dumper); // 7.parent_hfinfo->abbrev
|
||||||
json_builder_set_member_name(builder, "properties");
|
json_dumper_set_member_name(&dumper, "properties");
|
||||||
json_builder_begin_object(builder); // 8.properties
|
json_dumper_begin_object(&dumper); // 8.properties
|
||||||
open_object = FALSE;
|
open_object = FALSE;
|
||||||
}
|
}
|
||||||
str = g_strdup(hfinfo->abbrev);
|
str = g_strdup(hfinfo->abbrev);
|
||||||
json_builder_set_member_name(builder, dot_to_underscore(str));
|
json_dumper_set_member_name(&dumper, dot_to_underscore(str));
|
||||||
g_free(str);
|
g_free(str);
|
||||||
json_builder_begin_object(builder); // 9.hfinfo->abbrev
|
json_dumper_begin_object(&dumper); // 9.hfinfo->abbrev
|
||||||
json_builder_set_member_name(builder, "type");
|
json_dumper_set_member_name(&dumper, "type");
|
||||||
json_builder_add_string_value(builder, ws_type_to_elastic(hfinfo->type));
|
json_dumper_value_string(&dumper, ws_type_to_elastic(hfinfo->type));
|
||||||
json_builder_end_object(builder); // 9.hfinfo->abbrev
|
json_dumper_end_object(&dumper); // 9.hfinfo->abbrev
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prev_proto) {
|
if (prev_proto) {
|
||||||
json_builder_end_object(builder); // 8.properties
|
json_dumper_end_object(&dumper); // 8.properties
|
||||||
json_builder_end_object(builder); // 7.parent_hfinfo->abbrev
|
json_dumper_end_object(&dumper); // 7.parent_hfinfo->abbrev
|
||||||
}
|
}
|
||||||
|
|
||||||
json_builder_end_object(builder); // 6.properties
|
json_dumper_end_object(&dumper); // 6.properties
|
||||||
json_builder_end_object(builder); // 5.layers
|
json_dumper_end_object(&dumper); // 5.layers
|
||||||
json_builder_end_object(builder); // 4.properties
|
json_dumper_end_object(&dumper); // 4.properties
|
||||||
json_builder_end_object(builder); // 3.pcap_file
|
json_dumper_end_object(&dumper); // 3.pcap_file
|
||||||
json_builder_end_object(builder); // 2.mappings
|
json_dumper_end_object(&dumper); // 2.mappings
|
||||||
DISSECTOR_ASSERT(json_builder_end_object(builder)); // 1.root
|
json_dumper_end_object(&dumper); // 1.root
|
||||||
|
gboolean ret = json_dumper_finish(&dumper);
|
||||||
|
DISSECTOR_ASSERT(ret);
|
||||||
|
|
||||||
generator = json_generator_new();
|
|
||||||
json_generator_set_pretty(generator, TRUE);
|
|
||||||
root = json_builder_get_root(builder);
|
|
||||||
json_generator_set_root(generator, root);
|
|
||||||
json_node_free(root);
|
|
||||||
g_object_unref(builder);
|
|
||||||
data = json_generator_to_data(generator, &length);
|
|
||||||
g_object_unref(generator);
|
|
||||||
ws_debug_printf("%s\n", data);
|
|
||||||
g_free(data);
|
|
||||||
g_strfreev(protos);
|
g_strfreev(protos);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Dumps the contents of the registration database to stdout. An independent
|
/* Dumps the contents of the registration database to stdout. An independent
|
||||||
* program can take this output and format it into nice tables or HTML or
|
* program can take this output and format it into nice tables or HTML or
|
||||||
|
@ -2479,10 +2479,8 @@ WS_DLL_PUBLIC void proto_registrar_dump_protocols(void);
|
|||||||
/** Dumps a glossary of the field value strings or true/false strings to STDOUT */
|
/** Dumps a glossary of the field value strings or true/false strings to STDOUT */
|
||||||
WS_DLL_PUBLIC void proto_registrar_dump_values(void);
|
WS_DLL_PUBLIC void proto_registrar_dump_values(void);
|
||||||
|
|
||||||
#ifdef HAVE_JSONGLIB
|
|
||||||
/** Dumps a mapping file for loading tshark output into ElasticSearch */
|
/** Dumps a mapping file for loading tshark output into ElasticSearch */
|
||||||
WS_DLL_PUBLIC void proto_registrar_dump_elastic(const gchar* filter);
|
WS_DLL_PUBLIC void proto_registrar_dump_elastic(const gchar* filter);
|
||||||
#endif
|
|
||||||
|
|
||||||
/** Dumps the number of protocol and field registrations to STDOUT.
|
/** Dumps the number of protocol and field registrations to STDOUT.
|
||||||
@return FALSE if we pre-allocated enough fields, TRUE otherwise. */
|
@return FALSE if we pre-allocated enough fields, TRUE otherwise. */
|
||||||
|
45
test/baseline/elastic-mapping-ip-subset.json
Normal file
45
test/baseline/elastic-mapping-ip-subset.json
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"template" : "packets-*",
|
||||||
|
"settings" : {
|
||||||
|
"index.mapping.total_fields.limit" : 1000000
|
||||||
|
},
|
||||||
|
"mappings" : {
|
||||||
|
"pcap_file" : {
|
||||||
|
"dynamic" : false,
|
||||||
|
"properties" : {
|
||||||
|
"timestamp" : {
|
||||||
|
"type" : "date"
|
||||||
|
},
|
||||||
|
"layers" : {
|
||||||
|
"properties" : {
|
||||||
|
"ip" : {
|
||||||
|
"properties" : {
|
||||||
|
"ip_version" : {
|
||||||
|
"type" : "short"
|
||||||
|
},
|
||||||
|
"ip_tos_delay" : {
|
||||||
|
"type" : "boolean"
|
||||||
|
},
|
||||||
|
"ip_len" : {
|
||||||
|
"type" : "integer"
|
||||||
|
},
|
||||||
|
"ip_dst" : {
|
||||||
|
"type" : "ip"
|
||||||
|
},
|
||||||
|
"ip_dst_host" : {
|
||||||
|
"type" : "string"
|
||||||
|
},
|
||||||
|
"ip_geoip_lat" : {
|
||||||
|
"type" : "float"
|
||||||
|
},
|
||||||
|
"ip_opt_padding" : {
|
||||||
|
"type" : "byte"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,8 @@
|
|||||||
#
|
#
|
||||||
'''Command line option tests'''
|
'''Command line option tests'''
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os.path
|
||||||
import subprocess
|
import subprocess
|
||||||
import subprocesstest
|
import subprocesstest
|
||||||
import fixtures
|
import fixtures
|
||||||
@ -181,6 +183,21 @@ class case_tshark_dump_glossaries(subprocesstest.SubprocessTestCase):
|
|||||||
self.runProcess((cmd_tshark, '-G', 'plugins'), env=base_env)
|
self.runProcess((cmd_tshark, '-G', 'plugins'), env=base_env)
|
||||||
self.assertGreaterEqual(self.countOutput('dissector'), 10, 'Fewer than 10 dissector plugins found')
|
self.assertGreaterEqual(self.countOutput('dissector'), 10, 'Fewer than 10 dissector plugins found')
|
||||||
|
|
||||||
|
def test_tshark_elastic_mapping(self, cmd_tshark, dirs, base_env):
|
||||||
|
def get_ip_props(obj):
|
||||||
|
return obj['mappings']['pcap_file']['properties']['layers']['properties']['ip']['properties']
|
||||||
|
baseline_file = os.path.join(dirs.baseline_dir, 'elastic-mapping-ip-subset.json')
|
||||||
|
with open(baseline_file) as f:
|
||||||
|
expected_obj = json.load(f)
|
||||||
|
keys_to_check = get_ip_props(expected_obj).keys()
|
||||||
|
proc = self.assertRun((cmd_tshark, '-G', 'elastic-mapping', '--elastic-mapping-filter', 'ip'))
|
||||||
|
actual_obj = json.loads(proc.stdout_str)
|
||||||
|
ip_props = get_ip_props(actual_obj)
|
||||||
|
for key in list(ip_props.keys()):
|
||||||
|
if key not in keys_to_check:
|
||||||
|
del ip_props[key]
|
||||||
|
self.assertEqual(actual_obj, expected_obj)
|
||||||
|
|
||||||
|
|
||||||
@fixtures.mark_usefixtures('test_env')
|
@fixtures.mark_usefixtures('test_env')
|
||||||
@fixtures.uses_fixtures
|
@fixtures.uses_fixtures
|
||||||
|
14
tshark.c
14
tshark.c
@ -140,9 +140,7 @@
|
|||||||
*/
|
*/
|
||||||
#define LONGOPT_COLOR (65536+1000)
|
#define LONGOPT_COLOR (65536+1000)
|
||||||
#define LONGOPT_NO_DUPLICATE_KEYS (65536+1001)
|
#define LONGOPT_NO_DUPLICATE_KEYS (65536+1001)
|
||||||
#ifdef HAVE_JSONGLIB
|
|
||||||
#define LONGOPT_ELASTIC_MAPPING_FILTER (65536+1002)
|
#define LONGOPT_ELASTIC_MAPPING_FILTER (65536+1002)
|
||||||
#endif
|
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
#define tshark_debug(...) g_warning(__VA_ARGS__)
|
#define tshark_debug(...) g_warning(__VA_ARGS__)
|
||||||
@ -439,10 +437,8 @@ print_usage(FILE *output)
|
|||||||
fprintf(output, " --no-duplicate-keys If -T json is specified, merge duplicate keys in an object\n");
|
fprintf(output, " --no-duplicate-keys If -T json is specified, merge duplicate keys in an object\n");
|
||||||
fprintf(output, " into a single key with as value a json array containing all\n");
|
fprintf(output, " into a single key with as value a json array containing all\n");
|
||||||
fprintf(output, " values\n");
|
fprintf(output, " values\n");
|
||||||
#ifdef HAVE_JSONGLIB
|
|
||||||
fprintf(output, " --elastic-mapping-filter <protocols> If -G elastic-mapping is specified, put only the\n");
|
fprintf(output, " --elastic-mapping-filter <protocols> If -G elastic-mapping is specified, put only the\n");
|
||||||
fprintf(output, " specified protocols within the mapping file\n");
|
fprintf(output, " specified protocols within the mapping file\n");
|
||||||
#endif
|
|
||||||
|
|
||||||
fprintf(output, "\n");
|
fprintf(output, "\n");
|
||||||
fprintf(output, "Miscellaneous:\n");
|
fprintf(output, "Miscellaneous:\n");
|
||||||
@ -479,9 +475,7 @@ glossary_option_help(void)
|
|||||||
fprintf(output, " -G column-formats dump column format codes and exit\n");
|
fprintf(output, " -G column-formats dump column format codes and exit\n");
|
||||||
fprintf(output, " -G decodes dump \"layer type\"/\"decode as\" associations and exit\n");
|
fprintf(output, " -G decodes dump \"layer type\"/\"decode as\" associations and exit\n");
|
||||||
fprintf(output, " -G dissector-tables dump dissector table names, types, and properties\n");
|
fprintf(output, " -G dissector-tables dump dissector table names, types, and properties\n");
|
||||||
#ifdef HAVE_JSONGLIB
|
|
||||||
fprintf(output, " -G elastic-mapping dump ElasticSearch mapping file\n");
|
fprintf(output, " -G elastic-mapping dump ElasticSearch mapping file\n");
|
||||||
#endif
|
|
||||||
fprintf(output, " -G fieldcount dump count of header fields and exit\n");
|
fprintf(output, " -G fieldcount dump count of header fields and exit\n");
|
||||||
fprintf(output, " -G fields dump fields glossary and exit\n");
|
fprintf(output, " -G fields dump fields glossary and exit\n");
|
||||||
fprintf(output, " -G ftypes dump field type basic and descriptive names\n");
|
fprintf(output, " -G ftypes dump field type basic and descriptive names\n");
|
||||||
@ -683,9 +677,7 @@ real_main(int argc, char *argv[])
|
|||||||
{"export-objects", required_argument, NULL, LONGOPT_EXPORT_OBJECTS},
|
{"export-objects", required_argument, NULL, LONGOPT_EXPORT_OBJECTS},
|
||||||
{"color", no_argument, NULL, LONGOPT_COLOR},
|
{"color", no_argument, NULL, LONGOPT_COLOR},
|
||||||
{"no-duplicate-keys", no_argument, NULL, LONGOPT_NO_DUPLICATE_KEYS},
|
{"no-duplicate-keys", no_argument, NULL, LONGOPT_NO_DUPLICATE_KEYS},
|
||||||
#ifdef HAVE_JSONGLIB
|
|
||||||
{"elastic-mapping-filter", required_argument, NULL, LONGOPT_ELASTIC_MAPPING_FILTER},
|
{"elastic-mapping-filter", required_argument, NULL, LONGOPT_ELASTIC_MAPPING_FILTER},
|
||||||
#endif
|
|
||||||
{0, 0, 0, 0 }
|
{0, 0, 0, 0 }
|
||||||
};
|
};
|
||||||
gboolean arg_error = FALSE;
|
gboolean arg_error = FALSE;
|
||||||
@ -729,9 +721,7 @@ real_main(int argc, char *argv[])
|
|||||||
gchar *volatile pdu_export_arg = NULL;
|
gchar *volatile pdu_export_arg = NULL;
|
||||||
char *volatile exp_pdu_filename = NULL;
|
char *volatile exp_pdu_filename = NULL;
|
||||||
exp_pdu_t exp_pdu_tap_data;
|
exp_pdu_t exp_pdu_tap_data;
|
||||||
#ifdef HAVE_JSONGLIB
|
|
||||||
const gchar* elastic_mapping_filter = NULL;
|
const gchar* elastic_mapping_filter = NULL;
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The leading + ensures that getopt_long() does not permute the argv[]
|
* The leading + ensures that getopt_long() does not permute the argv[]
|
||||||
@ -866,11 +856,9 @@ real_main(int argc, char *argv[])
|
|||||||
case 'X':
|
case 'X':
|
||||||
ex_opt_add(optarg);
|
ex_opt_add(optarg);
|
||||||
break;
|
break;
|
||||||
#ifdef HAVE_JSONGLIB
|
|
||||||
case LONGOPT_ELASTIC_MAPPING_FILTER:
|
case LONGOPT_ELASTIC_MAPPING_FILTER:
|
||||||
elastic_mapping_filter = optarg;
|
elastic_mapping_filter = optarg;
|
||||||
break;
|
break;
|
||||||
#endif
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -973,10 +961,8 @@ real_main(int argc, char *argv[])
|
|||||||
write_prefs(NULL);
|
write_prefs(NULL);
|
||||||
else if (strcmp(argv[2], "dissector-tables") == 0)
|
else if (strcmp(argv[2], "dissector-tables") == 0)
|
||||||
dissector_dump_dissector_tables();
|
dissector_dump_dissector_tables();
|
||||||
#ifdef HAVE_JSONGLIB
|
|
||||||
else if (strcmp(argv[2], "elastic-mapping") == 0)
|
else if (strcmp(argv[2], "elastic-mapping") == 0)
|
||||||
proto_registrar_dump_elastic(elastic_mapping_filter);
|
proto_registrar_dump_elastic(elastic_mapping_filter);
|
||||||
#endif
|
|
||||||
else if (strcmp(argv[2], "fieldcount") == 0) {
|
else if (strcmp(argv[2], "fieldcount") == 0) {
|
||||||
/* return value for the test suite */
|
/* return value for the test suite */
|
||||||
exit_status = proto_registrar_dump_fieldcount();
|
exit_status = proto_registrar_dump_fieldcount();
|
||||||
|
@ -43,6 +43,7 @@ set(WSUTIL_PUBLIC_HEADERS
|
|||||||
inet_ipv6.h
|
inet_ipv6.h
|
||||||
interface.h
|
interface.h
|
||||||
jsmn.h
|
jsmn.h
|
||||||
|
json_dumper.h
|
||||||
mpeg-audio.h
|
mpeg-audio.h
|
||||||
netlink.h
|
netlink.h
|
||||||
nstime.h
|
nstime.h
|
||||||
@ -99,6 +100,7 @@ set(WSUTIL_COMMON_FILES
|
|||||||
inet_addr.c
|
inet_addr.c
|
||||||
interface.c
|
interface.c
|
||||||
jsmn.c
|
jsmn.c
|
||||||
|
json_dumper.c
|
||||||
mpeg-audio.c
|
mpeg-audio.c
|
||||||
nstime.c
|
nstime.c
|
||||||
cpu_info.c
|
cpu_info.c
|
||||||
|
285
wsutil/json_dumper.c
Normal file
285
wsutil/json_dumper.c
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
/* wsjson.h
|
||||||
|
* Routines for serializing data as JSON.
|
||||||
|
*
|
||||||
|
* Copyright 2018, Peter Wu <peter@lekensteyn.nl>
|
||||||
|
*
|
||||||
|
* Wireshark - Network traffic analyzer
|
||||||
|
* By Gerald Combs <gerald@wireshark.org>
|
||||||
|
* Copyright 1998 Gerald Combs
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "json_dumper.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* json_dumper.state[current_depth] describes a nested element:
|
||||||
|
* - type: none/object/array/value
|
||||||
|
* - has_name: Whether the object member name was set.
|
||||||
|
*/
|
||||||
|
enum json_dumper_element_type {
|
||||||
|
JSON_DUMPER_TYPE_NONE = 0,
|
||||||
|
JSON_DUMPER_TYPE_VALUE = 1,
|
||||||
|
JSON_DUMPER_TYPE_OBJECT = 2,
|
||||||
|
JSON_DUMPER_TYPE_ARRAY = 3,
|
||||||
|
};
|
||||||
|
#define JSON_DUMPER_TYPE(state) ((enum json_dumper_element_type)((state) & 3))
|
||||||
|
#define JSON_DUMPER_HAS_NAME (1 << 2)
|
||||||
|
|
||||||
|
#define JSON_DUMPER_FLAGS_ERROR (1 << 16) /* Output flag: an error occurred. */
|
||||||
|
|
||||||
|
enum json_dumper_change {
|
||||||
|
JSON_DUMPER_BEGIN,
|
||||||
|
JSON_DUMPER_END,
|
||||||
|
JSON_DUMPER_SET_NAME,
|
||||||
|
JSON_DUMPER_SET_VALUE,
|
||||||
|
JSON_DUMPER_FINISH,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
json_puts_string(FILE *fp, const char *str)
|
||||||
|
{
|
||||||
|
static const char json_cntrl[0x20][6] = {
|
||||||
|
"u0000", "u0001", "u0002", "u0003", "u0004", "u0005", "u0006", "u0007", "b", "t", "n", "u000b", "f", "r", "u000e", "u000f",
|
||||||
|
"u0010", "u0011", "u0012", "u0013", "u0014", "u0015", "u0016", "u0017", "u0018", "u0019", "u001a", "u001b", "u001c", "u001d", "u001e", "u001f"
|
||||||
|
};
|
||||||
|
|
||||||
|
fputc('"', fp);
|
||||||
|
for (int i = 0; str[i]; i++) {
|
||||||
|
if ((guint)str[i] < 0x20) {
|
||||||
|
fputc('\\', fp);
|
||||||
|
fputs(json_cntrl[(guint)str[i]], fp);
|
||||||
|
} else {
|
||||||
|
if (str[i] == '\\' || str[i] == '"') {
|
||||||
|
fputc('\\', fp);
|
||||||
|
}
|
||||||
|
fputc(str[i], fp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fputc('"', fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the dumper state is valid for a new change. Any error will be
|
||||||
|
* sticky and prevent further dumps from succeeding.
|
||||||
|
*/
|
||||||
|
static gboolean
|
||||||
|
json_dumper_check_state(json_dumper *dumper, enum json_dumper_change change, enum json_dumper_element_type type)
|
||||||
|
{
|
||||||
|
if ((dumper->flags & JSON_DUMPER_FLAGS_ERROR)) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int depth = dumper->current_depth;
|
||||||
|
if (depth < 0 || depth >= JSON_DUMPER_MAX_DEPTH) {
|
||||||
|
/* Corrupted state, no point in continuing. */
|
||||||
|
dumper->flags |= JSON_DUMPER_FLAGS_ERROR;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
guint8 prev_state = depth > 0 ? dumper->state[depth - 1] : 0;
|
||||||
|
enum json_dumper_element_type prev_type = JSON_DUMPER_TYPE(prev_state);
|
||||||
|
|
||||||
|
gboolean ok = FALSE;
|
||||||
|
switch (change) {
|
||||||
|
case JSON_DUMPER_BEGIN:
|
||||||
|
ok = depth + 1 < JSON_DUMPER_MAX_DEPTH;
|
||||||
|
break;
|
||||||
|
case JSON_DUMPER_END:
|
||||||
|
ok = prev_type == type && !(prev_state & JSON_DUMPER_HAS_NAME);
|
||||||
|
break;
|
||||||
|
case JSON_DUMPER_SET_NAME:
|
||||||
|
/* An object name can only be set once before a value is set. */
|
||||||
|
ok = prev_type == JSON_DUMPER_TYPE_OBJECT && !(prev_state & JSON_DUMPER_HAS_NAME);
|
||||||
|
break;
|
||||||
|
case JSON_DUMPER_SET_VALUE:
|
||||||
|
if (prev_type == JSON_DUMPER_TYPE_OBJECT) {
|
||||||
|
ok = (prev_state & JSON_DUMPER_HAS_NAME);
|
||||||
|
} else if (prev_type == JSON_DUMPER_TYPE_ARRAY) {
|
||||||
|
ok = TRUE;
|
||||||
|
} else {
|
||||||
|
ok = JSON_DUMPER_TYPE(dumper->state[depth]) == JSON_DUMPER_TYPE_NONE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case JSON_DUMPER_FINISH:
|
||||||
|
ok = depth == 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!ok) {
|
||||||
|
dumper->flags |= JSON_DUMPER_FLAGS_ERROR;
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
print_newline_indent(const json_dumper *dumper, int depth)
|
||||||
|
{
|
||||||
|
if ((dumper->flags & JSON_DUMPER_FLAGS_PRETTY_PRINT)) {
|
||||||
|
fputc('\n', dumper->output_file);
|
||||||
|
for (int i = 0; i < depth; i++) {
|
||||||
|
fputs(" ", dumper->output_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints commas, newlines and indentation (if necessary). Used for array
|
||||||
|
* values, object names and normal values (strings, etc.).
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
prepare_token(json_dumper *dumper)
|
||||||
|
{
|
||||||
|
if (dumper->current_depth == 0) {
|
||||||
|
// not part of an array or object.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
guint8 prev_state = dumper->state[dumper->current_depth - 1];
|
||||||
|
|
||||||
|
// While processing the object value, reset the key state as it is consumed.
|
||||||
|
dumper->state[dumper->current_depth - 1] &= ~JSON_DUMPER_HAS_NAME;
|
||||||
|
|
||||||
|
switch (JSON_DUMPER_TYPE(prev_state)) {
|
||||||
|
case JSON_DUMPER_TYPE_OBJECT:
|
||||||
|
if ((prev_state & JSON_DUMPER_HAS_NAME)) {
|
||||||
|
// Object key already set, value follows. No indentation needed.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case JSON_DUMPER_TYPE_ARRAY:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Initial values do not need indentation.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dumper->state[dumper->current_depth]) {
|
||||||
|
fputc(',', dumper->output_file);
|
||||||
|
}
|
||||||
|
print_newline_indent(dumper, dumper->current_depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common code to close an object/array, printing a closing character (and if
|
||||||
|
* necessary, it is preceded by newline and indentation).
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
finish_token(const json_dumper *dumper, char close_char)
|
||||||
|
{
|
||||||
|
// if the object/array was non-empty, add a newline and indentation.
|
||||||
|
if (dumper->state[dumper->current_depth]) {
|
||||||
|
print_newline_indent(dumper, dumper->current_depth - 1);
|
||||||
|
}
|
||||||
|
fputc(close_char, dumper->output_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
json_dumper_begin_object(json_dumper *dumper)
|
||||||
|
{
|
||||||
|
if (!json_dumper_check_state(dumper, JSON_DUMPER_BEGIN, JSON_DUMPER_TYPE_OBJECT)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_token(dumper);
|
||||||
|
fputc('{', dumper->output_file);
|
||||||
|
|
||||||
|
dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_OBJECT;
|
||||||
|
++dumper->current_depth;
|
||||||
|
dumper->state[dumper->current_depth] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
json_dumper_set_member_name(json_dumper *dumper, const char *name)
|
||||||
|
{
|
||||||
|
if (!json_dumper_check_state(dumper, JSON_DUMPER_SET_NAME, JSON_DUMPER_TYPE_NONE)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_token(dumper);
|
||||||
|
json_puts_string(dumper->output_file, name);
|
||||||
|
fputc(':', dumper->output_file);
|
||||||
|
if ((dumper->flags & JSON_DUMPER_FLAGS_PRETTY_PRINT)) {
|
||||||
|
fputc(' ', dumper->output_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
dumper->state[dumper->current_depth - 1] |= JSON_DUMPER_HAS_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
json_dumper_end_object(json_dumper *dumper)
|
||||||
|
{
|
||||||
|
if (!json_dumper_check_state(dumper, JSON_DUMPER_END, JSON_DUMPER_TYPE_OBJECT)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_token(dumper, '}');
|
||||||
|
|
||||||
|
--dumper->current_depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
json_dumper_begin_array(json_dumper *dumper)
|
||||||
|
{
|
||||||
|
if (!json_dumper_check_state(dumper, JSON_DUMPER_BEGIN, JSON_DUMPER_TYPE_ARRAY)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_token(dumper);
|
||||||
|
fputc('[', dumper->output_file);
|
||||||
|
|
||||||
|
dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_ARRAY;
|
||||||
|
++dumper->current_depth;
|
||||||
|
dumper->state[dumper->current_depth] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
json_dumper_end_array(json_dumper *dumper)
|
||||||
|
{
|
||||||
|
if (!json_dumper_check_state(dumper, JSON_DUMPER_END, JSON_DUMPER_TYPE_ARRAY)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_token(dumper, ']');
|
||||||
|
|
||||||
|
--dumper->current_depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
json_dumper_value_string(json_dumper *dumper, const char *value)
|
||||||
|
{
|
||||||
|
if (!json_dumper_check_state(dumper, JSON_DUMPER_SET_VALUE, JSON_DUMPER_TYPE_VALUE)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_token(dumper);
|
||||||
|
json_puts_string(dumper->output_file, value);
|
||||||
|
|
||||||
|
dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
json_dumper_value_anyf(json_dumper *dumper, const char *format, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
if (!json_dumper_check_state(dumper, JSON_DUMPER_SET_VALUE, JSON_DUMPER_TYPE_VALUE)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_token(dumper);
|
||||||
|
va_start(ap, format);
|
||||||
|
vfprintf(dumper->output_file, format, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
json_dumper_finish(json_dumper *dumper)
|
||||||
|
{
|
||||||
|
if (!json_dumper_check_state(dumper, JSON_DUMPER_FINISH, JSON_DUMPER_TYPE_NONE)) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
fputc('\n', dumper->output_file);
|
||||||
|
dumper->state[0] = 0;
|
||||||
|
return TRUE;
|
||||||
|
}
|
107
wsutil/json_dumper.h
Normal file
107
wsutil/json_dumper.h
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/* wsjson.h
|
||||||
|
* Routines for serializing data as JSON.
|
||||||
|
*
|
||||||
|
* Copyright 2018, Peter Wu <peter@lekensteyn.nl>
|
||||||
|
*
|
||||||
|
* Wireshark - Network traffic analyzer
|
||||||
|
* By Gerald Combs <gerald@wireshark.org>
|
||||||
|
* Copyright 1998 Gerald Combs
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __JSON_DUMPER_H__
|
||||||
|
#define __JSON_DUMPER_H__
|
||||||
|
|
||||||
|
#include "ws_symbol_export.h"
|
||||||
|
#include <glib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* json_dumper dumper = {
|
||||||
|
* .output_file = stdout,
|
||||||
|
* .flags = JSON_DUMPER_FLAGS_PRETTY_PRINT,
|
||||||
|
* };
|
||||||
|
* json_dumper_begin_object(&dumper);
|
||||||
|
* json_dumper_set_member_name(&dumper, "key");
|
||||||
|
* json_dumper_value_string(&dumper, "value");
|
||||||
|
* json_dumper_set_member_name(&dumper, "array");
|
||||||
|
* json_dumper_begin_array(&dumper);
|
||||||
|
* json_dumper_value_anyf(&dumper, "true");
|
||||||
|
* json_dumper_value_anyf(&dumper, "1.0");
|
||||||
|
* json_dumper_begin_object(&dumper);
|
||||||
|
* json_dumper_end_object(&dumper);
|
||||||
|
* json_dumper_begin_array(&dumper);
|
||||||
|
* json_dumper_end_array(&dumper);
|
||||||
|
* json_dumper_end_array(&dumper);
|
||||||
|
* json_dumper_end_object(&dumper);
|
||||||
|
* json_dumper_finish(&dumper);
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Maximum object/array nesting depth. */
|
||||||
|
#define JSON_DUMPER_MAX_DEPTH 15
|
||||||
|
typedef struct json_dumper {
|
||||||
|
FILE *output_file; /**< Output file, must be set. */
|
||||||
|
#define JSON_DUMPER_FLAGS_PRETTY_PRINT (1 << 0) /* Enable pretty printing. */
|
||||||
|
int flags;
|
||||||
|
/* for internal use, initialize with zeroes. */
|
||||||
|
int current_depth;
|
||||||
|
guint8 state[JSON_DUMPER_MAX_DEPTH];
|
||||||
|
} json_dumper;
|
||||||
|
|
||||||
|
WS_DLL_PUBLIC void
|
||||||
|
json_dumper_begin_object(json_dumper *dumper);
|
||||||
|
|
||||||
|
WS_DLL_PUBLIC void
|
||||||
|
json_dumper_set_member_name(json_dumper *dumper, const char *name);
|
||||||
|
|
||||||
|
WS_DLL_PUBLIC void
|
||||||
|
json_dumper_end_object(json_dumper *dumper);
|
||||||
|
|
||||||
|
WS_DLL_PUBLIC void
|
||||||
|
json_dumper_begin_array(json_dumper *dumper);
|
||||||
|
|
||||||
|
WS_DLL_PUBLIC void
|
||||||
|
json_dumper_end_array(json_dumper *dumper);
|
||||||
|
|
||||||
|
WS_DLL_PUBLIC void
|
||||||
|
json_dumper_value_string(json_dumper *dumper, const char *value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dump number, "true", "false" or "null" values.
|
||||||
|
*/
|
||||||
|
WS_DLL_PUBLIC void
|
||||||
|
json_dumper_value_anyf(json_dumper *dumper, const char *format, ...)
|
||||||
|
G_GNUC_PRINTF(2, 3);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finishes dumping data. Returns TRUE if everything is okay and FALSE if
|
||||||
|
* something went wrong (open/close mismatch, missing values, etc.).
|
||||||
|
*/
|
||||||
|
WS_DLL_PUBLIC gboolean
|
||||||
|
json_dumper_finish(json_dumper *dumper);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __JSON_DUMPER_H__ */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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:
|
||||||
|
*/
|
Loading…
x
Reference in New Issue
Block a user