Refactor JSON output functions
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Mon, 12 Jun 2017 12:50:55 +0000 (14:50 +0200)
committerAnders Broman <a.broman58@gmail.com>
Fri, 16 Jun 2017 07:41:27 +0000 (07:41 +0000)
Refactors the print.c json output functions to be more intuitive and
to allow easy switching to single json keys with a json array of values
instead of duplicate json keys. With this commit the json output does
not change at all.

These changes have been tested on multiple decrypted http2 traces with
the following testing method:
- Save the pcap file as json with a build of the current master branch.
- Save the pcap file as json with a build of the master branch + this
commit.
- Compare the files for changes with the "cmp" utility.

No differences were found between files for multiple different decrypted
http2 traces. Printing with the "-x" or "-j" options also does not
produce any changes either.

Bug: 12958
Change-Id: Ibd3d39119c3a08906389aa8bbf4e2a2b21dd824e
Reviewed-on: https://code.wireshark.org/review/22064
Petri-Dish: Michael Mann <mmann78@netscape.net>
Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org>
Reviewed-by: Anders Broman <a.broman58@gmail.com>
epan/print.c
tshark.c

index d4343aaadb18ce010559fe09a6c30f6f877c1566..fad8dc5e41baa0a8debbb8a144da9985f37b21db 100644 (file)
@@ -95,7 +95,6 @@ struct _output_fields {
 static gchar *get_field_hex_value(GSList *src_list, field_info *fi);
 static void proto_tree_print_node(proto_node *node, gpointer data);
 static void proto_tree_write_node_pdml(proto_node *node, gpointer data);
-static void proto_tree_write_node_json(proto_node *node, gpointer data);
 static void proto_tree_write_node_ek(proto_node *node, gpointer data);
 static const guint8 *get_field_data(GSList *src_list, field_info *fi);
 static void pdml_write_field_hex_value(write_pdml_data *pdata, field_info *fi);
@@ -110,12 +109,36 @@ static void print_escaped_xml(FILE *fh, const char *unescaped_string);
 static void print_escaped_json(FILE *fh, const char *unescaped_string);
 static void print_escaped_ek(FILE *fh, const char *unescaped_string);
 
+typedef void (*proto_node_value_writer)(proto_node *, write_json_data *);
+static void write_json_proto_node_list(GSList *proto_node_list_head, write_json_data *data);
+static void write_json_proto_node(GSList *node_values_head,
+                                  const char *suffix,
+                                  proto_node_value_writer value_writer,
+                                  write_json_data *data);
+static void write_json_proto_node_value_list(GSList *node_values_head,
+                                             proto_node_value_writer value_writer,
+                                             write_json_data *data);
+static void write_json_proto_node_filtered(proto_node *node, write_json_data *data);
+static void write_json_proto_node_hex_dump(proto_node *node, write_json_data *data);
+static void write_json_proto_node_children(proto_node *node, write_json_data *data);
+static void write_json_proto_node_value(proto_node *node, write_json_data *data);
+
+typedef GSList* (*proto_node_children_grouper_func)(proto_node *node);
+static void write_json_proto_node_no_value(proto_node *node, write_json_data *data);
+static GSList *proto_node_group_children_by_unique(proto_node *node);
+
+static const char *proto_node_to_json_key(proto_node *node);
+
 static void print_pdml_geninfo(epan_dissect_t *edt, FILE *fh);
 
 static void proto_tree_get_node_field_values(proto_node *node, gpointer data);
 
 static gboolean json_is_first;
 
+// Function used to group a node's children. Children in the same group are represented in the json output by a single
+// json key. If multiple nodes are in a group they are wrapped in a json array in the json output.
+static proto_node_children_grouper_func json_proto_node_children_grouper = proto_node_group_children_by_unique;
+
 /* Cache the protocols and field handles that the print functionality needs
    This helps break explicit dependency on the dissectors. */
 static int proto_data = -1;
@@ -270,13 +293,6 @@ write_pdml_preamble(FILE *fh, const gchar *filename)
     fprintf(fh, "\">\n");
 }
 
-void
-write_json_preamble(FILE *fh)
-{
-    fputs("[\n", fh);
-    json_is_first = TRUE;
-}
-
 /* Check if the str match the protocolfilter. json_filter is space
    delimited string and str need to exact-match to one of the value. */
 static gboolean check_protocolfilter(gchar **protocolfilter, const char *str)
