summaryrefslogtreecommitdiffstats
path: root/wsutil/json_dumper.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:34:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:34:10 +0000
commite4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc (patch)
tree68cb5ef9081156392f1dd62a00c6ccc1451b93df /wsutil/json_dumper.c
parentInitial commit. (diff)
downloadwireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.tar.xz
wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.zip
Adding upstream version 4.2.2.upstream/4.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'wsutil/json_dumper.c')
-rw-r--r--wsutil/json_dumper.c689
1 files changed, 689 insertions, 0 deletions
diff --git a/wsutil/json_dumper.c b/wsutil/json_dumper.c
new file mode 100644
index 0000000..5cbc949
--- /dev/null
+++ b/wsutil/json_dumper.c
@@ -0,0 +1,689 @@
+/* json_dumper.c
+ * Routines for serializing data as JSON.
+ *
+ * Copyright 2018, Peter Wu <peter@lekensteyn.nl>
+ * Copyright (C) 2016 Jakub Zawadzki
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <glib.h>
+
+#include "json_dumper.h"
+#define WS_LOG_DOMAIN LOG_DOMAIN_WSUTIL
+
+#include <math.h>
+
+#include <wsutil/wslog.h>
+
+/*
+ * json_dumper.state[current_depth] describes a nested element:
+ * - type: none/object/array/non-base64 value/base64 value
+ * - has_name: Whether the object member name was set.
+ *
+ * (A base64 value isn't really a nested element, but that's a
+ * convenient way of handling them, with a begin call that opens
+ * the string with a double-quote, one or more calls to convert
+ * raw bytes to base64 and add them to the value, and an end call
+ * that finishes the base64 encoding, adds any remaining raw bytes
+ * in base64 encoding, and closes the string with a double-quote.)
+ */
+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,
+ JSON_DUMPER_TYPE_BASE64 = 4,
+};
+#define JSON_DUMPER_TYPE(state) ((enum json_dumper_element_type)((state) & 7))
+#define JSON_DUMPER_HAS_NAME (1 << 3)
+
+static const char *json_dumper_element_type_names[] = {
+ [JSON_DUMPER_TYPE_NONE] = "none",
+ [JSON_DUMPER_TYPE_VALUE] = "value",
+ [JSON_DUMPER_TYPE_OBJECT] = "object",
+ [JSON_DUMPER_TYPE_ARRAY] = "array",
+ [JSON_DUMPER_TYPE_BASE64] = "base64"
+};
+#define NUM_JSON_DUMPER_ELEMENT_TYPE_NAMES (sizeof json_dumper_element_type_names / sizeof json_dumper_element_type_names[0])
+
+#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_WRITE_BASE64,
+ JSON_DUMPER_FINISH,
+};
+
+/* JSON Dumper putc */
+static void
+jd_putc(const json_dumper *dumper, char c)
+{
+ if (dumper->output_file) {
+ fputc(c, dumper->output_file);
+ }
+
+ if (dumper->output_string) {
+ g_string_append_c(dumper->output_string, c);
+ }
+}
+
+/* JSON Dumper puts */
+static void
+jd_puts(const json_dumper *dumper, const char *s)
+{
+ if (dumper->output_file) {
+ fputs(s, dumper->output_file);
+ }
+
+ if (dumper->output_string) {
+ g_string_append(dumper->output_string, s);
+ }
+}
+
+static void
+jd_puts_len(const json_dumper *dumper, const char *s, size_t len)
+{
+ if (dumper->output_file) {
+ fwrite(s, 1, len, dumper->output_file);
+ }
+
+ if (dumper->output_string) {
+ g_string_append_len(dumper->output_string, s, len);
+ }
+}
+
+static void
+jd_vprintf(const json_dumper *dumper, const char *format, va_list args)
+{
+ if (dumper->output_file) {
+ vfprintf(dumper->output_file, format, args);
+ }
+
+ if (dumper->output_string) {
+ g_string_append_vprintf(dumper->output_string, format, args);
+ }
+}
+
+static void
+json_puts_string(const json_dumper *dumper, const char *str, bool dot_to_underscore)
+{
+ if (!str) {
+ jd_puts(dumper, "null");
+ return;
+ }
+
+ 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"
+ };
+
+ jd_putc(dumper, '"');
+ for (int i = 0; str[i]; i++) {
+ if ((unsigned)str[i] < 0x20) {
+ jd_putc(dumper, '\\');
+ jd_puts(dumper, json_cntrl[(unsigned)str[i]]);
+ } else if (i > 0 && str[i - 1] == '<' && str[i] == '/') {
+ // Convert </script> to <\/script> to avoid breaking web pages.
+ jd_puts(dumper, "\\/");
+ } else {
+ if (str[i] == '\\' || str[i] == '"') {
+ jd_putc(dumper, '\\');
+ }
+ if (dot_to_underscore && str[i] == '.')
+ jd_putc(dumper, '_');
+ else
+ jd_putc(dumper, str[i]);
+ }
+ }
+ jd_putc(dumper, '"');
+}
+
+static inline uint8_t
+json_dumper_get_prev_state(json_dumper *dumper)
+{
+ unsigned depth = dumper->current_depth;
+ return depth != 0 ? dumper->state[depth - 1] : 0;
+}
+
+static inline uint8_t
+json_dumper_get_curr_state(json_dumper *dumper)
+{
+ unsigned depth = dumper->current_depth;
+ return dumper->state[depth];
+}
+
+/**
+ * Called when a programming error is encountered where the JSON manipulation
+ * state got corrupted. This could happen when pairing the wrong begin/end
+ * calls, when writing multiple values for the same object, etc.
+ */
+static void
+json_dumper_bad(json_dumper *dumper, const char *what)
+{
+ dumper->flags |= JSON_DUMPER_FLAGS_ERROR;
+ if ((dumper->flags & JSON_DUMPER_FLAGS_NO_DEBUG)) {
+ /* Console output can be slow, disable log calls to speed up fuzzing. */
+ /*
+ * XXX - should this call abort()? If that flag isn't set,
+ * ws_error() wou;d call it; is there any point in continuing
+ * to do anything if we get here when fuzzing?
+ */
+ return;
+ }
+
+ if (dumper->output_file) {
+ fflush(dumper->output_file);
+ }
+ char unknown_curr_type_name[10+1];
+ char unknown_prev_type_name[10+1];
+ const char *curr_type_name, *prev_type_name;
+ uint8_t curr_state = json_dumper_get_curr_state(dumper);
+ uint8_t curr_type = JSON_DUMPER_TYPE(curr_state);
+ if (curr_type < NUM_JSON_DUMPER_ELEMENT_TYPE_NAMES) {
+ curr_type_name = json_dumper_element_type_names[curr_type];
+ } else {
+ snprintf(unknown_curr_type_name, sizeof unknown_curr_type_name, "%u", curr_type);
+ curr_type_name = unknown_curr_type_name;
+ }
+ if (dumper->current_depth != 0) {
+ uint8_t prev_state = json_dumper_get_prev_state(dumper);
+ uint8_t prev_type = JSON_DUMPER_TYPE(prev_state);
+ if (prev_type < NUM_JSON_DUMPER_ELEMENT_TYPE_NAMES) {
+ prev_type_name = json_dumper_element_type_names[prev_type];
+ } else {
+ snprintf(unknown_prev_type_name, sizeof unknown_prev_type_name, "%u", prev_type);
+ prev_type_name = unknown_prev_type_name;
+ }
+ } else {
+ prev_type_name = "(none)";
+ }
+ ws_error("json_dumper error: %s: current stack depth %u, current type %s, previous_type %s",
+ what, dumper->current_depth, curr_type_name, prev_type_name);
+ /* NOTREACHED */
+}
+
+static inline bool
+json_dumper_stack_would_overflow(json_dumper *dumper)
+{
+ if (dumper->current_depth + 1 >= JSON_DUMPER_MAX_DEPTH) {
+ json_dumper_bad(dumper, "JSON dumper stack overflow");
+ return true;
+ }
+ return false;
+}
+
+static inline bool
+json_dumper_stack_would_underflow(json_dumper *dumper)
+{
+ if (dumper->current_depth == 0) {
+ json_dumper_bad(dumper, "JSON dumper stack underflow");
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Checks that the dumper has not already had an error. Fail, and
+ * return false, to tell our caller not to do any more work, if it
+ * has.
+ */
+static bool
+json_dumper_check_previous_error(json_dumper *dumper)
+{
+ if ((dumper->flags & JSON_DUMPER_FLAGS_ERROR)) {
+ json_dumper_bad(dumper, "previous corruption detected");
+ return false;
+ }
+ return true;
+}
+
+static void
+print_newline_indent(const json_dumper *dumper, unsigned depth)
+{
+ if ((dumper->flags & JSON_DUMPER_FLAGS_PRETTY_PRINT)) {
+ jd_putc(dumper, '\n');
+ for (unsigned i = 0; i < depth; i++) {
+ jd_puts(dumper, " ");
+ }
+ }
+}
+
+/**
+ * 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;
+ }
+ uint8_t 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;
+ }
+
+ uint8_t curr_state = json_dumper_get_curr_state(dumper);
+ if (curr_state != JSON_DUMPER_TYPE_NONE) {
+ jd_putc(dumper, ',');
+ }
+ print_newline_indent(dumper, dumper->current_depth);
+}
+
+/**
+ * Common code to open an object/array/base64 value, printing
+ * an opening character.
+ *
+ * It also makes various correctness checks.
+ */
+static bool
+json_dumper_begin_nested_element(json_dumper *dumper, enum json_dumper_element_type type)
+{
+ if (!json_dumper_check_previous_error(dumper)) {
+ return false;
+ }
+
+ /* Make sure we won't overflow the dumper stack */
+ if (json_dumper_stack_would_overflow(dumper)) {
+ return false;
+ }
+
+ prepare_token(dumper);
+ switch (type) {
+ case JSON_DUMPER_TYPE_OBJECT:
+ jd_putc(dumper, '{');
+ break;
+ case JSON_DUMPER_TYPE_ARRAY:
+ jd_putc(dumper, '[');
+ break;
+ case JSON_DUMPER_TYPE_BASE64:
+ dumper->base64_state = 0;
+ dumper->base64_save = 0;
+
+ jd_putc(dumper, '"');
+ break;
+ default:
+ json_dumper_bad(dumper, "beginning unknown nested element type");
+ return false;
+ }
+
+ dumper->state[dumper->current_depth] = type;
+ /*
+ * Guaranteed not to overflow, as json_dumper_stack_would_overflow()
+ * returned false.
+ */
+ ++dumper->current_depth;
+ dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_NONE;
+ return true;
+}
+
+/**
+ * Common code to close an object/array/base64 value, printing a
+ * closing character (and if necessary, it is preceded by newline
+ * and indentation).
+ *
+ * It also makes various correctness checks.
+ */
+static bool
+json_dumper_end_nested_element(json_dumper *dumper, enum json_dumper_element_type type)
+{
+ if (!json_dumper_check_previous_error(dumper)) {
+ return false;
+ }
+
+ uint8_t prev_state = json_dumper_get_prev_state(dumper);
+
+ switch (type) {
+ case JSON_DUMPER_TYPE_OBJECT:
+ if (JSON_DUMPER_TYPE(prev_state) != JSON_DUMPER_TYPE_OBJECT) {
+ json_dumper_bad(dumper, "ending non-object nested item type as object");
+ return false;
+ }
+ break;
+ case JSON_DUMPER_TYPE_ARRAY:
+ if (JSON_DUMPER_TYPE(prev_state) != JSON_DUMPER_TYPE_ARRAY) {
+ json_dumper_bad(dumper, "ending non-array nested item type as array");
+ return false;
+ }
+ break;
+ case JSON_DUMPER_TYPE_BASE64:
+ if (JSON_DUMPER_TYPE(prev_state) != JSON_DUMPER_TYPE_BASE64) {
+ json_dumper_bad(dumper, "ending non-base64 nested item type as base64");
+ return false;
+ }
+ break;
+ default:
+ json_dumper_bad(dumper, "ending unknown nested element type");
+ return false;
+ }
+
+ if (prev_state & JSON_DUMPER_HAS_NAME) {
+ json_dumper_bad(dumper, "finishing object with last item having name but no value");
+ return false;
+ }
+
+ /* Make sure we won't underflow the dumper stack */
+ if (json_dumper_stack_would_underflow(dumper)) {
+ return false;
+ }
+
+ // 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);
+ }
+
+ switch (type) {
+ case JSON_DUMPER_TYPE_OBJECT:
+ jd_putc(dumper, '}');
+ break;
+ case JSON_DUMPER_TYPE_ARRAY:
+ jd_putc(dumper, ']');
+ break;
+ case JSON_DUMPER_TYPE_BASE64:
+ {
+ char buf[4];
+ size_t wrote;
+
+ wrote = g_base64_encode_close(false, buf, &dumper->base64_state, &dumper->base64_save);
+ jd_puts_len(dumper, buf, wrote);
+
+ jd_putc(dumper, '"');
+ break;
+ }
+ default:
+ json_dumper_bad(dumper, "endning unknown nested element type");
+ return false;
+ }
+
+ /*
+ * Guaranteed not to underflow, as json_dumper_stack_would_underflow()
+ * returned false.
+ */
+ --dumper->current_depth;
+ return true;
+}
+
+void
+json_dumper_begin_object(json_dumper *dumper)
+{
+ json_dumper_begin_nested_element(dumper, JSON_DUMPER_TYPE_OBJECT);
+}
+
+void
+json_dumper_set_member_name(json_dumper *dumper, const char *name)
+{
+ if (!json_dumper_check_previous_error(dumper)) {
+ return;
+ }
+
+ uint8_t prev_state = json_dumper_get_prev_state(dumper);
+
+ /* Only object members, not array members, have names. */
+ if (JSON_DUMPER_TYPE(prev_state) != JSON_DUMPER_TYPE_OBJECT) {
+ json_dumper_bad(dumper, "setting name on non-object nested item type");
+ return;
+ }
+ /* An object member name can only be set once before its value is set. */
+ if (prev_state & JSON_DUMPER_HAS_NAME) {
+ json_dumper_bad(dumper, "setting name twice on an object member");
+ return;
+ }
+
+ prepare_token(dumper);
+ json_puts_string(dumper, name, dumper->flags & JSON_DUMPER_DOT_TO_UNDERSCORE);
+ jd_putc(dumper, ':');
+ if ((dumper->flags & JSON_DUMPER_FLAGS_PRETTY_PRINT)) {
+ jd_putc(dumper, ' ');
+ }
+
+ dumper->state[dumper->current_depth - 1] |= JSON_DUMPER_HAS_NAME;
+}
+
+void
+json_dumper_end_object(json_dumper *dumper)
+{
+ json_dumper_end_nested_element(dumper, JSON_DUMPER_TYPE_OBJECT);
+}
+
+void
+json_dumper_begin_array(json_dumper *dumper)
+{
+ json_dumper_begin_nested_element(dumper, JSON_DUMPER_TYPE_ARRAY);
+}
+
+void
+json_dumper_end_array(json_dumper *dumper)
+{
+ json_dumper_end_nested_element(dumper, JSON_DUMPER_TYPE_ARRAY);
+}
+
+static bool
+json_dumper_setting_value_ok(json_dumper *dumper)
+{
+ uint8_t prev_state = json_dumper_get_prev_state(dumper);
+
+ switch (JSON_DUMPER_TYPE(prev_state)) {
+ case JSON_DUMPER_TYPE_OBJECT:
+ /*
+ * This value is part of an object. As such, it must
+ * have a name.
+ */
+ if (!(prev_state & JSON_DUMPER_HAS_NAME)) {
+ json_dumper_bad(dumper, "setting value of object member without a name");
+ return false;
+ }
+ break;
+ case JSON_DUMPER_TYPE_ARRAY:
+ /*
+ * This value is part of an array. As such, it's not
+ * required to have a name (and shouldn't have a name;
+ * that's already been checked in json_dumper_set_member_name()).
+ */
+ break;
+ case JSON_DUMPER_TYPE_BASE64:
+ /*
+ * We're in the middle of constructing a base64-encoded
+ * value. Only json_dumper_write_base64() can be used
+ * for that; we can't add individual values to it.
+ */
+ json_dumper_bad(dumper, "attempt to set value of base64 item to something not base64-encoded");
+ return false;
+ case JSON_DUMPER_TYPE_NONE:
+ case JSON_DUMPER_TYPE_VALUE:
+ {
+ uint8_t curr_state = json_dumper_get_curr_state(dumper);
+ switch (JSON_DUMPER_TYPE(curr_state)) {
+ case JSON_DUMPER_TYPE_NONE:
+ /*
+ * We haven't put a value yet, so we can put one now.
+ */
+ break;
+ case JSON_DUMPER_TYPE_VALUE:
+ /*
+ * This value isn't part of an object or array,
+ * and we've already put one value.
+ */
+ json_dumper_bad(dumper, "value not in object or array immediately follows another value");
+ return false;
+ case JSON_DUMPER_TYPE_OBJECT:
+ case JSON_DUMPER_TYPE_ARRAY:
+ case JSON_DUMPER_TYPE_BASE64:
+ /*
+ * This should never be the case, no matter what
+ * our callers do:
+ *
+ * JSON_DUMPER_TYPE_OBJECT can be the previous
+ * type, meaning we're in the process of adding
+ * elements to an object, but it should never be
+ * the current type;
+ *
+ * JSON_DUMPER_TYPE_ARRAY can be the previous
+ * type, meaning we're in the process of adding
+ * elements to an array, but it should never be
+ * the current type;
+ *
+ * JSON_DUMPER_TYPE_BASE64 should only be the
+ * current type if we're in the middle of
+ * building a base64 value, in which case the
+ * previous type should also be JSON_DUMPER_TYPE_BASE64,
+ * but that's not the previous type.
+ */
+ json_dumper_bad(dumper, "internal error setting value - should not happen");
+ return false;
+ default:
+ json_dumper_bad(dumper, "internal error setting value, bad current state - should not happen");
+ return false;
+ }
+ break;
+ }
+ default:
+ json_dumper_bad(dumper, "internal error setting value, bad previous state - should not happen");
+ return false;
+ }
+ return true;
+}
+
+void
+json_dumper_value_string(json_dumper *dumper, const char *value)
+{
+ if (!json_dumper_check_previous_error(dumper)) {
+ return;
+ }
+ if (!json_dumper_setting_value_ok(dumper)) {
+ return;
+ }
+
+ prepare_token(dumper);
+ json_puts_string(dumper, value, false);
+
+ dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE;
+}
+
+void
+json_dumper_value_double(json_dumper *dumper, double value)
+{
+ if (!json_dumper_check_previous_error(dumper)) {
+ return;
+ }
+
+ if (!json_dumper_setting_value_ok(dumper)) {
+ return;
+ }
+
+ prepare_token(dumper);
+ char buffer[G_ASCII_DTOSTR_BUF_SIZE] = { 0 };
+ if (isfinite(value) && g_ascii_dtostr(buffer, G_ASCII_DTOSTR_BUF_SIZE, value) && buffer[0]) {
+ jd_puts(dumper, buffer);
+ } else {
+ jd_puts(dumper, "null");
+ }
+
+ dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE;
+}
+
+void
+json_dumper_value_va_list(json_dumper *dumper, const char *format, va_list ap)
+{
+ if (!json_dumper_check_previous_error(dumper)) {
+ return;
+ }
+
+ if (!json_dumper_setting_value_ok(dumper)) {
+ return;
+ }
+
+ prepare_token(dumper);
+ jd_vprintf(dumper, format, ap);
+
+ dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE;
+}
+
+void
+json_dumper_value_anyf(json_dumper *dumper, const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ json_dumper_value_va_list(dumper, format, ap);
+ va_end(ap);
+}
+
+bool
+json_dumper_finish(json_dumper *dumper)
+{
+ if (!json_dumper_check_previous_error(dumper)) {
+ return false;
+ }
+
+ if (dumper->current_depth != 0) {
+ json_dumper_bad(dumper, "JSON dumper stack not empty at finish");
+ return false;
+ }
+
+ jd_putc(dumper, '\n');
+ dumper->state[0] = JSON_DUMPER_TYPE_NONE;
+ return true;
+}
+
+void
+json_dumper_begin_base64(json_dumper *dumper)
+{
+ json_dumper_begin_nested_element(dumper, JSON_DUMPER_TYPE_BASE64);
+}
+
+void
+json_dumper_write_base64(json_dumper* dumper, const unsigned char *data, size_t len)
+{
+ if (!json_dumper_check_previous_error(dumper)) {
+ return;
+ }
+
+ uint8_t prev_state = json_dumper_get_prev_state(dumper);
+
+ if (JSON_DUMPER_TYPE(prev_state) != JSON_DUMPER_TYPE_BASE64) {
+ json_dumper_bad(dumper, "writing base64 data to a non-base64 value");
+ return;
+ }
+
+ #define CHUNK_SIZE 1024
+ char buf[(CHUNK_SIZE / 3 + 1) * 4 + 4];
+
+ while (len > 0) {
+ size_t chunk_size = len < CHUNK_SIZE ? len : CHUNK_SIZE;
+ size_t output_size = g_base64_encode_step(data, chunk_size, false, buf, &dumper->base64_state, &dumper->base64_save);
+ jd_puts_len(dumper, buf, output_size);
+ data += chunk_size;
+ len -= chunk_size;
+ }
+
+ dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_BASE64;
+}
+
+void
+json_dumper_end_base64(json_dumper *dumper)
+{
+ json_dumper_end_nested_element(dumper, JSON_DUMPER_TYPE_BASE64);
+}