@@ -338,66 +354,6 @@ write_pdml_proto_tree(output_fields_t* fields, gchar **protocolfilter, pf_flags
     fprintf(fh, "</packet>\n\n");
 }
 
-void
-write_json_proto_tree(output_fields_t* fields,
-                      print_dissections_e print_dissections,
-                      gboolean print_hex, gchar **protocolfilter,
-                      pf_flags protocolfilter_flags, epan_dissect_t *edt,
-                      FILE *fh)
-{
-    write_json_data data;
-    char ts[30];
-    time_t t = time(NULL);
-    struct tm * timeinfo;
-
-    g_assert(edt);
-    g_assert(fh);
-
-    /* Create the output */
-    timeinfo = localtime(&t);
-    if (timeinfo != NULL)
-        strftime(ts, sizeof ts, "%Y-%m-%d", timeinfo);
-    else
-        g_strlcpy(ts, "XXXX-XX-XX", sizeof ts); /* XXX - better way of saying "Not representable"? */
-
-    if (!json_is_first)
-        fputs("  ,\n", fh);
-    else
-        json_is_first = FALSE;
-
-    fputs("  {\n", fh);
-    fprintf(fh, "    \"_index\": \"packets-%s\",\n", ts);
-    fputs("    \"_type\": \"pcap_file\",\n", fh);
-    fputs("    \"_score\": null,\n", fh);
-    fputs("    \"_source\": {\n", fh);
-    fputs("      \"layers\": {\n", fh);
-
-    if (fields == NULL || fields->fields == NULL) {
-        /* Write out all fields */
-        data.level    = 1;
-        data.fh       = fh;
-        data.src_list = edt->pi.data_src;
-        data.filter   = protocolfilter;
-        data.filter_flags = protocolfilter_flags;
-        data.print_hex = print_hex;
-        data.print_text = TRUE;
-        if (print_dissections == print_dissections_none) {
-            data.print_text = FALSE;
-        }
-
-        proto_tree_children_foreach(edt->tree, proto_tree_write_node_json,
-                                    &data);
-    } else {
-        /* Write out specified fields */
-        write_specified_fields(FORMAT_JSON, fields, edt, NULL, fh);
-    }
-
-    fputs("      }\n", fh);
-    fputs("    }\n", fh);
-    fputs("  }\n", fh);
-
-}
-
 void
 write_ek_proto_tree(output_fields_t* fields,
                     gboolean print_hex, gchar **protocolfilter,
@@ -721,246 +677,428 @@ proto_tree_write_node_pdml(proto_node *node, gpointer data)
     }
 }
 
+void
+write_json_preamble(FILE *fh)
+{
+    fputs("[\n", fh);
+    json_is_first = TRUE;
+}
 
-/* Write out a tree's data, and any child nodes, as JSON */
-static void
-proto_tree_write_node_json(proto_node *node, gpointer data)
+void
+write_json_finale(FILE *fh)
 {
-    field_info      *fi    = PNODE_FINFO(node);
-    write_json_data *pdata = (write_json_data*) data;
-    const gchar     *label_ptr;
-    gchar            label_str[ITEM_LABEL_LENGTH];
-    char            *dfilter_string;
+    fputs("\n\n]\n", fh);
+}
 
-    /* dissection with an invisible proto tree? */
-    g_assert(fi);
+void
+write_json_proto_tree(output_fields_t* fields,
+                      print_dissections_e print_dissections,
+                      gboolean print_hex, gchar **protocolfilter,
+                      pf_flags protocolfilter_flags, epan_dissect_t *edt,
+                      FILE *fh)
+{
+    char ts[30];
+    time_t t = time(NULL);
+    struct tm * timeinfo;
+    write_json_data data;
 
-    /* Text label. It's printed as a field with no name. */
-    if (fi->hfinfo->id == hf_text_only) {
-        print_indent(pdata->level + 3, pdata->fh);
+    if (!json_is_first) {
+        fputs("\n\n  ,\n", fh);
+    } else {
+        json_is_first = FALSE;
+    }
 
-        /* Get the text */
-        if (fi->rep) {
-            label_ptr = fi->rep->representation;
-        }
-        else {
-            label_ptr = "";
-        }
+    timeinfo = localtime(&t);
+    if (timeinfo != NULL) {
+        strftime(ts, sizeof ts, "%Y-%m-%d", timeinfo);
+    } else {
+        g_strlcpy(ts, "XXXX-XX-XX", sizeof ts); /* XXX - better way of saying "Not representable"? */
+    }
 
-        /* Show empty name since it is a required field */
-        fputs("\"", pdata->fh);
-        print_escaped_json(pdata->fh, label_ptr);
+    fputs("  {\n", fh);
+    fprintf(fh, "    \"_index\": \"packets-%s\",\n", ts);
+    fputs("    \"_type\": \"pcap_file\",\n", fh);
+    fputs("    \"_score\": null,\n", fh);
+    fputs("    \"_source\": {\n", fh);
+    fputs("      \"layers\": ", fh);
 
-        if (node->first_child != NULL) {
-            fputs("\": {\n", pdata->fh);
-        }
-        else {
-            if (node->next == NULL) {
-              fputs("\": \"\"\n",  pdata->fh);
-            } else {
-              fputs("\": \"\",\n",  pdata->fh);
-            }
+    if (fields == NULL || fields->fields == NULL) {
+        /* Write out all fields */
+        data.level    = 3;
+        data.fh       = fh;
+        data.src_list = edt->pi.data_src;
+        data.filter   = protocolfilter;
+        data.filter_flags = protocolfilter_flags;
+        data.print_hex = print_hex;
+        data.print_text = TRUE;
+        if (print_dissections == print_dissections_none) {
+            data.print_text = FALSE;
         }
-    }
 
-    /* Normal protocols and fields */
-    else {
         /*
-         * Hex dump -x
+         * Group nodes together by the key they will have in the json output. This is necessary to know which json keys
+         * have multiple values which need to be put in a json array in the output. A map is not required since we can
+         * easily retrieve the json key from the first value in the linked list.
          */
-        if (pdata->print_hex && (!pdata->print_text || fi->length > 0)) {
-            print_indent(pdata->level + 3, pdata->fh);
+        GSList *same_key_nodes_list = json_proto_node_children_grouper(edt->tree);
+        write_json_proto_node_list(same_key_nodes_list, &data);
+        g_slist_free(same_key_nodes_list);
 
-            fputs("\"", pdata->fh);
-            print_escaped_json(pdata->fh, fi->hfinfo->abbrev);
-            fputs("_raw", pdata->fh);
-            fputs("\": [\"", pdata->fh);
+    } else {
+        write_specified_fields(FORMAT_JSON, fields, edt, NULL, fh);
+    }
 
-            if (fi->hfinfo->bitmask!=0) {
-                switch (fi->value.ftype->ftype) {
-                    case FT_INT8:
-                    case FT_INT16:
-                    case FT_INT24:
-                    case FT_INT32:
-                        fprintf(pdata->fh, "%X", (guint) fvalue_get_sinteger(&fi->value));
-                        break;
-                    case FT_UINT8:
-                    case FT_UINT16:
-                    case FT_UINT24:
-                    case FT_UINT32:
-                        fprintf(pdata->fh, "%X", fvalue_get_uinteger(&fi->value));
-                        break;
-                    case FT_INT40:
-                    case FT_INT48:
-                    case FT_INT56:
-                    case FT_INT64:
-                        fprintf(pdata->fh, "%" G_GINT64_MODIFIER "X", fvalue_get_sinteger64(&fi->value));
-                        break;
-                    case FT_UINT40:
-                    case FT_UINT48:
-                    case FT_UINT56:
-                    case FT_UINT64:
-                    case FT_BOOLEAN:
-                        fprintf(pdata->fh, "%" G_GINT64_MODIFIER "X", fvalue_get_uinteger64(&fi->value));
-                        break;
-                    default:
-                        g_assert_not_reached();
-                }
-            }
-            else {
-                json_write_field_hex_value(pdata, fi);
-            }
+    fputs("\n", fh);
+    fputs("    }\n", fh);
+    fputs("  }", fh);
+}
 
-            /* Dump raw hex-encoded dissected information including position, length, bitmask, type */
-            fprintf(pdata->fh, "\", %" G_GINT32_MODIFIER "d", fi->start);
-            fprintf(pdata->fh, ", %" G_GINT32_MODIFIER "d", fi->length);
-            fprintf(pdata->fh, ", %" G_GUINT64_FORMAT, fi->hfinfo->bitmask);
-            fprintf(pdata->fh, ", %" G_GINT32_MODIFIER "d", (gint32)fi->value.ftype->ftype);
+/**
+ * Write a json object containing a list of key:value pairs where each key:value pair corresponds to a different json
+ * key and its associated nodes in the proto_tree.
+ * @param proto_node_list_head A 2-dimensional list containing a list of values for each different node json key. The
+ * elements themselves are a linked list of values associated with the same json key.
+ * @param data json writing metadata
+ */
+static void
+write_json_proto_node_list(GSList *proto_node_list_head, write_json_data *data)
+{
+    GSList *current_node = proto_node_list_head;
 
-            if (pdata->print_text) {
-                fputs("],\n", pdata->fh);
-            } else {
-                if (node->next == NULL && node->first_child == NULL) {
-                    fputs("]\n", pdata->fh);
-                } else {
-                    fputs("],\n", pdata->fh);
-                }
-            }
+    fputs("{\n", data->fh);
+    data->level++;
 
-        }
+    /*
+     * In most of the following if statements we cannot be sure if its the first or last if statement to be
+     * executed. Thus we need a way of knowing whether a key:value pair has already been printed in order to know
+     * if a comma should be printed before the next key:value pair. We use the delimiter_needed variable to store
+     * whether a comma needs to be written before a new key:value pair is written. Note that instead of checking
+     * before writing a new key:value pair if a comma is needed we could also check after writing a key:value pair
+     * whether a comma is needed but this would be considerably more complex since after each if statement a
+     * different condition would have to be checked. After the first value is written a delimiter is always needed so
+     * this value is never set back to FALSE after it has been set to TRUE.
+     */
+    gboolean delimiter_needed = FALSE;
 
+    // Loop over each list of nodes (differentiated by json key) and write the associated json key:value pair in the
+    // output.
+    while (current_node != NULL) {
+        // Get the list of values for the current json key.
+        GSList *node_values_list = (GSList *) current_node->data;
 
-        /* show, value, and unmaskedvalue attributes */
-        switch (fi->hfinfo->type)
-        {
-        case FT_PROTOCOL:
-            if (node->first_child != NULL) {
-                print_indent(pdata->level + 3, pdata->fh);
+        // Retrieve the json key from the first value.
+        proto_node *first_value = (proto_node *) node_values_list->data;
+        const char *json_key = proto_node_to_json_key(first_value);
+        // Check if the current json key is filtered from the output with the "-j" cli option.
+        gboolean is_filtered = data->filter != NULL && !check_protocolfilter(data->filter, json_key);
 
-                fputs("\"", pdata->fh);
-                print_escaped_json(pdata->fh, fi->hfinfo->abbrev);
+        field_info *fi = first_value->finfo;
+        char *value_string_repr = fvalue_to_string_repr(NULL, &fi->value, FTREPR_DISPLAY, fi->hfinfo->display);
 
-                fputs("\": {\n", pdata->fh);
-            } else if (pdata->print_text) {
-                print_indent(pdata->level + 3, pdata->fh);
+        // We assume all values of a json key have roughly the same layout. Thus we can use the first value to derive
+        // attributes of all the values.
+        gboolean has_value = value_string_repr != NULL;
+        gboolean has_children = first_value->first_child != NULL;
+        gboolean is_pseudo_text_field = fi->hfinfo->id == 0;
 
-                fputs("\"", pdata->fh);
-                print_escaped_json(pdata->fh, fi->hfinfo->abbrev);
+        wmem_free(NULL, value_string_repr); // fvalue_to_string_repr returns allocated buffer
 
-                fputs("\": \"", pdata->fh);
-                if (fi->rep) {
-                    print_escaped_json(pdata->fh, fi->rep->representation);
-                }
-                else {
-                    label_ptr = label_str;
-                    proto_item_fill_label(fi, label_str);
-                    print_escaped_json(pdata->fh, label_ptr);
-                }
-                if (node->next == NULL) {
-                    fputs("\"\n",  pdata->fh);
-                } else {
-                    fputs("\",\n",  pdata->fh);
-                }
-            }
-            break;
-        case FT_NONE:
-            if (node->first_child != NULL) {
-                print_indent(pdata->level + 3, pdata->fh);
+        // "-x" command line option. A "_raw" suffix is added to the json key so the textual value can be printed
+        // with the original json key. If both hex and text writing are enabled the raw information of fields whose
+        // length is equal to 0 is not written to the output. If the field is a special text pseudo field no raw
+        // information is written either.
+        if (data->print_hex && (!data->print_text || fi->length > 0) && !is_pseudo_text_field) {
+            if (delimiter_needed) fputs(",\n", data->fh);
+            write_json_proto_node(node_values_list, "_raw", write_json_proto_node_hex_dump, data);
+            delimiter_needed = TRUE;
+        }
 
-                fputs("\"", pdata->fh);
-                print_escaped_json(pdata->fh, fi->hfinfo->abbrev);
+        if (data->print_text && has_value) {
+            if (delimiter_needed) fputs(",\n", data->fh);
+            write_json_proto_node(node_values_list, "", write_json_proto_node_value, data);
+            delimiter_needed = TRUE;
+        }
 
-                fputs("\": {\n", pdata->fh);
-            } else if (pdata->print_text) {
-                print_indent(pdata->level + 3, pdata->fh);
+        if (has_children) {
+            if (delimiter_needed) fputs(",\n", data->fh);
 
-                fputs("\"", pdata->fh);
-                print_escaped_json(pdata->fh, fi->hfinfo->abbrev);
+            if (is_filtered) {
+                write_json_proto_node(node_values_list, "", write_json_proto_node_filtered, data);
+            } else {
 
-                if (node->next == NULL) {
-                  fputs("\": \"\"\n",  pdata->fh);
-                } else {
-                  fputs("\": \"\",\n",  pdata->fh);
+                // Remove protocol filter for children, if children should be included. This functionality is enabled
+                // with the "-J" command line option. We save the filter so it can be reenabled when we are done with
+                // the current key:value pair.
+                gchar **_filter = NULL;
+                if ((data->filter_flags&PF_INCLUDE_CHILDREN) == PF_INCLUDE_CHILDREN) {
+                    _filter = data->filter;
+                    data->filter = NULL;
                 }
-            }
-            break;
-        default:
-            if (pdata->print_text) {
-                print_indent(pdata->level + 3, pdata->fh);
 
-                fputs("\"", pdata->fh);
-                print_escaped_json(pdata->fh, fi->hfinfo->abbrev);
-
-                dfilter_string = fvalue_to_string_repr(NULL, &fi->value, FTREPR_DISPLAY, fi->hfinfo->display);
-                if (dfilter_string != NULL) {
-                    if (pdata->print_text) {
-                      fputs("\": \"", pdata->fh);
-                      print_escaped_json(pdata->fh, dfilter_string);
-                      if (node->first_child != NULL) {
-                        fputs("\",\n", pdata->fh);
-                      }
-                    }
+                // If a node has both a value and a set of children we print the value and the children in separate
+                // key:value pairs. These can't have the same key so whenever a value is already printed with the node
+                // json key we print the children with the same key with a "_tree" suffix added.
+                if (has_value) {
+                    write_json_proto_node(node_values_list, "_tree", write_json_proto_node_children, data);
+                } else {
+                    write_json_proto_node(node_values_list, "", write_json_proto_node_children, data);
                 }
-                wmem_free(NULL, dfilter_string);
 
-                if (node->first_child == NULL) {
-                    if (node->next == NULL) {
-                        fputs("\"\n", pdata->fh);
-                    } else {
-                        fputs("\",\n", pdata->fh);
-                    }
+                // Put protocol filter back
+                if ((data->filter_flags&PF_INCLUDE_CHILDREN) == PF_INCLUDE_CHILDREN) {
+                    data->filter = _filter;
                 }
             }
 
-            if (node->first_child != NULL) {
-                print_indent(pdata->level + 3, pdata->fh);
+            delimiter_needed = TRUE;
+        }
 
-                fputs("\"", pdata->fh);
-                print_escaped_json(pdata->fh, fi->hfinfo->abbrev);
-                fputs("_tree\": {\n", pdata->fh);
-            }
+        if (!has_value && !has_children && (data->print_text || (data->print_hex && is_pseudo_text_field))) {
+            if (delimiter_needed) fputs(",\n", data->fh);
+            write_json_proto_node(node_values_list, "", write_json_proto_node_no_value, data);
+            delimiter_needed = TRUE;
         }
 
+        current_node = current_node->next;
     }
 
-    /* We print some levels for JSON. Recurse here. */
-    if (node->first_child != NULL) {
-        if (pdata->filter == NULL || check_protocolfilter(pdata->filter, fi->hfinfo->abbrev)) {
-            gchar **_filter = NULL;
-            /* Remove protocol filter for children, if children should be included */
-            if ((pdata->filter_flags&PF_INCLUDE_CHILDREN) == PF_INCLUDE_CHILDREN) {
-                _filter = pdata->filter;
-                pdata->filter = NULL;
-            }
+    data->level--;
+    fputs("\n", data->fh);
+    print_indent(data->level, data->fh);
+    fputs("}", data->fh);
+}
 
-            pdata->level++;
-            proto_tree_children_foreach(node, proto_tree_write_node_json, pdata);
-            pdata->level--;
+/**
+ * Writes a single node as a key:value pair. The value_writer param can be used to specify how the node's value should
+ * be written.
+ * @param node_values_head Linked list containing all nodes associated with the same json key in this object.
+ * @param suffix Suffix that should be added to the json key.
+ * @param value_writer A function which writes the actual values of the node json key.
+ * @param data json writing metadata
+ */
+static void
+write_json_proto_node(GSList *node_values_head,
+                      const char *suffix,
+                      proto_node_value_writer value_writer,
+                      write_json_data *data)
+{
+    // Retrieve json key from first value.
+    proto_node *first_value = (proto_node *) node_values_head->data;
+    const char *json_key = proto_node_to_json_key(first_value);
 
-            /* Put protocol filter back */
-            if ((pdata->filter_flags&PF_INCLUDE_CHILDREN) == PF_INCLUDE_CHILDREN) {
-                pdata->filter = _filter;
-            }
-        } else {
-            print_indent(pdata->level + 4, pdata->fh);
+    print_indent(data->level, data->fh);
+    fputs("\"", data->fh);
+    print_escaped_json(data->fh, json_key);
+    print_escaped_json(data->fh, suffix);
+    fputs("\": ", data->fh);
 
-            /* print dummy field */
-            fputs("\"filtered\": \"", pdata->fh);
-            print_escaped_json(pdata->fh, fi->hfinfo->abbrev);
-            fputs("\"\n", pdata->fh);
+    write_json_proto_node_value_list(node_values_head, value_writer, data);
+}
+
+/**
+ * Writes a list of values of a single json key. If multiple values are passed they are wrapped in a json array.
+ * @param node_values_head Linked list containing all values that should be written.
+ * @param value_writer Function which writes the separate values.
+ * @param data json writing metadata
+ */
+static void
+write_json_proto_node_value_list(GSList *node_values_head, proto_node_value_writer value_writer, write_json_data *data)
+{
+    GSList *current_value = node_values_head;
+
+    // Write directly if only a single value is passed. Wrap in json array otherwise.
+    if (current_value->next == NULL) {
+        value_writer((proto_node *) current_value->data, data);
+    } else {
+        fputs("[\n", data->fh);
+        data->level++;
+
+        // Print first value outside the while loop so we write the delimiter at the start of each loop without having
+        // to check if we are at the last element.
+        print_indent(data->level, data->fh);
+        value_writer((proto_node *) current_value->data, data);
+        current_value = current_value->next;
+
+        while (current_value != NULL) {
+            fputs(",\n", data->fh);
+
+            print_indent(data->level, data->fh);
+            value_writer((proto_node *) current_value->data, data);
+            current_value = current_value->next;
         }
+
+        data->level--;
+        fputs("\n", data->fh);
+        print_indent(data->level, data->fh);
+        fputs("]", data->fh);
     }
+}
 
-    if (node->first_child != NULL) {
-        print_indent(pdata->level + 3, pdata->fh);
+/**
+ * Writes the value for a node that's filtered from the output.
+ */
+static void
+write_json_proto_node_filtered(proto_node *node, write_json_data *data)
+{
+    const char *json_key = proto_node_to_json_key(node);
 
-        /* Close off current element */
-        if (node->next == NULL) {
-            fputs("}\n", pdata->fh);
+    fputs("{\n", data->fh);
+    data->level++;
+
+    print_indent(data->level, data->fh);
+    fputs("\"filtered\": ", data->fh);
+    fputs("\"", data->fh);
+    print_escaped_json(data->fh, json_key);
+    fputs("\"\n", data->fh);
+
+    data->level--;
+    print_indent(data->level, data->fh);
+    fputs("}", data->fh);
+}
+
+/**
+ * Writes the hex dump of a node. A json array is written containing the hex dump, position, length, bitmask and type of
+ * the node.
+ */
+static void
+write_json_proto_node_hex_dump(proto_node *node, write_json_data *data)
+{
+    field_info *fi = node->finfo;
+
+    fputs("[\"", data->fh);
+
+    if (fi->hfinfo->bitmask!=0) {
+        switch (fi->value.ftype->ftype) {
+            case FT_INT8:
+            case FT_INT16:
+            case FT_INT24:
+            case FT_INT32:
+                fprintf(data->fh, "%X", (guint) fvalue_get_sinteger(&fi->value));
+                break;
+            case FT_UINT8:
+            case FT_UINT16:
+            case FT_UINT24:
+            case FT_UINT32:
+                fprintf(data->fh, "%X", fvalue_get_uinteger(&fi->value));
+                break;
+            case FT_INT40:
+            case FT_INT48:
+            case FT_INT56:
+            case FT_INT64:
+                fprintf(data->fh, "%" G_GINT64_MODIFIER "X", fvalue_get_sinteger64(&fi->value));
+                break;
+            case FT_UINT40:
+            case FT_UINT48:
+            case FT_UINT56:
+            case FT_UINT64:
+            case FT_BOOLEAN:
+                fprintf(data->fh, "%" G_GINT64_MODIFIER "X", fvalue_get_uinteger64(&fi->value));
+                break;
+            default:
+                g_assert_not_reached();
+        }
+    } else {
+        json_write_field_hex_value(data, fi);
+    }
+
+    /* Dump raw hex-encoded dissected information including position, length, bitmask, type */
+    fprintf(data->fh, "\", %" G_GINT32_MODIFIER "d", fi->start);
+    fprintf(data->fh, ", %" G_GINT32_MODIFIER "d", fi->length);
+    fprintf(data->fh, ", %" G_GUINT64_FORMAT, fi->hfinfo->bitmask);
+    fprintf(data->fh, ", %" G_GINT32_MODIFIER "d", (gint32)fi->value.ftype->ftype);
+
+    fputs("]", data->fh);
+}
+
+/**
+ * Writes the children of a node. Calls write_json_proto_node_list internally which recursively writes children of nodes
+ * to the output.
+ */
+static void
+write_json_proto_node_children(proto_node *node, write_json_data *data)
+{
+    GSList *same_key_nodes_list = json_proto_node_children_grouper(node);
+    write_json_proto_node_list(same_key_nodes_list, data);
+    g_slist_free(same_key_nodes_list);
+}
+
+/**
+ * Writes the value of a node to the output.
+ */
+static void
+write_json_proto_node_value(proto_node *node, write_json_data *data)
+{
+    field_info *fi = node->finfo;
+    // Get the actual value of the node as a string.
+    char *value_string_repr = fvalue_to_string_repr(NULL, &fi->value, FTREPR_DISPLAY, fi->hfinfo->display);
+
+    fputs("\"", data->fh);
+    print_escaped_json(data->fh, value_string_repr);
+    fputs("\"", data->fh);
+
+    wmem_free(NULL, value_string_repr);
+}
+
+/**
+ * Write the value for a node that has no value and no children. This is the empty string for all nodes except those of
+ * type FT_PROTOCOL for which the full name is written instead.
+ */
+static void
+write_json_proto_node_no_value(proto_node *node, write_json_data *data)
+{
+    field_info *fi = node->finfo;
+
+    fputs("\"", data->fh);
+
+    if (fi->hfinfo->type == FT_PROTOCOL) {
+        if (fi->rep) {
+            print_escaped_json(data->fh, fi->rep->representation);
         } else {
-            fputs("},\n", pdata->fh);
+            gchar label_str[ITEM_LABEL_LENGTH];
+            proto_item_fill_label(fi, label_str);
+            print_escaped_json(data->fh, label_str);
         }
     }
+
+    fputs("\"", data->fh);
+}
+
+/**
+ * Groups each node separately as if it had a unique json key even if it doesn't. Using this function leads to duplicate
+ * keys in the json output.
+ */
+static GSList *
+proto_node_group_children_by_unique(proto_node *node) {
+    GSList *unique_nodes_list = NULL;
+    proto_node *current_child = node->first_child;
+
+    while (current_child != NULL) {
+        GSList *unique_node = g_slist_prepend(NULL, current_child);
+        unique_nodes_list = g_slist_prepend(unique_nodes_list, unique_node);
+        current_child = current_child->next;
+    }
+
+    return g_slist_reverse(unique_nodes_list);
+}
+
+/**
+ * Returns the json key of a node. Tries to use the node's abbreviated name. If the abbreviated name is not available
+ * the representation is used instead.
+ */
+static const char *
+proto_node_to_json_key(proto_node *node)
+{
+    const char *json_key;
+    // Check if node has abbreviated name.
+    if (node->finfo->hfinfo->id != hf_text_only) {
+        json_key = node->finfo->hfinfo->abbrev;
+    } else if (node->finfo->rep != NULL) {
+        json_key = node->finfo->rep->representation;
+    } else {
+        json_key = "";
+    }
+
+    return json_key;
 }
 
 /* Write out a tree's data, and any child nodes, as JSON for EK */
@@ -1271,11 +1409,7 @@ write_pdml_finale(FILE *fh)
     fputs("</pdml>\n", fh);
 }
 
-void
-write_json_finale(FILE *fh)
-{
-    fputs("]\n", fh);
-}
+
 
 void
 write_psml_preamble(column_info *cinfo, FILE *fh)
@@ -2223,6 +2357,7 @@ static void write_specified_fields(fields_format format, output_fields_t *fields
         }
         break;
     case FORMAT_JSON:
+        fputs("{\n", fh);
         for(i = 0; i < fields->fields->len; ++i) {
             gchar *field = (gchar *)g_ptr_array_index(fields->fields, i);
 
@@ -2234,7 +2369,7 @@ static void write_specified_fields(fields_format format, output_fields_t *fields
 
                 /* Output the array of (partial) field values */
                 for (j = 0; j < (g_ptr_array_len(fv_p)); j += 2) {
-                    str = (gchar *)g_ptr_array_index(fv_p, j);
+                    str = (gchar *) g_ptr_array_index(fv_p, j);
 
                     if (j == 0) {
                         if (!first) {
@@ -2249,12 +2384,10 @@ static void write_specified_fields(fields_format format, output_fields_t *fields
 
                     if (j + 2 < (g_ptr_array_len(fv_p))) {
                         fputs(",", fh);
-                    }
-                    else {
+                    } else {
                         fputs("]", fh);
-
-                        }
                     }
+                }
 
                 first = FALSE;
                 g_ptr_array_free(fv_p, TRUE);  /* get ready for the next packet */
@@ -2262,6 +2395,8 @@ static void write_specified_fields(fields_format format, output_fields_t *fields
             }
         }
         fputc('\n',fh);
+
+        fputs("      }", fh);
         break;
     case FORMAT_EK:
         for(i = 0; i < fields->fields->len; ++i) {
index eae9acf34e2349a3a88cec72028ff5d543fa3a45..60a65cb6a7987f3c2c1ad16acd3e6957fb6cdbe9 100644 (file)
--- a/tshark.c
+++ b/tshark.c
@@ -3902,12 +3902,10 @@ print_packet(capture_file *cf, epan_dissect_t *edt)
       write_json_proto_tree(output_fields, print_dissections_expanded,
                             print_hex, protocolfilter, protocolfilter_flags,
                             edt, stdout);
-      printf("\n");
       return !ferror(stdout);
     case WRITE_JSON_RAW:
       write_json_proto_tree(output_fields, print_dissections_none, TRUE,
                             protocolfilter, protocolfilter_flags, edt, stdout);
-      printf("\n");
       return !ferror(stdout);
     case WRITE_EK:
       write_ek_proto_tree(output_fields, print_hex, protocolfilter,